Starting with Dio
For REST Api request, here we will use DIO Package.
Dio Setup with Riverpod
This setup ensures a single Dio instance (Singleton), centralizes configuration (BaseOptions), and maps low-level DioException to your custom AppException classes automatically.
Core Dio Client (handles exceptions)
dart
import 'package:dio/dio.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
// Reuse AppExceptionsclass NetworkException extends AppException { /* ... */ }class UnauthorizedException extends AppException { /* ... */ }class ServerException extends AppException { /* ... */ }
class ApiClient { late final Dio _dio;
ApiClient() { _dio = Dio(BaseOptions( baseUrl: 'https://api.example.com/', connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), headers: {'Content-Type': 'application/json'}, ));
_dio.interceptors.addAll([ LogInterceptor(responseBody: true, error: true), InterceptorsWrapper( onRequest: (options, handler) { // token: options.headers['Authorization'] = ...; handler.next(options); }, onError: (e, handler) => handler.next(e), ), ]); }
Future<T> get<T>(String path, {Map<String, dynamic>? query}) async { try { final res = await _dio.get(path, queryParameters: query); return res.data as T; } on DioException catch (e, st) { _mapDioError(e, st); } catch (e, st) { throw NetworkException('Unexpected', cause: e, stackTrace: st); } throw const NetworkException('Unknown'); // unreachable }
Future<T> post<T>(String path, dynamic data) async { try { final res = await _dio.post(path, data: data); return res.data as T; } on DioException catch (e, st) { _mapDioError(e, st); } catch (e, st) { throw NetworkException('Unexpected', cause: e, stackTrace: st); } }
void _mapDioError(DioException e, StackTrace st) { final status = e.response?.statusCode;
if (status == 401) throw UnauthorizedException('Unauthorized', cause: e, stackTrace: st); else if ([408, 500, 502, 503, 504].contains(status)) throw ServerException('Server error', cause: e, stackTrace: st); else if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout || e.type == DioExceptionType.connectionError) throw NetworkException('Network', cause: e, stackTrace: st);
throw ServerException(e.message ?? 'Failed', cause: e, stackTrace: st); }}2) Providers (providers.dart)
dart
// Singleton-like ApiClient (keepAlive: true for app-life)final apiProvider = Provider<ApiClient>((_) => ApiClient());
// Repo (depends on ApiClient)final userRepoProvider = Provider<UserRepo>((ref) => UserRepo(ref.read(apiProvider)));Now Do Dependency injection with your service or repo classes.