パッケージ、リポジトリ、アプリカテゴリを検索

パッケージ詳細v3.0.2

rest_api_client

Flutter プロジェクトでの REST API との通信のための抽象化。例外処理とリフレッシュトークンによる JWT 認証を含む。

AndroidiOSWebmacOSWindowsLinux
30日間ダウンロード
589
いいね数
34
Pub ポイント
160

パッケージをインストール

flutter pub add rest_api_client

パッケージ概要

公開情報に基づく選定ガイドです。参考としてご利用ください。

REST API とのやり取りに向けた明確な抽象を提供する Flutter/Dart パッケージです。エラー管理や自動リフレッシュトークン対応の JWT 認証といった一般的な課題に対処しながら、HTTP リクエストを簡素化します。複数のプラットフォームで API 通信をスムーズにし、ボイラープレートコードを削減し、ネットワーク操作の信頼性を高めます。セキュアで一貫したバックエンド統合が必要なアプリに最適です。

用途

  • 認証済みREST APIとの統合
  • HTTPエラーを丁寧に処理する
  • JWT トークンの自動更新
  • スケーラブルなバックエンド統合の構築
  • APIリクエストライフサイクルの管理
  • クロスプラットフォームネットワーク呼び出しのサポート

主な機能

  • 組み込みの JWT およびリフレッシュトークン処理
  • 包括的な例外処理
  • クロスプラットフォーム互換性
  • シンプルで再利用可能な API クライアントインターフェース
  • 自動リクエストのインターセプト
  • 設定可能なベースURLとヘッダー

適している対象

  • RESTバックエンドを搭載したFlutterアプリ
  • セキュアなAPIアクセスを必要とするプロジェクト
  • ネットワークのボイラープレートを減らしたい開発者
  • マルチプラットフォームアプリケーションを構築するチーム
  • JWTベースの認証を使用するアプリ

注意事項

  • 適切なJWTトークンのセットアップが必要です
  • 外部のhttpパッケージに依存しています
  • カスタムエラーマッピングが必要になる場合があります
  • RESTスタイルのエンドポイントに限定
  • HTTPプロトコル以外には適していません
  • 認証フローはサーバー論理と整合性を保つ必要があります

元の README

関連するソースリポジトリの README 原文のスナップショットです。最新の内容はリポジトリをご覧ください。

Rest API Client

A production-ready HTTP client for Flutter applications that simplifies REST API communication with built-in support for JWT authentication, automatic token refresh, response caching, retry logic, and comprehensive error handling.

pub package

Features

  • JWT Authentication - Automatic token management with secure storage (Keychain/EncryptedSharedPreferences)
  • Token Refresh - Two strategies: response-and-retry or preemptive refresh before expiry
  • Response Caching - Intelligent caching with configurable expiration
  • Retry Logic - Exponential backoff for failed requests
  • Request Deduplication - Prevents duplicate concurrent requests
  • Exception Handling - Typed exceptions with global error stream
  • Multi-Platform - Works on iOS, Android, Web, and Desktop

Installation

Add to your pubspec.yaml:

dependencies:
  rest_api_client: ^2.4.1

Quick Start

import 'package:rest_api_client/rest_api_client.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize storage (call once per app lifetime)
  await RestApiClient.initFlutter();

  // Create the client
  final client = RestApiClientImpl(
    options: RestApiClientOptions(
      baseUrl: 'https://api.example.com',
    ),
  );

  await client.init();

  // Make requests
  final result = await client.get('/users');

  if (result.hasData) {
    print(result.data);
  }
}

Configuration

Basic Options
RestApiClientImpl(
  options: RestApiClientOptions(
    baseUrl: 'https://api.example.com',
    cacheEnabled: true,  // Enable response caching
    overrideBadCertificate: true,  // For development only!
  ),
)
Authentication

Configure JWT authentication with automatic token refresh:

