패키지, 저장소 및 앱 카테고리 검색

패키지 상세v3.0.2

rest_api_client

Flutter 프로젝트에서 REST API와 통신하기 위한 추상화입니다. 예외 처리 및 리프레시 토큰을 사용한 JWT 인증을 포함합니다.

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
589
좋아요
34
Pub 점수
160

패키지 설치

flutter pub add rest_api_client

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

REST API와 상호 작용하기 위한 명확한 추상화를 제공하는 플러터/다트 패키지입니다. 오류 관리 및 자동 리프레시 토큰 지원을 갖춘 JWT 인증과 같은 일반적인 문제를 처리하면서 HTTP 요청을 단순화합니다. 여러 플랫폼에서 API 통신을 간소화하고, 반복 코드를 줄이며 네트워크 작업의 신뢰성을 높입니다. 보안성과 일관된 백엔드 통합이 필요한 앱에 적합합니다.

사용 사례

  • 인증된 REST API와의 통합
  • HTTP 오류를 우아하게 처리하기
  • JWT 토큰 갱신 자동화
  • 확장 가능한 백엔드 통합 구축
  • API 요청 라이프사이클 관리
  • 크로스 플랫폼 네트워크 호출 지원

주요 기능

  • 내장된 JWT 및 리프레시 토큰 처리
  • 포괄적인 예외 처리
  • 크로스 플랫폼 호환성
  • 간단하고 재사용 가능한 API 클라이언트 인터페이스
  • 자동 요청 가로채기
  • 사용자 정의 가능한 기본 URL 및 헤더

적합한 대상

  • 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 개

소스 저장소

이 패키지를 사용하는 프로젝트

아직 데이터가 없습니다.