================================================================================ // File: /Users/rbs_saiful_mac_m1/office_projects/appza_tutor/packages/rest_client_kit/lib/rest_client_kit.dart ================================================================================ library rest_client_kit; export 'package:dio/dio.dart'; export 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; export 'package:dio_smart_retry/dio_smart_retry.dart'; export 'package:rest_client_kit/src/failures.dart'; export 'package:rest_client_kit/src/rest_client_kit_impl.dart'; export 'package:rest_client_kit/src/token_manager.dart'; ================================================================================ // File: /Users/rbs_saiful_mac_m1/office_projects/appza_tutor/packages/rest_client_kit/lib/src/exception_handlers.dart ================================================================================ import 'package:dio/dio.dart'; import 'package:flutter/services.dart'; import 'failures.dart'; class ExceptionHandlerInterceptor extends Interceptor { final VoidCallback? onUnAuthorizedError; ExceptionHandlerInterceptor({this.onUnAuthorizedError}); dynamic handleError(dynamic err) { final response = err.response; if (response != null) { final errorData = response.data; switch (response.statusCode) { case 400: throw BadRequest(errorData); case 401: onUnAuthorizedError?.call(); throw Unauthorized(errorData); case 403: throw Forbidden(errorData); case 404: throw NotFound(errorData); case 405: throw MethodNotAllowed(errorData); case 406: throw NotAcceptable(errorData); case 408: throw RequestTimeout(errorData); case 409: throw Conflict(errorData); case 410: throw Gone(errorData); case 411: throw LengthRequired(errorData); case 412: throw PreconditionFailed(errorData); case 413: throw PayloadTooLarge(errorData); case 414: throw URITooLong(errorData); case 415: throw UnsupportedMediaType(errorData); case 416: throw RangeNotSatisfiable(errorData); case 417: throw ExpectationFailed(errorData); case 422: throw UnprocessableEntity(errorData); case 429: throw TooManyRequests(errorData); case 500: throw InternalServerError(errorData); case 501: throw NotImplemented(errorData); case 502: throw BadGateway(errorData); case 503: throw ServiceUnavailable(errorData); case 504: throw GatewayTimeout(errorData); default: throw Unexpected(errorData); } } } } ================================================================================ // File: /Users/rbs_saiful_mac_m1/office_projects/appza_tutor/packages/rest_client_kit/lib/src/failures.dart ================================================================================ abstract class Failure { const Failure( this.name, this.code, this.error, ); final String? name; final String? code; final dynamic error; @override String toString() { return { 'name': name, 'code': code, 'error': error, }.toString(); } } /// Client Error Responses class BadRequest extends Failure { const BadRequest( dynamic error, ) : super('Bad Request', '400', error); } class Unauthorized extends Failure { const Unauthorized( dynamic error, ) : super('Unauthorized', '401', error); } class Forbidden extends Failure { const Forbidden( dynamic error, ) : super('Forbidden', '403', error); } class NotFound extends Failure { const NotFound( dynamic error, ) : super('Not Found', '404', error); } class MethodNotAllowed extends Failure { const MethodNotAllowed( dynamic error, ) : super('Method Not Allowed', '405', error); } class NotAcceptable extends Failure { const NotAcceptable( dynamic error, ) : super('Not Acceptable', '406', error); } class RequestTimeout extends Failure { const RequestTimeout( dynamic error, ) : super('Request Timeout', '408', error); } class Conflict extends Failure { const Conflict( dynamic error, ) : super('Conflict', '409', error); } class Gone extends Failure { const Gone( dynamic error, ) : super('Gone', '410', error); } class LengthRequired extends Failure { const LengthRequired( dynamic error, ) : super('Length Required', '411', error); } class PreconditionFailed extends Failure { const PreconditionFailed( dynamic error, ) : super('Precondition Failed', '412', error); } class PayloadTooLarge extends Failure { const PayloadTooLarge( dynamic error, ) : super('Payload Too Large', '413', error); } class URITooLong extends Failure { const URITooLong( dynamic error, ) : super('URI Too Long', '414', error); } class UnsupportedMediaType extends Failure { const UnsupportedMediaType( dynamic error, ) : super('Unsupported Media Type', '415', error); } class RangeNotSatisfiable extends Failure { const RangeNotSatisfiable( dynamic error, ) : super('Range Not Satisfiable', '416', error); } class ExpectationFailed extends Failure { const ExpectationFailed( dynamic error, ) : super('Expectation Failed', '417', error); } class UnprocessableEntity extends Failure { const UnprocessableEntity( dynamic error, ) : super('Unprocessable Entity', '422', error); } class TooManyRequests extends Failure { const TooManyRequests( dynamic error, ) : super('Too Many Requests', '429', error); } /// Server Error Responses class InternalServerError extends Failure { const InternalServerError( dynamic error, ) : super('Internal Server Error', '500', error); } class NotImplemented extends Failure { const NotImplemented( dynamic error, ) : super('Not Implemented', '501', error); } class BadGateway extends Failure { const BadGateway( dynamic error, ) : super('Bad Gateway', '502', error); } class ServiceUnavailable extends Failure { const ServiceUnavailable( dynamic error, ) : super('Service Unavailable', '503', error); } class GatewayTimeout extends Failure { const GatewayTimeout( dynamic error, ) : super('Gateway Timeout', '504', error); } class Unexpected extends Failure { const Unexpected( dynamic error, ) : super('Unknown Error', '500', error); } ================================================================================ // File: /Users/rbs_saiful_mac_m1/office_projects/appza_tutor/packages/rest_client_kit/lib/src/rest_client_kit_impl.dart ================================================================================ import 'package:flutter/foundation.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:rest_client_kit/rest_client_kit.dart'; import 'exception_handlers.dart'; class RestClientKit { late Dio _dio; final String baseUrl; final TokenManager? tokenManager; final int connectionTimeout; final int receiveTimeout; final CacheOptions? cacheOptions; final RetryInterceptor? retryInterceptor; final VoidCallback? onUnAuthorizedError; RestClientKit({ required this.baseUrl, this.cacheOptions, this.retryInterceptor, this.tokenManager, this.connectionTimeout = 30000, this.receiveTimeout = 30000, this.onUnAuthorizedError, }) { BaseOptions options = BaseOptions( baseUrl: baseUrl, connectTimeout: Duration(milliseconds: connectionTimeout), receiveTimeout: Duration(milliseconds: receiveTimeout), ); _dio = Dio(options); } Future> _request( String method, String path, { Object? data, Map? headers, Map? query, }) async { _setDioInterceptorList(); Options options = Options() ..contentType = 'application/json' ..method = method; if (headers != null) { options.headers ??= {}; options.headers?.addAll(headers); } return _dio .request(path, data: data, options: options, queryParameters: query) .then((value) => value) .catchError( ExceptionHandlerInterceptor( onUnAuthorizedError: onUnAuthorizedError, ).handleError, ); } Future> get( String path, { Map? query, Map? headers, bool shouldCache = false, }) { return _request('GET', path, query: query, headers: headers); } Future> post( String path, { Object? data, bool isFormData = false, Map? headers, Map? query, }) { return _request('POST', path, data: data, headers: headers, query: query); } Future> patch( String path, { Object? data, Map? headers, Map? query, }) { return _request('PATCH', path, data: data, headers: headers, query: query); } Future> put( String path, { Object? data, bool isFormData = false, Map? headers, Map? query, }) { return _request('PUT', path, data: data, headers: headers, query: query); } Future> delete( String path, { Object? data, Map? headers, Map? query, }) { return _request('DELETE', path, data: data, headers: headers, query: query); } void _setDioInterceptorList() async { /// List of interceptors to be added to the Dio instance List interceptorList = [ if (tokenManager != null) tokenManager!, if (cacheOptions != null) DioCacheInterceptor(options: cacheOptions!), if (retryInterceptor != null) retryInterceptor!, if (kDebugMode) PrettyDioLogger( requestHeader: true, requestBody: true, ), ]; /// Clear all the existing interceptors _dio.interceptors.clear(); /// Add the new interceptors _dio.interceptors.addAll(interceptorList); } } ================================================================================ // File: /Users/rbs_saiful_mac_m1/office_projects/appza_tutor/packages/rest_client_kit/lib/src/token_manager.dart ================================================================================ import 'dart:developer'; import 'package:shared_preferences/shared_preferences.dart'; import '../rest_client_kit.dart'; class TokenManager extends Interceptor { static const String _accessTokenKey = 'accessToken'; static const String _refreshTokenKey = 'refreshToken'; final String? refreshTokenEndpoint; final String accessTokenKey; final String? refreshTokenKey; TokenManager({ required this.refreshTokenEndpoint, required this.accessTokenKey, this.refreshTokenKey, }); @override void onRequest( RequestOptions options, RequestInterceptorHandler handler, ) async { final accessToken = await getAccessToken(); if (accessToken != null) { options.headers['Authorization'] = 'Bearer $accessToken'; } handler.next(options); } @override void onResponse( Response response, ResponseInterceptorHandler handler, ) async { // Function to recursively search for keys in a nested JSON object void findTokens(Map json) { for (var key in json.keys) { var value = json[key]; if ((accessTokenKey == key) && value is String) { log( 'Found access token: $value', name: 'RestClientKit/TokenStorageHandler', ); saveToken(_accessTokenKey, value); } else if ((refreshTokenKey == key) && value is String) { log( 'Found refresh token: $value', name: 'RestClientKit/TokenStorageHandler', ); saveToken(_refreshTokenKey, value); } else if (value is Map) { findTokens(value); } else if (value is List) { for (var item in value) { if (item is Map) { findTokens(item); } } } } } try { if (response.data is Map) { findTokens(response.data); } } catch (e, stackTrace) { log( 'Failed to parse tokens: $e', name: 'RestClientKit/TokenStorageHandler', error: e, stackTrace: stackTrace, ); } handler.next(response); } Future saveToken(String key, String value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(key, value); } Future getAccessToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString(_accessTokenKey); } Future getRefreshToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString(_refreshTokenKey); } }