dio icon indicating copy to clipboard operation
dio copied to clipboard

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

Open yehorh opened this issue 2 years ago • 15 comments

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 avatar Mar 13 '24 21:03 yehorh

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.

yehorh avatar Mar 14 '24 08:03 yehorh

the same problem is happening with our project too.

khamidjon avatar May 30 '24 11:05 khamidjon

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));
  }
}

sdgroot avatar Jun 07 '24 14:06 sdgroot

@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

romandrahan avatar Jun 13 '24 09:06 romandrahan

@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!

sdgroot avatar Jun 13 '24 19:06 sdgroot

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

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

Slexom avatar Jun 24 '24 14:06 Slexom

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?

romandrahan avatar Jun 24 '24 16:06 romandrahan

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.

Slexom avatar Jun 28 '24 22:06 Slexom

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

AlexV525 avatar Jun 29 '24 01:06 AlexV525

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.

AlexV525 avatar Jun 30 '24 04:06 AlexV525