RestApiClientImpl(
  options: RestApiClientOptions(baseUrl: 'https://api.example.com'),
  authOptions: AuthOptions(
    // Endpoint for refreshing tokens
    refreshTokenEndpoint: '/auth/refresh',

    // Token refresh strategy
    refreshTokenExecutionType: RefreshTokenStrategy.responseAndRetry,

    // Extract tokens from refresh response
    resolveJwt: (response) => response.data['accessToken'],
    resolveRefreshToken: (response) => response.data['refreshToken'],

    // Custom request body for token refresh
    refreshTokenBodyBuilder: (jwt, refreshToken) => {
      'accessToken': jwt,
      'refreshToken': refreshToken,
    },

    // Paths that don't require authentication
    ignoreAuthForPaths: ['/auth/login', '/auth/register'],

    // Use secure storage (Keychain/EncryptedSharedPreferences)
    useSecureStorage: true,
  ),
)
Token Refresh Strategies
Strategy Description
responseAndRetry Wait for 401 response, refresh token, retry request (default)
preemptivelyRefreshBeforeExpiry Check JWT expiry before each request, refresh proactively
Caching
RestApiClientImpl(
  options: RestApiClientOptions(
    baseUrl: 'https://api.example.com',
    cacheEnabled: true,
  ),
  cacheOptions: CacheOptions(
    cacheLifetimeDuration: Duration(days: 7),
    useAuthorization: true,  // Include JWT in cache key
    resetOnRestart: false,   // Persist cache across app restarts
  ),
)
Retry Logic

Enable automatic retry with exponential backoff:

RestApiClientImpl(
  options: RestApiClientOptions(baseUrl: 'https://api.example.com'),
  retryOptions: RetryOptions(
    enabled: true,
    maxRetries: 3,
    initialDelay: Duration(milliseconds: 500),
    maxDelay: Duration(seconds: 30),
    backoffMultiplier: 2.0,
    retryableStatusCodes: [408, 429, 500, 502, 503, 504],
    retryOnConnectionError: true,
  ),
)
Logging
RestApiClientImpl(
  options: RestApiClientOptions(baseUrl: 'https://api.example.com'),
  loggingOptions: LoggingOptions(
    logNetworkTraffic: true,  // Log requests/responses to console
  ),
)
Exception Handling
RestApiClientImpl(
  options: RestApiClientOptions(baseUrl: 'https://api.example.com'),
  exceptionOptions: ExceptionOptions(
    // Custom parser for validation errors from your API
    resolveValidationErrorsMap: (response) {
      if (response?.data?['errors'] != null) {
        return response.data['errors'];
      }
      return {};
    },
  ),
)
Custom Interceptors

Add Dio interceptors for custom logic:

RestApiClientImpl(
  options: RestApiClientOptions(baseUrl: 'https://api.example.com'),
  interceptors: [
    InterceptorsWrapper(
      onRequest: (options, handler) {
        // Add custom headers, logging, etc.
        options.headers['X-Custom-Header'] = 'value';
        handler.next(options);
      },
      onResponse: (response, handler) {
        // Process responses
        handler.next(response);
      },
      onError: (error, handler) {
        // Handle errors
        handler.next(error);
      },
    ),
  ],
)

Making Requests

Basic Requests
// GET request
final result = await client.get('/users');

// GET with query parameters
final result = await client.get('/users', queryParameters: {'page': 1, 'limit': 10});

// POST request
final result = await client.post('/users', data: {'name': 'John', 'email': 'john@example.com'});

// PUT request
final result = await client.put('/users/1', data: {'name': 'John Updated'});

// PATCH request
final result = await client.patch('/users/1', data: {'name': 'John Patched'});

// DELETE request
final result = await client.delete('/users/1');

// HEAD request
final result = await client.head('/users/1');
With Type Conversion

Use onSuccess to parse responses into typed objects:

final result = await client.get<User>(
  '/users/1',
  onSuccess: (data) => User.fromJson(data),
);

if (result.hasData) {
  User user = result.data!;
  print(user.name);
}
Per-Request Options
final result = await client.get(
  '/users',
  options: RestApiClientRequestOptions(
    headers: {'X-Custom': 'value'},
    contentType: 'application/json',
    requiresAuth: false,    // Skip authentication for this request
    silentException: true,  // Don't broadcast exceptions
  ),
);

Caching Strategies

Cache-First (Stale-While-Revalidate)

Returns cached data immediately, then fetches fresh data:

await for (final result in client.getStreamed('/users')) {
  if (result.hasData) {
    // First emission: cached data (if available)
    // Second emission: fresh network data
    updateUI(result.data);
  }
}
Cache-Only

Get data from cache only (no network request):

final result = await client.getCached('/users');
if (result.hasData) {
  print('Cached data: ${result.data}');
}
Cache-Or-Network

Try cache first, fall back to network:

final result = await client.getCachedOrNetwork('/users');
Custom Cache Duration
final result = await client.get(
  '/users',
  cacheLifetimeDuration: Duration(hours: 1),
);

Authentication

Login Flow
// 1. Make login request
final loginResult = await client.post('/auth/login', data: {
  'email': 'user@example.com',
  'password': 'password123',
});

// 2. Authorize the client with received tokens
if (loginResult.hasData) {
  await client.authorize(
    jwt: loginResult.data['accessToken'],
    refreshToken: loginResult.data['refreshToken'],
  );
}

