Skip to content

QueuedInterceptors strange behavior #2462

@bustazone

Description

@bustazone

Package

dio

Version

5.9.0

Operating-System

iOS

Adapter

Default Dio

Output of flutter doctor -v

[✓] Flutter (Channel stable, 3.35.0, on macOS 14.7.1 23H222 darwin-arm64, locale es-ES) [3,4s]
    • Flutter version 3.35.0 on channel stable at /Users/javierbustamante/fvm/versions/3.35.0
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b896255557 (3 months ago), 2025-08-13 17:14:08 -0700
    • Engine revision 1e9a811bf8
    • Dart version 3.9.0
    • DevTools version 2.48.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
      enable-lldb-debugging

[!] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [12,9s]
    • Android SDK at /Users/javierbustamante/Library/Android/sdk
    • Emulator version 35.2.10.0 (build_id 12414864) (CL:N/A)
    • Platform android-36, build-tools 35.0.0
    • Java binary at: /Users/javierbustamante/Library/Java/JavaVirtualMachines/jbr-17.0.12/Contents/Home/bin/java
      This JDK is specified in your Flutter configuration.
      To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment JBR-17.0.12+1-1207.37-nomod (build 17.0.12+1-b1207.37)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[✓] Xcode - develop for iOS and macOS (Xcode 16.2) [13,9s]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16C5032a
    • CocoaPods version 1.16.2

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

[✓] Android Studio (version 2024.2) [9ms]
    • Android Studio at /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 21.0.3+-79915917-b509.11)

[✓] VS Code (version 1.104.3) [7ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (3 available) [13,1s]
    • iPhone 16 Pro (mobile) • A4547058-A9C0-4194-816F-8EAC302CD150 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-18-2 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 14.7.1 23H222 darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 142.0.7444.162
    ! Error: Browsing on the local area network for iPhone de Busta. Ensure the device is unlocked and attached with a cable or associated with
      the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)

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

! Doctor found issues in 1 category.

Dart Version

3.9.0

Steps to Reproduce

I've found a problem with the performance of the QueuedInterceptors.
I have a really complex interceptors distribution so it's easier to describe the problem with an example.
I'd like to clarify that maybe it's no a bug but a lack of clear documentation.

Let's see the case:

To start, I have 4 interceptors (I1, I2, I3, I4) and I passed it to dio with:
instance.interceptors.addAll([I1, I2, I3, I4]);
Each interceptor is a queued inteceptor (beaceause the order of execution is very important) and has different functionality in onResponse and onError.
So the Idea is to chain the Interceptors with the following order:

OutGoing: I1(onRequest) -> I2(onRequest) -> I3(onRequest) -> I4(onRequest)
Incoming: I4(onResponse|onError) -> I3(onResponse|onError) -> I2(onResponse|onError) -> I1(onResponse|onError)

The goal is that each interceptor get the state of the previous one, i.e.: if the onResponse function of I3 returns a handler.reject it has to be followed by the execution of the I2 onError function.

Here comes the problems:

  • Actually the outgoing order is good I1(onRequest) -> I2(onRequest) -> I3(onRequest) -> I4(onRequest) but when the call incomes from network I see the same execution order (I1(onRequest) -> I2(onRequest) -> I3(onRequest) -> I4(onRequest)).
  • The other problem is that when I execute a handler.reject the call returns an exception directly instead of execute the onError of the next Interceptor.

In order to clarify, that the dummy code for the test:

Test interceptor:

import 'package:dio/dio.dart';

class DioNetTestErrorInterceptor extends QueuedInterceptor {
  final String id;
  DioNetTestErrorInterceptor(this.id);

  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    print('[DioNetTestErrorInterceptor-$id] onRequest');
    return handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) async {
    print('[DioNetTestErrorInterceptor-$id] onResponse');

    if (id == '4') {
      final dioError = DioException(
        requestOptions: response.requestOptions,
        error: 'Fake error',
        type: DioExceptionType.badResponse,
        response: Response(
          requestOptions: response.requestOptions,
          statusCode: 500,
          data: {"code": 900, "message": "Fake error"},
        ),
      );
      return handler.reject(dioError);
    } else {
      return handler.next(response);
    }
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('[DioNetTestErrorInterceptor-$id] onError');
    return handler.next(err);
  }
}

Dio instance creation:

Dio newInstance = Dio(options);
newInstance.interceptors.addAll([
	DioNetTestErrorInterceptor(1),
	DioNetTestErrorInterceptor(2),
	DioNetTestErrorInterceptor(3),
	DioNetTestErrorInterceptor(4),
])

Expected Result

Start Call
[DioNetTestErrorInterceptor-1] onRequest
[DioNetTestErrorInterceptor-2] onRequest
[DioNetTestErrorInterceptor-3] onRequest
[DioNetTestErrorInterceptor-4] onRequest
[DioNetTestErrorInterceptor-4] onResponse
[DioNetTestErrorInterceptor-3] onError
[DioNetTestErrorInterceptor-2] onError
[DioNetTestErrorInterceptor-1] onError
End Call

Actual Result

Start Call
[DioNetTestErrorInterceptor-1] onRequest
[DioNetTestErrorInterceptor-2] onRequest
[DioNetTestErrorInterceptor-3] onRequest
[DioNetTestErrorInterceptor-4] onRequest
[DioNetTestErrorInterceptor-1] onResponse
End Call

Metadata

Metadata

Assignees

No one assigned

    Labels

    e: documentationImprovements or additions to documentationh: need triageThis issue needs to be categorizedp: dioTargeting `dio` packages: best practiseIt's the best way to do something so far

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions