cfug / dio

A powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc.
https://dio.pub
MIT License
12.5k stars 1.51k forks source link

A request with an interceptor, which produces an error, makes the code stuck. #2138

Open yehorh opened 7 months ago

yehorh commented 7 months ago

Package

dio

Version

5.4.1

Operating-System

Android

Output of flutter doctor -v

flutter doctor -v
[✓] Flutter (Channel stable, 3.19.3, on macOS 14.4 23E214 darwin-arm64, locale en-UA)
    • Flutter version 3.19.3 on channel stable at /Users/yehorh/opt/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ba39319843 (6 days ago), 2024-03-07 15:22:21 -0600
    • Engine revision 2e4ba9c6fb
    • Dart version 3.3.1
    • DevTools version 2.31.1

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0-rc1)
    • Android SDK at /Users/yehorh/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0-rc1
    • Java binary at: /Users/yehorh/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15E204a
    • CocoaPods version 1.14.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.2)
    • Android Studio at /Users/yehorh/Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)

[✓] IntelliJ IDEA Ultimate Edition (version 2023.3.5)
    • IntelliJ at /Users/yehorh/Applications/IntelliJ IDEA Ultimate.app
    • Flutter plugin version 78.3.1
    • Dart plugin version 233.14888

[✓] VS Code (version 1.87.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.84.0

[✓] Connected device (4 available)            
    • sdk gphone64 arm64 (mobile) • emulator-5554             • android-arm64  • Android 13 (API 33) (emulator)
    • yiPhone (mobile)            • 00008101-000E28880AD2001E • ios            • iOS 17.4 21E219
    • macOS (desktop)             • macos                     • darwin-arm64   • macOS 14.4 23E214 darwin-arm64
    • Chrome (web)                • chrome                    • web-javascript • Google Chrome 122.0.6261.128

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Dart Version

3.3.1

Steps to Reproduce

Here is a minimal example; I can't understand why the error isn't caught in any way, and everything that follows the request never executes.

tried on Android and macOS

import 'package:dio/dio.dart';

Future<void> main() async {
  final dio = Dio();

  dio.interceptors.add(InterceptorWithError());

  try {
    final response = await dio.get('https://google.com');
  } catch (e, st) {
    print('Unreachable code');
  }

  print('Unreachable code too');
}

class InterceptorWithError extends Interceptor {
  @override
  Future<void> onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    throw Exception('some error');
    return handler.next(options);
  }

  @override
  Future<void> onResponse(
    Response response,
    ResponseInterceptorHandler handler,
  ) async {
    handler.next(response);
  }

  @override
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    handler.next(err);
  }
}

Expected Result

Errors from broken requests must be caught.

Actual Result

The program stops when executing a request, and the subsequent code becomes unreachable.

yehorh commented 7 months ago

It seems I have broken the interface contract. I have overridden synchronous functions with asynchronous functions.

The override_on_non_overriding_member lint hasn't helped me this time.

khamidjon commented 5 months ago

the same problem is happening with our project too.

sdgroot commented 4 months ago

We're currently experiencing the same issue using the following interactor to refresh an access token and retry the request, using Dio v5.4.3+1. The request never finishes executing and no exceptions are caught in the try/catch block.

try {
  final body = {...};
  final options = Options(extra: {'authenticated': true}, validateStatus: (status) => status == 200);
  final response = await _dio.post('...', data: body, options: options);
  // We never get here
} on DioException catch (error, trace) {
  // We never get here
}
class AuthInterceptor extends Interceptor {
  final Dio _dio;
  final Future<String?> Function() _getAccessToken;

  AuthInterceptor({
    required Dio dio,
    required Future<String?> Function() getAccessToken,
  })  : _dio = dio,
        _getAccessToken = getAccessToken;

  @override
  Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    // If the request is not authenticated, continue without adding the Authorization header
    if (options.extra['authenticated'] == false) {
      return handler.next(options);
    }

    // Try to retrieve a valid (non-expired) access token for the Authorization header.
    // If no token could be retrieved, we don't add the header.
    final String? accessToken = await _getAccessToken();
    if (accessToken != null) {
      options.headers['Authorization'] = 'Bearer $accessToken';
    }

    // Continue request
    return handler.next(options);
  }

  @override
  FutureOr<void> onError(DioException error, ErrorInterceptorHandler handler) async {
    final Response? response = error.response;

    // We're only interested in handling the 401 header here. Let other errors pass through.
    if (response == null || response.statusCode != 401) {
      return handler.next(error);
    }

    final RequestOptions options = response.requestOptions;

    // If the request was already retried we back off and let the error pass through.
    if (options.extra['retried'] == true) {
      return handler.next(error); // also tried handler.reject(error) with same result
    }

    // Add flag to indicate that we retried the request, to prevent infinite retries.
    options.extra['retried'] = true;

    // Retry the request. A refresh token will be added to the request in the [onRequest]
    // handler, when available.
    return handler.resolve(await _dio.fetch(options));
  }
}
romandrahan commented 4 months ago

@sdgroot

is this part from your code using the same _dio instance as the main app thread (final response = await _dio.post('...', data: body, options: options);?

// Retry the request. A refresh token will be added to the request in the [onRequest]
// handler, when available.
return handler.resolve(await _dio.fetch(options));

for token interceptors it's better to use QueuedInterceptor and separate Dio instance which do retries, see https://github.com/cfug/dio/blob/main/example/lib/queued_interceptor_crsftoken.dart

sdgroot commented 4 months ago

@sdgroot

is this part from your code using the same _dio instance as the main app thread (final response = await _dio.post('...', data: body, options: options);?

// Retry the request. A refresh token will be added to the request in the [onRequest]
// handler, when available.
return handler.resolve(await _dio.fetch(options));

for token interceptors it's better to use QueuedInterceptor and separate Dio instance which do retries, see https://github.com/cfug/dio/blob/main/example/lib/queued_interceptor_crsftoken.dart

Hi @romandrahan, yes, it was using the same Dio instance as the original request. I've now rewritten my interceptor code according to the example that you posted and that did seem to do the trick, so thank you!

sagar-acharya-vectorsolutions-ctr commented 4 months ago

Facing the same issue on my project, do we have any work around for this one.

Slexom commented 4 months ago

I solved a similar issue, related to interceptors, downgrading to version 5.4.0, was using 5.4.3+1.

romandrahan commented 4 months ago

I solved a similar issue, related to interceptors, downgrading to version 5.4.0, was using 5.4.3+1.

@Slexom what was your issue just wondering?

Slexom commented 4 months ago

I solved a similar issue, related to interceptors, downgrading to version 5.4.0, was using 5.4.3+1.

@Slexom what was your issue just wondering?

So, basically the interceptors wasn't intercepting at all in iOS 16 (simulator and physical device), built with XCode 15, while working perfectly fine in Android 14 (emulator), Android 13(physical device) and web (chrome). No errors at all from the application logs. Searching for solutions I've reached this issue. Reading the used version was 5.4.1, I've downgraded from 5.4.3+1 to 5.4.0 and this, luckily, solved any issue with iOS.

AlexV525 commented 4 months ago

Please submit reproducible example rather than saying "same issue". The original exception is about mismatched signatures which is not a wide-range behavior.

AlexV525 commented 4 months ago

The original reproducible example can be reproduced since v4 which literally means from the beginning, so anyone who can workaround by downgrading to any version is invalid.

muhammad67uzair commented 1 week ago

did anyone find any workaround to this other than downgrading the package since i know downgrading the package doesn't work as stated by @AlexV525?