rspl_network_manager 0.0.2
rspl_network_manager: ^0.0.2 copied to clipboard
A Flutter plugin that simplifies networking with configurable logging, token persistence, mock API support, automatic token refresh, connectivity checks, and proxy configuration.
RSPL Network Manager #
RSPLNetworkManager is a production-ready networking wrapper for Flutter apps, built on top of Dio. It simplifies HTTP requests with built-in logging, token management, offline support, and automatic token refreshing.
It abstracts away common boilerplate code associated with HTTP clients, offering a clean API for handling authentication, logging, error handling, and connectivity states.
Table of Contents #
- Features
- Platform Support
- Requirements
- Dependencies & Configuration
- Getting Started
- Detailed Usage
- Folder Structure
- Contributing
- License
Features #
- π Configurable Logging: Debug your network traffic with ease using configurable logging levels (request/response headers, body, errors).
- π Secure Token Storage: Seamless token persistence using
flutter_secure_storagewith automatic injection into requests. - π Auto Token Refresh: Built-in mechanism to handle 401 errors and refresh expired access tokens automatically.
- π‘ Connectivity Awareness: Automatically checks for internet connection before making requests to prevent unnecessary failures.
- π Proxy Support: Easy configuration for proxy settings during debugging.
- π§ͺ Testable: Designed with dependency injection in mind, making it easy to unit test your networking logic.
Platform Support #
- Android β API Level: 21+
- iOS β iOS 12.0+
- macOS β macOS 10.14+
- Web β All modern browsers
Note
Proxy configuration is not supported on web. The proxyConfig parameter in DioFactory.create() will be ignored when running on web platforms.
Requirements #
- Dart: >=3.5.0 <4.0.0
- Flutter: Flutter 3.24.0+
- Dio: ^5.0.0
Build Requirements #
- Android:
- Gradle 8.5+ (for Java 17+ compatibility)
- Java 17 or higher (Java 22 recommended)
- Android Gradle Plugin 8.1.4+
- Kotlin 1.9.0+
- iOS/macOS: Xcode 14.0+
Permissions #
- Android: Add
INTERNETpermission inAndroidManifest.xml. - iOS: No explicit permissions required for basic networking.
- macOS: Add
com.apple.security.network.cliententitlement.
Dependencies & Configuration #
This package relies on the following core dependencies. Please review their documentation for any specific platform configurations:
| Package | Purpose |
|---|---|
| dio | Core HTTP client for making network requests. |
| flutter_secure_storage | Secure storage for persisting authentication tokens. |
| dio_smart_retry | Flexible retry logic for failed requests. |
| connectivity_plus | Network connectivity detection. |
Important Configuration Notes #
flutter_secure_storage
- macOS: You must add the
Keychain Sharingcapability in Xcode and enablekeychain-access-groupsin your entitlements file (as shown in the example app). - Android: Can be configured to use
EncryptedSharedPreferences.
connectivity_plus
- Android: Uses
ConnectivityManager. EnsureACCESS_NETWORK_STATEpermission is in your manifest (usually added automatically). - iOS/macOS: Uses
NWPathMonitor. No extra configuration needed for basic usage.
Getting Started #
1. Install #
Add the dependency to your pubspec.yaml:
dependencies:
rspl_network_manager: ^0.0.2
Then run:
flutter pub get
2. Import #
import 'package:rspl_network_manager/rspl_network_manager.dart';
Detailed Usage #
1. Creating a Client (DioFactory) #
The DioFactory class simplifies creating a Dio instance with common configurations like base URL, timeouts, and headers.
// Simple setup
final dioFactory = DioFactory('https://api.example.com');
final dio = dioFactory.create();
// With custom headers
final dio = dioFactory.create(
headers: {
'ApiKey': 'your-api-key',
'Accept-Language': 'en-US',
},
);
2. Logging Network Traffic #
Use WSLoggerInterceptor to see detailed logs of your network requests in the console. This is crucial for debugging.
dio.interceptors.add(
WSLoggerInterceptor(
request: true, // Log request method and URL
requestHeader: true, // Log request headers
requestBody: true, // Log request body
responseHeader: true, // Log response headers
responseBody: true, // Log response body
error: true, // Log errors
compact: true, // Use compact format for cleaner logs
),
);
3. Checking Connectivity #
The ConnectivityInterceptor checks for an active internet connection before attempting a request. If offline, it throws a NoInternetConnectionException immediately.
dio.interceptors.add(ConnectivityInterceptor());
// Handling the exception
try {
await dio.get('/endpoint');
} on DioException catch (e) {
if (e.error is NoInternetConnectionException) {
print('No internet connection! Please check your settings.');
}
}
4. Token Management #
This package handles the entire lifecycle of authentication tokens: storage, injection, and refreshing.
Secure Storage
Use KeyChainTokenPersister to securely save tokens. It uses flutter_secure_storage under the hood.
final tokenPersister = KeyChainTokenPersister();
// Save tokens after login
await tokenPersister.save(
token: 'access_token_value',
refreshToken: 'refresh_token_value',
);
// Clear tokens on logout
await tokenPersister.remove();
Injecting Tokens
The TokenInterceptor automatically adds the Authorization: Bearer <token> header to your requests. You can exclude specific paths (like login or register) where a token isn't needed.
dio.interceptors.add(
TokenInterceptor(
tokenPersister: tokenPersister,
exceptionList: [
'/auth/login',
'/auth/register',
'/auth/forgot-password',
],
),
);
Automatic Token Refresh (Deep Dive)
This is one of the most powerful features. When an API returns a 401 Unauthorized error (usually meaning the access token expired), the package can automatically:
- Catch the error.
- Refresh the token using your refresh token.
- Retry the original request with the new token.
This happens seamlessly; the user (and your calling code) never knows it failed!
The 3 Key Components:
ITokenRefresher: An interface you implement to tell the package how to call your specific refresh API.TokenRetryEvaluator: Logic that decides when to retry (e.g., "If status is 401, try to refresh").TokenRefreshInterceptorWrapper: The manager that ties it all together.
Step 1: Implement ITokenRefresher
Create a class that implements ITokenRefresher. This is where you put your API call to refresh the token.
class MyTokenRefresher implements ITokenRefresher {
final Dio dio;
final ITokenPersister tokenPersister;
MyTokenRefresher(this.dio, this.tokenPersister);
@override
Future<bool> refreshToken() async {
try {
// 1. Get the refresh token from storage
final refreshToken = await tokenPersister.refreshToken;
if (refreshToken == null) {
throw TokenRefreshFailedException(
'No refresh token available',
reason: RefreshFailureReason.noRefreshToken,
);
}
// 2. Call your backend's refresh endpoint
// Note: We use the same Dio instance, but the 'TokenRetryEvaluator'
// ensures this request doesn't trigger a loop (see Step 2).
final response = await dio.post('/auth/refresh', data: {
'refreshToken': refreshToken,
});
if (response.statusCode == 200) {
// 3. Save the NEW tokens
await tokenPersister.save(
token: response.data['accessToken'],
refreshToken: response.data['refreshToken'],
);
return true; // Success!
}
throw TokenRefreshFailedException(
'Invalid response from refresh endpoint',
reason: RefreshFailureReason.serverError,
);
} on TokenRefreshFailedException {
rethrow; // Re-throw our custom exception
} on DioException catch (e) {
// Determine failure reason based on error type
RefreshFailureReason reason;
if (e.response?.statusCode == 401 || e.response?.statusCode == 403) {
reason = RefreshFailureReason.refreshTokenExpired;
} else {
reason = RefreshFailureReason.networkError;
}
throw TokenRefreshFailedException(
'Token refresh failed: ${e.message}',
originalError: e,
reason: reason,
);
} catch (e) {
throw TokenRefreshFailedException(
'Unexpected error during token refresh',
reason: RefreshFailureReason.unknown,
);
}
}
}
Handling Token Refresh Failures:
You can catch TokenRefreshFailedException to handle refresh failures specifically:
try {
final response = await dio.get('/api/profile');
} on TokenRefreshFailedException catch (e) {
// Token refresh failed - handle specifically
print('Refresh failed: ${e.message}');
print('Reason: ${e.reason}');
// Force logout if refresh token expired
if (e.reason == RefreshFailureReason.refreshTokenExpired) {
navigateToLogin();
}
} on DioException catch (e) {
// Handle other network errors
print('Request failed: ${e.message}');
}
The exception provides:
message: Human-readable error descriptionreason: Categorized failure type (refreshTokenExpired,networkError,serverError,noRefreshToken,unknown)originalError: The underlyingDioExceptionif available
Step 2: Configure TokenRetryEvaluator
The TokenRetryEvaluator is a helper class provided by the package. It checks if the error is a 401/403 and triggers the refresher.
CRITICAL: You MUST provide
exceptionalUris. These are endpoints that should NEVER trigger a retry loop. Always include your login and refresh endpoints here!
final retryEvaluator = TokenRetryEvaluator(
tokenRefresher: myTokenRefresher,
retryCodes: [401, 403], // Retry on these codes
exceptionalUris: [
'/auth/login', // Don't retry login
'/auth/refresh-token' // CRITICAL: Don't retry the refresh endpoint itself!
],
).evaluate;
Step 3: Tie it all together
Now, add the TokenRefreshInterceptorWrapper to your Dio instance.
Important: Add this interceptor FIRST (index 0). In Dio, response interceptors are executed in reverse order (Last In, First Out). Adding it first ensures it's the last to see the error, allowing other interceptors (like Loggers) to see the 401 error before it's "fixed" by the refresh logic.
// 1. Setup dependencies
final tokenPersister = KeyChainTokenPersister();
final dio = DioFactory('https://api.example.com').create();
// 2. Create your refresher
final refresher = MyTokenRefresher(dio, tokenPersister);
// 3. Create the wrapper
final refreshWrapper = TokenRefreshInterceptorWrapper(
dio: dio,
tokenRefresher: refresher,
retryEvaluator: TokenRetryEvaluator(
tokenRefresher: refresher,
retryCodes: [401, 403],
exceptionalUris: ['/auth/login', '/auth/refresh-token'],
).evaluate,
);
// 4. Add interceptors (Order matters!)
dio.interceptors.add(refreshWrapper.interceptor); // Add FIRST
dio.interceptors.add(ConnectivityInterceptor());
dio.interceptors.add(WSLoggerInterceptor(...));
dio.interceptors.add(TokenInterceptor(...)); // Add LAST
5. Advanced Configuration #
Proxy Support (Debugging)
What is it? A proxy allows you to route your app's network traffic through a tool like Charles Proxy, Fiddler, or Wireshark.
Why use it? Sometimes console logs aren't enough. You might need to inspect the exact raw bytes being sent, modify requests on the fly, or simulate slow networks.
How to use it:
final dio = dioFactory.create(
proxyConfig: ProxyConfig(
ip: '192.168.1.10', // Your computer's local IP address
port: 8888, // The port your proxy tool is listening on
),
);
Warning
Proxy configuration is not supported on web platforms. The proxyConfig parameter will be ignored when running on web. Proxy support is only available on Android, iOS, and macOS.
Note: Ensure your device and computer are on the same Wi-Fi network.
Custom Timeouts
By default, DioFactory sets reasonable timeouts (15s receive/send, 5s connect). If you need to change these, use createWithOptions:
final options = BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
);
final dio = dioFactory.createWithOptions(options);
Folder Structure #
rspl_network_manager/
ββ lib/
β ββ rspl_network_manager.dart # Main package export
β ββ src/
β ββ dio_factory.dart # Dio instance creator
β ββ interceptors/ # Network interceptors
β β ββ token_interceptor.dart
β β ββ logger_interceptor.dart
β β ββ ...
β ββ token/ # Token management
β β ββ token_persister.dart
β β ββ token_refresher.dart
β ββ ...
ββ example/ # Complete example app
ββ test/ # Unit tests
ββ CHANGELOG.md # Version history
ββ LICENSE # MIT License
ββ README.md # Documentation
Example #
For a complete, working example including login, profile fetching, and automatic token refresh, see the example directory.
Note:
The example app uses a public mock API with demo login credentials:{ "email": "john@mail.com", "password": "changeme" }These credentials are provided by the Platzi Fake API and may change over time.
If the example app throws authentication or API errors, verify the latest valid credentials on the official API documentation:
https://fakeapi.platzi.com/en/rest/auth-jwt/
Contributing #
Contributions welcome! Please read:
- CONTRIBUTING.md β setup, branch strategy, commit convention
- CODE_OF_CONDUCT.md
Run checks before push:
dart format .flutter analyzeflutter test
User Privacy Notes #
- This package does not collect any user information or share data with third-party services.
Author, Maintainers & Acknowledgements #
- Developed by Rishabh Software.
- Thanks to the Flutter community for the amazing packages used in this project.
License #
This package is licensed under the MIT License. See LICENSE for details.