retrofit.dart

repository·master·Indexed 22 days ago

https://github.com/trevorwang/retrofit.dart

A type-safe API client generator for Dart inspired by Square's Retrofit. It utilizes Dio for networking and source_gen to automate boilerplate code for RESTful API calls. Supports standard HTTP methods, multipart uploads, streaming responses, and integration with json_serializable or dart_mappable for type conversion.

Tokens
6.3K
Snippets
26
Records
27
Agent score
79%

What's inside retrofit.dart

  1. How Lean Builder support works (Experimental)

    master

    Lean Builder is a streamlined Dart build system designed for fast incremental builds, parallel processing, and watch mode with hot reload.

    While the infrastructure for lean_builder has been added to retrofit_generator, it is currently experimental. Full support is pending a stable release of lean_builder and further adaptation of the retrofit_generator codebase.

    When fully implemented, you will be able to replace build_runner with lean_builder commands without changing your @RestApi annotations or API definitions.

  2. Configure base URL priority

    master

    When setting up your RestClient, the priority for the baseUrl is as follows:

    1. Highest: The baseUrl passed to the RestClient constructor (e.g., RestClient(dio, baseUrl: '...')). This overrides everything else.
    2. Medium: The baseUrl defined in the @RestApi annotation. This is ignored if a baseUrl is passed to the constructor.
    3. Lowest: The dio.options.baseUrl. If you want to use this value, do not pass a baseUrl to the @RestApi annotation or the RestClient constructor.

    If you use a relative baseUrl in @RestApi, you must specify a baseUrl in dio.options.baseUrl.

    // Using relative baseUrl from @RestApi
    @RestApi(baseUrl: '/tasks')
    abstract class RestClient {
      factory RestClient(Dio dio, {String? baseUrl}) = _RestClient;
      @GET('{id}')
      Future<HttpResponse<Task>> getTask(@Path('id') String id);
    }
    
    dio.options.baseUrl = 'https://api.example.com/api/v1';
    final client = RestClient(dio);
  3. Experimental lean_builder support

    master

    Retrofit has experimental support for lean_builder, a faster build system. This is an optional dependency and is not required. To use it, add lean_builder to your dev_dependencies. Note that full integration is still under development and you should continue using build_runner for now.

    dev_dependencies:
      lean_builder: ^0.1.2  # Optional - only if you want to use lean_builder
  4. Customize iOS Launch Screen Assets

    master

    To change the launch screen image for your iOS application, you can either replace the image files directly in the flutter_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory or use Xcode for a visual approach.

    Using Xcode (Recommended for visual management):

    1. Open your Flutter project's iOS workspace using the command: open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Drag and drop your desired images into the asset catalog to replace the existing launch images.
    open ios/Runner.xcworkspace
  5. Enable multithreading for model parsing (Flutter only)

    master

    To parse models on a separate thread (Isolate) in Flutter, use the Parser.FlutterCompute parser in your @RestApi annotation.

    For each model, you must define two top-level functions:

    1. FutureOr<T> deserialize<T>(Map<String, dynamic> json)
    2. FutureOr<dynamic> serialize<T>(T object)

    If handling lists, you must also provide list counterparts (e.g., deserializeTaskList).

    Warning: Avoid using Map values as return types (e.g., Future<Map<String, Task>>) to prevent spawning excessive background isolates, which is extremely resource-intensive. Instead, wrap the map in a dedicated class.

    @RestApi(
      baseUrl: 'https://api.example.com/',
      parser: Parser.FlutterCompute,
    )
    abstract class RestClient {
      factory RestClient(Dio dio, {String? baseUrl}) = _RestClient;
      @GET('/task')
      Future<Task> getTask();
    }
    
    Task deserializeTask(Map<String, dynamic> json) => Task.fromJson(json);
    Map<String, dynamic> serializeTask(Task object) => object.toJson();
  6. Handle errors using catchError or CallAdapters

    master

    Basic Error Handling

    You can catch errors using standard Dart catchError. For DioException, you can access the failed response via (obj as DioException).response to retrieve status codes and messages.

    CallAdapters for custom error handling

    To handle errors globally or per-method by transforming the return type (e.g., Future<T> to Future<Result<T>>), implement the CallAdapter<R, T> interface.

    • Global: Apply via @RestApi(callAdapter: MyAdapter).
    • Per-method: Apply via @UseCallAdapter(MyAdapter) on a specific method.
    // Basic catchError
    client.getTask('2').catchError((obj) {
      if (obj is DioException) {
        final res = obj.response;
        print('Error: ${res.statusCode}');
      }
    });
    
    // CallAdapter example
    class MyCallAdapter<T> extends CallAdapter<Future<T>, Future<Result<T>>> {
      @override
      Future<Result<T>> adapt(Future<T> Function() call) async {
        try {
          final response = await call();
          return Result<T>.ok(response);
        } catch (e) {
          return Result.err(e.toString());
        }
      }
    }
    
    @RestApi(callAdapter: MyCallAdapter)
    abstract class RestClient {
      factory RestClient(Dio dio) = _RestClient;
      @GET('/')
      Future<Result<User>> getUser();
    }
  7. Handle streaming responses and SSE

    master

    Retrofit supports streaming responses using the @DioResponseType(ResponseType.stream) annotation. The return type must be either Stream<Uint8List> (for binary data) or Stream<String> (for text data).

    For Server-Sent Events (SSE), it is recommended to use the simple_sse package to parse the stream.

    @RestApi(baseUrl: 'https://api.example.com')
    abstract class RestClient {
      factory RestClient(Dio dio, {String? baseUrl}) = _RestClient;
    
      @GET('/download/file')
      @DioResponseType(ResponseType.stream)
      Stream<Uint8List> downloadFile();
    
      @GET('/events')
      @DioResponseType(ResponseType.stream)
      Stream<String> getServerSentEvents();
    }
    
    // SSE Usage
    final eventStream = client
        .getServerSentEvents()
        .transform(const LineSplitter())
        .transform(const SseEventTransformer());
  8. Run the code generator

    master

    After defining your API, run the build_runner command to generate the implementation files. Use build for a single run or watch during development to automatically regenerate files when they change.

    # dart
    dart pub run build_runner build
    
    # for watch mode (recommended during development)
    dart pub run build_runner watch
  9. Upload files with runtime metadata using @PartMap()

    master

    To provide runtime metadata like contentType and fileName for multipart uploads, use the @PartMap() annotation. It accepts a Map<String, dynamic> where keys follow the pattern:

    • '<partName>_contentType'
    • '<partName>_fileName'

    Behavior:

    1. Runtime values in @PartMap() override static values in @Part().
    2. If @PartMap() is empty, it uses the static value from @Part().
    3. If neither is provided, fileName defaults to the file's actual name, and contentType defaults to null (Dio auto-detects via extension).
      @POST('/api/files')
      @MultiPart()
      Future<void> uploadFile({
        @Part(name: 'file') required File file,
        @PartMap() Map<String, dynamic>? metadata,
      });
      
      // Usage
      await client.uploadFile(
        file: File('/path/to/image.jpg'),
        metadata: {
          'file_contentType': 'image/jpeg',
          'file_fileName': 'photo.jpg',
        },
      );
  10. Use build_runner for code generation (Recommended)

    master

    Since Lean Builder support is currently experimental and under development, you should use build_runner for all code generation tasks in retrofit.dart projects.

    To perform a one-time build:

    dart pub run build_runner build

    To run in watch mode (recommended during development) to automatically regenerate code when files change:

    dart pub run build_runner watch --delete-conflicting-outputs
  11. Integrate dart_mappable with Retrofit

    master

    To use dart_mappable for type conversion within your Retrofit API clients, you must include both retrofit and dart_mappable in your dependencies, along with their respective generators in dev_dependencies. This allows Retrofit to automatically use dart_mappable's logic to parse JSON responses into your model classes.

    dependencies:
      retrofit: ^4.9.0
      dio: ^5.7.0
      dart_mappable: ^4.2.0
    
    dev_dependencies:
      retrofit_generator: ^10.0.1
      build_runner: ^2.4.0
      dart_mappable_builder: ^4.2.0