搜索插件、代码仓与 APP 分类

插件详情v3.0.2

rest_api_client

Flutter 项目中与 REST API 通信的抽象。包含异常处理和带有刷新令牌的身份验证的 JWT。

AndroidiOSWebmacOSWindowsLinux
近 30 天下载
589
喜欢数
34
Pub 分数
160

安装插件

flutter pub add rest_api_client

插件介绍

基于插件公开信息、社区数据与项目使用情况整理的选型参考。

一个 Flutter/Dart 包,为与 REST APIs 交互提供清晰的抽象。它简化了 HTTP 请求,同时处理常见的挑战,如错误管理以及带有自动刷新令牌支持的 JWT 认证。旨在跨多个平台简化 API 通信,减少样板代码,并增强网络操作的可靠性。非常适合需要安全、一致后端集成的应用程序。

适合场景

  • 与经过身份验证的 REST API 集成
  • 优雅地处理HTTP错误
  • 自动刷新 JWT 令牌
  • 构建可扩展的后端集成
  • 管理 API 请求生命周期
  • 支持跨平台网络调用

主要特点

  • 内置 JWT 和刷新令牌处理
  • 全面的异常处理
  • 跨平台兼容性
  • 简单、可重用的 API 客户端接口
  • 自动请求拦截
  • 可配置的基 URL 和标头

适合对象

  • Flutter 应用程序与 REST 后端
  • 需要安全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 项

源码仓库

使用此插件的项目

暂无相关数据。