networkResponse<T> method
Future<NyResponse<T> >
networkResponse<T>({
- required dynamic request(
- Dio api
- dynamic handleSuccess(
- NyResponse<
T> response
- NyResponse<
- dynamic handleFailure(
- NyResponse<
T> response
- NyResponse<
- String? bearerToken,
- String? baseUrl,
- bool useUndefinedResponse = true,
- bool shouldRetry = true,
- bool? shouldSetAuthHeaders,
- int? retry,
- Duration? retryDelay,
- bool retryIf(
- DioException dioException
- Duration? connectionTimeout,
- Duration? receiveTimeout,
- Duration? sendTimeout,
- Duration? cacheDuration,
- String? cacheKey,
- CachePolicy? cachePolicy,
- bool? checkConnectivity,
- Map<
String, dynamic> ? headers,
Implementation
Future<NyResponse<T>> networkResponse<T>({
required Function(Dio api) request,
Function(NyResponse<T> response)? handleSuccess,
Function(NyResponse<T> response)? handleFailure,
String? bearerToken,
String? baseUrl,
bool useUndefinedResponse = true,
bool shouldRetry = true,
bool? shouldSetAuthHeaders,
int? retry,
Duration? retryDelay,
bool Function(DioException dioException)? retryIf,
Duration? connectionTimeout,
Duration? receiveTimeout,
Duration? sendTimeout,
Duration? cacheDuration,
String? cacheKey,
CachePolicy? cachePolicy,
bool? checkConnectivity,
Map<String, dynamic>? headers,
}) async {
headers ??= {};
Stopwatch stopwatch = Stopwatch();
String? requestTime;
// Resolve cache policy - default to networkOnly if no cache settings
CachePolicy effectivePolicy = cachePolicy ?? CachePolicy.networkOnly;
String? cacheKeyRequest = cacheKey ?? _cacheKey;
Duration? cacheDurationRequest = cacheDuration ?? _cacheDuration;
// If cache key/duration provided but no policy, use cacheFirst for backward compatibility
if (cachePolicy == null &&
(cacheKeyRequest != null || cacheDurationRequest != null)) {
effectivePolicy = CachePolicy.cacheFirst;
}
try {
// Compute per-request headers
Map<String, dynamic> newValuesToAddToHeader = {};
if (headers.isNotEmpty) {
for (var header in headers.entries) {
if (!_api.options.headers.containsKey(header.key)) {
newValuesToAddToHeader.addAll({header.key: header.value});
}
}
}
if (await shouldRefreshToken()) {
await refreshToken(Dio());
}
if (bearerToken != null) {
newValuesToAddToHeader.addAll({"Authorization": "Bearer $bearerToken"});
} else {
if ((shouldSetAuthHeaders ?? this.shouldSetAuthHeaders) == true) {
newValuesToAddToHeader.addAll(await setAuthHeaders(headers));
}
}
// Create a per-request Dio instance to avoid shared mutable state
// across concurrent requests (prevents header/auth token leakage).
Dio requestDio = _createRequestDio(
additionalHeaders: newValuesToAddToHeader,
baseUrl: baseUrl,
connectTimeout: connectionTimeout,
receiveTimeout: receiveTimeout,
sendTimeout: sendTimeout,
);
// Handle cache-first policies
if (effectivePolicy.shouldTryCacheFirst && cacheKeyRequest != null) {
final cachedData = await _tryGetFromCache(cacheKeyRequest);
if (cachedData != null) {
printDebug('');
printDebug('╔╣ Cache hit: $cacheKeyRequest');
printDebug('╚╣ Policy: ${effectivePolicy.description}');
// For staleWhileRevalidate, trigger background refresh
if (effectivePolicy.shouldRevalidateInBackground) {
_revalidateInBackground(
request: request,
cacheKey: cacheKeyRequest,
cacheDuration: cacheDurationRequest,
requestDio: requestDio,
);
}
return _createResponseFromCache<T>(cachedData, handleSuccess);
} else if (effectivePolicy == CachePolicy.cacheOnly) {
throw DioException(
requestOptions: RequestOptions(path: ''),
type: DioExceptionType.unknown,
message: 'No cached data available for key: $cacheKeyRequest',
);
}
}
// Check connectivity before making request if enabled
bool shouldCheckConnectivity =
checkConnectivity ?? checkConnectivityBeforeRequest;
if (shouldCheckConnectivity && await NyConnectivity.isOffline()) {
// For networkFirst, try cache on offline
if (effectivePolicy.shouldFallbackToCache && cacheKeyRequest != null) {
final cachedData = await _tryGetFromCache(cacheKeyRequest);
if (cachedData != null) {
printDebug('Offline - using cached data for: $cacheKeyRequest');
return _createResponseFromCache<T>(cachedData, handleSuccess);
}
}
throw DioException(
requestOptions: RequestOptions(path: ''),
type: DioExceptionType.connectionError,
message: 'No network connection',
);
}
Response? response;
// Make the network request using the per-request Dio
stopwatch.start();
requestDio.options.extra['timestamp'] =
DateTime.now().microsecondsSinceEpoch;
response = await request(requestDio);
stopwatch.stop();
requestTime = "${stopwatch.elapsedMilliseconds}ms";
// Cache the response if caching is enabled
if (cacheKeyRequest != null && effectivePolicy.shouldTryNetwork) {
await _saveToCache(cacheKeyRequest, response!, cacheDurationRequest);
printDebug('Cached response: $cacheKeyRequest');
}
NyResponse<T> apiResponse = handleResponse<T>(
response!,
handleSuccess: handleSuccess,
);
_recordApiResponse(apiResponse, requestTime);
if (apiResponse.data == null && useUndefinedResponse) {
onUndefinedResponse(apiResponse.data, response);
}
if (_onSuccessEvent != null) {
_onSuccessEvent!(response, apiResponse.data);
}
return apiResponse;
} on DioException catch (dioException) {
NyResponse response = NyResponse(
response: dioException.response ?? null,
data: null,
rawData: dioException.response?.data,
);
stopwatch.stop();
requestTime = "${stopwatch.elapsedMilliseconds}ms";
_recordApiResponse(response, requestTime);
int nyRetries = retry ?? this.retry;
Duration nyRetryDelay = retryDelay ?? this.retryDelay;
bool Function(DioException dioException)? retryIfFinal = this.retryIf;
if (retryIf != null) {
retryIfFinal = retryIf;
}
if (retryIfFinal != null) {
shouldRetry = retryIfFinal(dioException);
}
if (shouldRetry == true && nyRetries > 0) {
for (var i = 0; i < nyRetries; i++) {
await Future.delayed(nyRetryDelay);
NyLogger.debug("[${i + 1}] Retrying request...");
dynamic response = await networkResponse(
request: request,
handleSuccess: handleSuccess,
handleFailure: handleFailure,
bearerToken: bearerToken,
baseUrl: baseUrl,
useUndefinedResponse: useUndefinedResponse,
shouldSetAuthHeaders: shouldSetAuthHeaders,
connectionTimeout: connectionTimeout,
receiveTimeout: receiveTimeout,
sendTimeout: sendTimeout,
headers: headers,
shouldRetry: false,
);
if (response != null) {
return response;
}
}
}
NyLogger.error(dioException.toString());
error(dioException);
if (handleFailure != null) {
NyResponse<T> errorResponse = _createErrorResponse<T>(dioException);
return handleFailure(errorResponse);
}
if (_onErrorEvent != null) {
_onErrorEvent!(dioException);
}
// Create an error response from the DioException
return _createErrorResponse<T>(dioException);
} on Exception catch (e) {
NyLogger.error(e.toString());
return _createGenericErrorResponse<T>(e);
} finally {
_api.options.queryParameters = {};
_cacheDuration = null;
_cacheKey = null;
}
}