// 3. All subsequent requests will include the JWT
final userResult = await client.get('/users/me');
Check Authorization Status
final isLoggedIn = await client.isAuthorized();
Logout
await client.unAuthorize();
await client.clearStorage();  // Clear tokens and cached data

Error Handling

Result Object

All requests return a Result<T> object:

final result = await client.get('/users');

// Check for data
if (result.hasData) {
  print(result.data);
}

// Check for errors
if (result.isError) {
  print(result.exception);
}

// Check specific error types
if (result.isConnectionError) {
  print('No internet connection');
}

// Access HTTP status
print(result.statusCode);      // e.g., 200, 404, 500
print(result.statusMessage);   // e.g., "OK", "Not Found"
Exception Types
Exception HTTP Status Description
ValidationException 400, 404, 422 Input validation errors
UnauthorizedException 401 Authentication required
ForbiddenException 403 Access denied
ServerErrorException 500, 502 Server-side errors
NetworkErrorException - Connection issues
Global Exception Stream

Listen to all exceptions across the app:

client.exceptionHandler.exceptions.stream.listen((exception) {
  if (exception is UnauthorizedException) {
    // Redirect to login
    navigatorKey.currentState?.pushReplacementNamed('/login');
  } else if (exception is NetworkErrorException) {
    // Show offline banner
    showSnackBar('No internet connection');
  }
});
Suppress Exceptions
// Suppress for a single request
final result = await client.get(
  '/users',
  options: RestApiClientRequestOptions(silentException: true),
);

File Downloads

final result = await client.download(
  '/files/document.pdf',
  '/path/to/save/document.pdf',
  onReceiveProgress: (received, total) {
    final progress = (received / total * 100).toStringAsFixed(0);
    print('Download progress: $progress%');
  },
);

Headers

// Set content type
client.setContentType('application/json');

// Set language
client.setAcceptLanguageHeader('en');

// Add custom header
client.addOrUpdateHeader(key: 'X-Api-Key', value: 'your-api-key');

// Access current headers
print(client.headers);

Complete Example

import 'package:flutter/material.dart';
import 'package:rest_api_client/rest_api_client.dart';

late RestApiClient apiClient;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await RestApiClient.initFlutter();

  apiClient = RestApiClientImpl(
    options: RestApiClientOptions(
      baseUrl: 'https://api.example.com',
      cacheEnabled: true,
    ),
    authOptions: AuthOptions(
      refreshTokenEndpoint: '/auth/refresh',
      resolveJwt: (r) => r.data['accessToken'],
      resolveRefreshToken: (r) => r.data['refreshToken'],
    ),
    loggingOptions: LoggingOptions(logNetworkTraffic: true),
    retryOptions: RetryOptions(enabled: true, maxRetries: 3),
  );

  await apiClient.init();

  // Listen to auth errors globally
  apiClient.exceptionHandler.exceptions.stream.listen((e) {
    if (e is UnauthorizedException) {
      // Handle logout
    }
  });

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: UserListScreen(),
    );
  }
}

class UserListScreen extends StatefulWidget {
  @override
  State<UserListScreen> createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  List<dynamic> users = [];
  bool isLoading = true;

  @override
  void initState() {
    super.initState();
    loadUsers();
  }

  Future<void> loadUsers() async {
    final result = await apiClient.get('/users');

    setState(() {
      isLoading = false;
      if (result.hasData) {
        users = result.data;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    if (isLoading) {
      return Scaffold(body: Center(child: CircularProgressIndicator()));
    }

    return Scaffold(
      appBar: AppBar(title: Text('Users')),
      body: ListView.builder(
        itemCount: users.length,
        itemBuilder: (context, index) {
          return ListTile(title: Text(users[index]['name']));
        },
      ),
    );
  }
}

API Reference

RestApiClient Methods
Method Description
init() Initialize the client (required)
get() GET request with optional caching
getCached() Get from cache only
getCachedOrNetwork() Cache-first, network fallback
getStreamed() Stale-while-revalidate pattern
post() POST request
postCached() Get cached POST response
postStreamed() Streamed POST with cache
put() PUT request
patch() PATCH request
delete() DELETE request
head() HEAD request
download() Download file with progress
authorize() Set JWT and refresh token
unAuthorize() Clear authorization
isAuthorized() Check auth status
clearStorage() Clear tokens and cache

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Repository: https://github.com/salihagic/rest_api_client

カテゴリー

依存関係

依存関係一覧 5 件

ソースリポジトリ

このパッケージを使用しているプロジェクト

データはまだありません。