# FAQ

## \*.chopper.dart not found ?

If you have this error, you probably forgot to run code generation. To do that, run the following command in your shell.

`dart run build_runner build`

It will generate the code that actually does the HTTP request (YOUR\_FILE.chopper.dart). If you wish to update the code automatically when you change your definition, run the `watch` command.

`dart run build_runner watch`

## How to set a connection timeout?

For Dart VM and Flutter mobile/desktop apps, provide an `IOClient` configured with a `dart:io` `HttpClient`. This controls the socket connection timeout, not the full request deadline.

```dart
import 'package:http/io_client.dart' as http;
import 'dart:io';

final chopper = ChopperClient(
  client: http.IOClient(
    HttpClient()..connectionTimeout = const Duration(seconds: 60),
  ),
);
```

Use Chopper's per-request timeout support when you need to abort a slow request after a deadline.

## How to set a timeout?

Chopper supports **two per‑request ways** to stop a slow request:

1. **Hard timeout (auto‑abort)** — add a `timeout:` to your endpoint annotation. The generated code creates an internal abort trigger and cancels the underlying HTTP request when the deadline expires. The error you get is a [`TimeoutException`](https://api.dart.dev/dart-async/TimeoutException-class.html).

```dart
import 'dart:async' show TimeoutException;

@GET(path: '/slow', timeout: Duration(seconds: 10))
Future<Response<MyModel>> getSlowThing();

// Usage
try {
  final res = await api.getSlowThing();
  // ...
} on TimeoutException catch (e) {
  // timed out and the request was actually aborted
}
```

2. **Manual cancellation — `@AbortTrigger`**

Accept an `@AbortTrigger` parameter and pass a `Future<void>` (usually from a [`Completer<void>`](https://api.dart.dev/dart-async/Completer/Completer.html)). When that future completes, Chopper aborts the underlying HTTP request (using `http`'s abortable support) and a [`RequestAbortedException`](https://pub.dev/documentation/http/latest/http/RequestAbortedException-class.html) is thrown — mirroring the HTTP package example.

```dart
// Service definition
@GET(path: '/download')
Future<Response<List<int>>> download({
  @Query('id') required String id,
  @AbortTrigger() Future<void>? abortTrigger,
});
```

**Usage:**

```dart
import 'dart:async' show Completer;
import 'package:http/http.dart' as http show RequestAbortedException;

final abortTrigger = Completer<void>();

// Keep a handle to the pending request future.
final download = api.download(id: '42', abortTrigger: abortTrigger.future);

// Call this from your cancellation event, e.g. a cancel button.
void cancelDownload() {
  if (!abortTrigger.isCompleted) {
    abortTrigger.complete();
  }
}

try {
  final Response<List<int>> response = await download;
  // consume response
} on http.RequestAbortedException {
  // request aborted before completion
  print('Request was aborted by user');
  rethrow;
}
```

### Rules & behavior

* **Don’t combine** a method `timeout:` **and** an `@AbortTrigger` param on the same endpoint; generation will fail (pick one mechanism).
* Timeouts **abort the HTTP request**, they are *not* just `Future.timeout(...)` wrappers. This uses the new abortable requests in `http` and stops uploads/streams mid‑flight.
* Manual aborts surface as [`RequestAbortedException`](https://pub.dev/documentation/http/latest/http/RequestAbortedException-class.html); timeouts surface as [`TimeoutException`](https://api.dart.dev/dart-async/TimeoutException-class.html) with a clear message.
* Chopper 8.5+ depends on `http` versions with abortable request support and requires Dart SDK 3.9 or newer.

## Add query parameter to all requests

Possible using an interceptor.

```dart
final chopper = ChopperClient(interceptors: [QueryInterceptor()]);

class QueryInterceptor implements Interceptor {
  @override
  FutureOr<Response<BodyType>> intercept<BodyType>(
    Chain<BodyType> chain,
  ) async {
    final request = _addQuery(chain.request);
    return chain.proceed(request);
  }

  Request _addQuery(Request req) {
    final params = Map<String, dynamic>.from(req.parameters);
    params['key'] = '123';

    return req.copyWith(parameters: params);
  }
}
```

## GZip converter example

You can use converters for modifying requests and responses. For example, to use GZip for post request you can write something like this:

```dart
import 'dart:convert' show jsonEncode, utf8;
import 'dart:io' show gzip;

Request compressRequest(Request request) {
  final body = utf8.encode(jsonEncode(request.body));
  final compressedBody = gzip.encode(body);

  return applyHeaders(request, {
    'content-type': 'application/json',
    'content-encoding': 'gzip',
  }).copyWith(body: compressedBody);
}

// ...

@FactoryConverter(request: compressRequest)
@POST()
Future<Response> postRequest(@Body() Map<String, String> data);
```

## Runtime baseUrl change

You may need to change the base URL of your network calls during runtime, for example, if you have to use different servers or routes dynamically in your app in case of a "regular" or a "paid" user. You can store the current server base URL in your SharedPreferences (encrypt/decrypt it if needed) and use it in an interceptor like this:

```dart
import 'package:shared_preferences/shared_preferences.dart';

Future<ChopperClient> createClient() async {
  final preferences = await SharedPreferences.getInstance();

  return ChopperClient(interceptors: [BaseUrlInterceptor(preferences)]);
}

class BaseUrlInterceptor implements Interceptor {
  BaseUrlInterceptor(this._preferences);

  final SharedPreferences _preferences;

  @override
  FutureOr<Response<BodyType>> intercept<BodyType>(
    Chain<BodyType> chain,
  ) async {
    final String? baseUrl = _preferences.getString('baseUrl');
    final request = baseUrl != null
        ? chain.request.copyWith(baseUri: Uri.parse(baseUrl))
        : chain.request;

    return chain.proceed(request);
  }
}
```

## Mock ChopperClient for testing

Chopper is built on top of `http` package.

So, one can just use the mocking API of the HTTP package. See the documentation for the [http.testing library](https://pub.dev/documentation/http/latest/http.testing/http.testing-library.html) and for the [MockClient class](https://pub.dev/documentation/http/latest/http.testing/MockClient-class.html).

Also, you can follow this code by [ozburo](https://github.com/ozburo):

```dart
import 'dart:convert';

import 'package:chopper/chopper.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';

part 'api_service.chopper.dart';

@ChopperApi()
abstract class ApiService extends ChopperService {
  static ApiService create() {
    final client = ChopperClient(
      client: MockClient((request) async {
        final results =
            mockData[request.url.path] ?? const <Map<String, dynamic>>[];
        Map<String, dynamic>? result;
        for (final candidate in results) {
          if (candidate['id'] == request.url.queryParameters['id']) {
            result = candidate;
            break;
          }
        }

        if (result == null) {
          return http.Response(json.encode({'error': 'not found'}), 404);
        }
        return http.Response(json.encode(result), 200);
      }),
      baseUrl: Uri.parse('https://mysite.com/api'),
      services: [_$ApiService()],
      converter: JsonConverter(),
      errorConverter: JsonConverter(),
    );
    return _$ApiService(client);
  }

  @GET(path: "/get")
  Future<Response> get(@Query() String url);
}
```

## Use HTTPS certificate

Chopper is built on top of `http` package and you can override the inner http client. Only bypass certificate validation for local development or controlled testing.

```dart
import 'dart:io';
import 'package:http/io_client.dart' as http;

final chopper = ChopperClient(
  client: http.IOClient(
    HttpClient()
      ..findProxy = ((url) => 'PROXY 192.168.0.102:9090')
      ..badCertificateCallback = (_, _, _) => true,
  ),
);
```

## Authorized HTTP requests

Basically, the algorithm goes like this (credits to [stewemetal](https://github.com/stewemetal)):

Add the authentication token to the request (by "Authorization" header, for example) -> try the request -> if it fails use the refresh token to get a new auth token -> if that succeeds, save the auth token and retry the original request with it if the refresh token is not valid anymore, drop the session (and navigate to the login screen, for example)

Simple code example:

```dart
class AuthInterceptor implements Interceptor {
  @override
  FutureOr<Response<BodyType>> intercept<BodyType>(
    Chain<BodyType> chain,
  ) async {
    final token = SharedPrefs.localStorage.getString(tokenHeader);
    final request = token == null
        ? chain.request
        : applyHeader(chain.request, 'authorization', token, override: false);

    final response = await chain.proceed(request);

    if (response.statusCode == 401) {
      SharedPrefs.localStorage.remove(tokenHeader);
      // Navigate to some login page or just request new token
    }

    return response;
  }
}

// ...
interceptors: [
  AuthInterceptor(),
  // ... other interceptors
]
//...
```

The actual implementation of the algorithm above may vary based on how the backend API - more precisely the login and session handling - of your app looks like. Breaking out of the authentication flow/interceptor can be achieved in multiple ways. For example, you can throw an exception or use a service that handles navigation. See [interceptors](/chopper/interceptors.md) for more info.

### Authorized HTTP requests using the special Authenticator interceptor

Similar to OkHTTP's [authenticator](https://github.com/square/okhttp/blob/480c20e46bb1745e280e42607bbcc73b2c953d97/okhttp/src/main/kotlin/okhttp3/Authenticator.kt), the idea here is to provide a reactive authentication in the event that an auth challenge is raised. It returns a nullable Request that contains a possible update to the original Request to satisfy the authentication challenge.

```dart
import 'dart:async' show FutureOr;
import 'dart:io' show HttpHeaders, HttpStatus;

import 'package:chopper/chopper.dart';

/// This method returns a [Request] that includes credentials to satisfy an authentication challenge received in
/// [response]. It returns `null` if the challenge cannot be satisfied.
class MyAuthenticator extends Authenticator {
  @override
  FutureOr<Request?> authenticate(
    Request request,
    Response response, [
    Request? originalRequest,
  ]) async {
    if (response.statusCode == HttpStatus.unauthorized) {
      final String? newToken = await refreshToken();

      if (newToken != null) {
        return request.copyWith(
          headers: {
            ...request.headers,
            HttpHeaders.authorizationHeader: newToken,
          },
        );
      }
    }

    return null;
  }

  Future<String?> refreshToken() async {
    /// Refresh the accessToken using refreshToken however needed.
    /// This could be done either via an HTTP client, or a ChopperService, or a
    /// repository could be a dependency.
    /// This approach is intentionally not opinionated about how this works.
    throw UnimplementedError();
  }
}

/// When initializing your ChopperClient
final client = ChopperClient(
  /// register your Authenticator here
  authenticator: MyAuthenticator(),
);
```

## Decoding JSON using Isolates

Sometimes you want to decode JSON outside the main thread in order to reduce janking. In this example we're going to go even further and implement a Worker Pool using [Squadron](https://pub.dev/packages/squadron/install) which can dynamically spawn a maximum number of Workers as they become needed.

#### Install the dependencies

* [squadron](https://pub.dev/packages/squadron)
* [squadron\_builder](https://pub.dev/packages/squadron_builder)
* [json\_annotation](https://pub.dev/packages/json_annotation)
* [json\_serializable](https://pub.dev/packages/json_serializable)

#### Write a JSON decode service

We'll leverage [squadron\_builder](https://pub.dev/packages/squadron_builder) and the power of code generation.

```dart
import 'dart:async';
import 'dart:convert' show json;

import 'package:squadron/squadron.dart';
import 'package:squadron/squadron_annotations.dart';

import 'json_decode_service.activator.g.dart';

part 'json_decode_service.worker.g.dart';

@SquadronService()
class JsonDecodeService extends WorkerService
    with $JsonDecodeServiceOperations {
  @SquadronMethod()
  Future<dynamic> jsonDecode(String source) async => json.decode(source);
}
```

Extracted from the [full example here](https://github.com/lejard-h/chopper/blob/master/example/lib/json_decode_service.dart).

#### Write a custom JsonConverter

Using [json\_serializable](https://pub.dev/packages/json_serializable) we'll create a [JsonConverter](https://github.com/lejard-h/chopper/blob/master/chopper/lib/src/converters.dart) which works with or without a [WorkerPool](https://github.com/d-markey/squadron#features).

When `JsonConverter` decodes directly into typed JSON collections, such as `Response<List<double>>` or `Response<Map<String, double>>`, Chopper can report the list index or map key that failed conversion. Model fields are decoded by your serializer factory, so enable `checked: true` for `json_serializable` to get `CheckedFromJsonException` errors with field context from generated `fromJson` methods.

```dart
import 'dart:async' show FutureOr;
import 'dart:convert' show jsonDecode;

import 'package:chopper/chopper.dart';
import 'package:chopper_example/json_decode_service.dart';
import 'package:chopper_example/json_serializable.dart';

typedef JsonFactory<T> = T Function(Map<String, dynamic> json);

class JsonSerializableWorkerPoolConverter extends JsonConverter {
  const JsonSerializableWorkerPoolConverter(this.factories, [this.workerPool]);

  final Map<Type, JsonFactory> factories;

  /// Make the WorkerPool optional so that the JsonConverter still works without it
  final JsonDecodeServiceWorkerPool? workerPool;

  /// By overriding tryDecodeJson we give our JsonConverter
  /// the ability to decode JSON in an Isolate.
  @override
  FutureOr<dynamic> tryDecodeJson(String data) async {
    try {
      return workerPool != null
          ? await workerPool!.jsonDecode(data)
          : jsonDecode(data);
    } catch (error) {
      print(error);

      chopperLogger.warning(error);

      return data;
    }
  }

  T? _decodeMap<T>(Map<String, dynamic> values) {
    final jsonFactory = factories[T];
    if (jsonFactory == null || jsonFactory is! JsonFactory<T>) {
      return null;
    }

    return jsonFactory(values);
  }

  List<T> _decodeList<T>(Iterable values) =>
      values.where((v) => v != null).map<T>((v) => _decode<T>(v)).toList();

  dynamic _decode<T>(entity) {
    if (entity is Iterable) return _decodeList<T>(entity as List);

    if (entity is Map) return _decodeMap<T>(entity as Map<String, dynamic>);

    return entity;
  }

  @override
  FutureOr<Response<ResultType>> convertResponse<ResultType, Item>(
    Response response,
  ) async {
    final jsonRes = await super.convertResponse(response);

    return jsonRes.copyWith<ResultType>(body: _decode<Item>(jsonRes.body));
  }

  @override
  FutureOr<Response> convertError<ResultType, Item>(Response response) async {
    final jsonRes = await super.convertError(response);

    return jsonRes.copyWith<ResourceError>(
      body: ResourceError.fromJsonFactory(jsonRes.body),
    );
  }
}
```

Extracted from the [full example here](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable_squadron_worker_pool.dart).

#### Code generation

It goes without saying that running the code generation is a pre-requisite at this stage

```bash
dart run build_runner build
```

**Changing the default extension of the generated files**

If you want to change the default extension of the generated files from `.chopper.dart` to something else, you can do so by adding the following to your `build.yaml` file:

```yaml
targets:
  $default:
    builders:
      chopper_generator:
        options:
          # This assumes you want the files to end with `.chopper.g.dart`
          # instead of the default `.chopper.dart`.
          build_extensions: { ".dart": [ ".chopper.g.dart" ] }
```

#### Configure a WorkerPool and run the example

```dart
/// inspired by https://github.com/d-markey/squadron_sample/blob/main/lib/main.dart
void initSquadron(String id) {
  Squadron.setId(id);
  Squadron.setLogger(ConsoleSquadronLogger());
  Squadron.logLevel = SquadronLogLevel.all;
  Squadron.debugMode = true;
}

Future<void> main() async {
  /// initialize Squadron before using it
  initSquadron('worker_pool_example');

  final jsonDecodeServiceWorkerPool = JsonDecodeServiceWorkerPool(
    // Set whatever you want here
    concurrencySettings: ConcurrencySettings.oneCpuThread,
  );

  /// start the Worker Pool
  await jsonDecodeServiceWorkerPool.start();

  /// Instantiate the JsonConverter from above
  final converter = JsonSerializableWorkerPoolConverter(
    {Resource: Resource.fromJsonFactory},

    /// make sure to provide the WorkerPool to the JsonConverter
    jsonDecodeServiceWorkerPool,
  );

  /// Instantiate a ChopperClient
  final chopper = ChopperClient(
    client: client,
    baseUrl: Uri.parse('http://localhost:8000'),
    // bind your object factories here
    converter: converter,
    errorConverter: converter,
    services: [
      // the generated service
      MyService.create(),
    ],
    /* Interceptor */
    interceptors: [authHeader],
  );

  /// Do stuff with myService
  final myService = chopper.getService<MyService>();

  /// ...stuff...

  /// stop the Worker Pool once done
  jsonDecodeServiceWorkerPool.stop();
}
```

[The full example can be found here](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable_squadron_worker_pool.dart).

#### Further reading

This barely scratches the surface. If you want to know more about [squadron](https://github.com/d-markey/squadron) and [squadron\_builder](https://github.com/d-markey/squadron_builder) make sure to head over to their respective repositories.

[David Markey](https://github.com/d-markey), the author of squadron, was kind enough as to provide us with an [excellent Flutter example](https://github.com/d-markey/squadron_builder) using both packages.

## How to use Chopper with [Injectable](https://pub.dev/packages/injectable)

### Create a module for your ChopperClient

Define a module for your ChopperClient. You can use the `@lazySingleton` (or other type if preferred) annotation to make sure that only one is created.

```dart
@module
abstract class ChopperModule {
  @lazySingleton
  ChopperClient get chopperClient => ChopperClient(
    baseUrl: Uri.parse('https://base-url.com'),
    converter: JsonConverter(),
  );
}
```

### Create ChopperService with Injectable

Define your ChopperService as usual. Annotate the class with `@lazySingleton` (or other type if preferred) and use the `@factoryMethod` annotation to specify the factory method for the service. This would normally be the static create method.

```dart
@lazySingleton
@ChopperApi(baseUrl: '/todos')
abstract class TodosListService extends ChopperService {
  @factoryMethod
  static TodosListService create(ChopperClient client) =>
      _$TodosListService(client);

  @GET()
  Future<Response<List<Todo>>> getTodos();
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hadrien-lejard.gitbook.io/chopper/faq.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
