SkillAgentSearch skills...

Retrofit.dart

retrofit.dart is an dio client generator using source_gen and inspired by Chopper and Retrofit.

Install / Use

/learn @trevorwang/Retrofit.dart
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Retrofit For Dart

retrofit retrofit_generator Pub Likes Testing Coverage Status

retrofit.dart is a type conversion dio client generator using source_gen and inspired by Chopper and Retrofit.

Usage

Generator

Add the generator to your dev dependencies

dependencies:
  retrofit: ^4.9.0
  logger: ^2.6.0  # for logging purpose
  json_annotation: ^4.9.0

dev_dependencies:
  retrofit_generator: ^10.0.1
  build_runner: ^2.6.0
  json_serializable: ^6.10.0

Define and Generate your API

import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';

part 'example.g.dart';

@RestApi(baseUrl: 'https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/')
abstract class RestClient {
  factory RestClient(Dio dio, {String? baseUrl}) = _RestClient;

  @GET('/tasks')
  Future<List<Task>> getTasks();
}

@JsonSerializable()
class Task {
  const Task({this.id, this.name, this.avatar, this.createdAt});

  factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);

  final String? id;
  final String? name;
  final String? avatar;
  final String? createdAt;

  Map<String, dynamic> toJson() => _$TaskToJson(this);
}

Configuration

You can customize the code generation behavior by creating a build.yaml file in your project root:

targets:
  $default:
    builders:
      retrofit_generator:
        options:
          # Control whether to add '// dart format off/on' comments (default: true)
          format_output: true
          # Enable automatic response type casting (default: true)
          auto_cast_response: true
          # Generate empty request body for methods without parameters (default: false)
          empty_request_body: false
          # Enable useResult annotation for methods (default: false)
          use_result: false

format_output

By default, retrofit_generator wraps the generated code with // dart format off and // dart format on comments to preserve the formatting. If you're combining retrofit with other generators (like riverpod) and need more control over formatting, you can disable this:

targets:
  $default:
    builders:
      retrofit_generator:
        options:
          format_output: false

then run the generator

# dart
dart pub run build_runner build

# for watch mode (recommended during development)
dart pub run build_runner watch

Lean Builder Support (Experimental)

Retrofit now has experimental support for lean_builder, a faster build system for Dart. While lean_builder support is still under development, the infrastructure has been added for future use.

Important: lean_builder is an optional dependency and is NOT required to use retrofit_generator. It's only needed if you want to try the experimental lean_builder support.

To prepare for lean_builder support, add it to your dev_dependencies:

dev_dependencies:
  lean_builder: ^0.1.2  # Optional - only if you want to use lean_builder

Note: For now, please continue using build_runner as shown above. Full lean_builder integration will be available in a future release once lean_builder reaches stability.

Use it

import 'package:dio/dio.dart';
import 'package:logger/logger.dart';
import 'package:retrofit_example/example.dart';

final logger = Logger();

void main(List<String> args) {
  final dio = Dio(); // Provide a dio instance
  dio.options.headers['Demo-Header'] = 'demo header'; // config your dio headers globally
  final client = RestClient(dio);

  client.getTasks().then((it) => logger.i(it));
}

More

Types

Types conversion

Before you use the type conversion, please make sure that a factory Task.fromJson(Map<String, dynamic> json) must be provided for each model class. json_serializable is recommended to be used as the serialization tool.

@GET('/tasks')
Future<List<Task>> getTasks();

@JsonSerializable()
class Task {
  const Task({required this.name});

  factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);

  final String name;
}

For enums, we rely on the toString() method to convert it to a string. Override the toString() method to return the value you want.

enum Status {
  pending,
  completed;

  @override
  String toString() => name;
}

@GET('/tasks/{status}')
Future<List<Task>> getTasksByStatus(@Path() Status status);

Using dart_mappable

You can use dart_mappable for type conversion by setting the parser to Parser.DartMappable:

@RestApi(
  baseUrl: 'https://api.example.com',
  parser: Parser.DartMappable,
)
abstract class ApiService {
  factory ApiService(Dio dio) = _ApiService;

  @GET('/tasks')
  Future<List<Task>> getTasks();
}

@MappableClass()
class Task with TaskMappable {
  const Task({this.id, this.name});

  final String? id;
  final String? name;
}

Don't forget to add the required dependencies:

dependencies:
  dart_mappable: ^4.2.0

dev_dependencies:
  dart_mappable_builder: ^4.2.0

And generate the code:

dart run build_runner build

For a complete example, see the example_dartmappable directory.

Typed extras

If you want to add static extra to all requests.

class MetaData extends TypedExtras {
  final String id;
  final String region;

  const MetaData({required this.id, required region});
}

@MetaData(
  id: '1234',
  region: 'ng',
)
@GET("/get")
Future<String> fetchData();

HTTP Methods

The HTTP methods in the below sample are supported.

  @GET('/tasks/{id}')
  Future<Task> getTask(@Path('id') String id);
  
  @GET('/demo')
  Future<String> queries(@Queries() Map<String, dynamic> queries);
  
  @GET('https://httpbin.org/get')
  Future<String> namedExample(
      @Query('apikey') String apiKey,
      @Query('scope') String scope,
      @Query('type') String type,
      @Query('from') int from,
  );
  
  @PATCH('/tasks/{id}')
  Future<Task> updateTaskPart(
    @Path() String id, 
    @Body() Map<String, dynamic> map,
  );
  
  @PUT('/tasks/{id}')
  Future<Task> updateTask(@Path() String id, @Body() Task task);
  
  @DELETE('/tasks/{id}')
  Future<void> deleteTask(@Path() String id);
  
  @POST('/tasks')
  Future<Task> createTask(@Body() Task task);
  
  @POST('http://httpbin.org/post')
  @MultiPart()
  Future<void> createNewTaskFromFile(@Part() File file);
  
  @POST('http://httpbin.org/post')
  @FormUrlEncoded()
  Future<String> postUrlEncodedFormData(@Field() String hello);

Runtime Content-Type for Multipart Uploads

Use @PartMap() to provide runtime metadata (like contentType and fileName) for multipart file uploads:

  @POST('/api/files')
  @MultiPart()
  Future<void> uploadFile({
    @Part(name: 'file') required File file,
    @PartMap() Map<String, dynamic>? metadata,
  });
  
  // Usage - Upload different file types to the same endpoint
  
  // Upload a JPEG image
  await client.uploadFile(
    file: File('/path/to/image.jpg'),
    metadata: {
      'file_contentType': 'image/jpeg',
      'file_fileName': 'photo.jpg',
    },
  );
  
  // Upload a PDF document
  await client.uploadFile(
    file: File('/path/to/document.pdf'),
    metadata: {
      'file_contentType': 'application/pdf',
      'file_fileName': 'report.pdf',
    },
  );

The @PartMap() annotation accepts a Map<String, dynamic> with keys in the format:

  • '<partName>_contentType' - Sets the content type for the part
  • '<partName>_fileName' - Sets the file name for the part

Fallback behavior:

  • Runtime values from @PartMap() override static values from @Part() annotation
  • If @PartMap() value is not provided, uses static value from @Part() annotation
  • If neither is provided:
    • fileName defaults to the file's actual name (extracted from file path)
    • contentType defaults to null (Dio will auto-detect based on file extension)

Dynamic Field Names for Multiple Files

Use @Part() with Map<String, File> to upload multiple files with dynamic field names:

  @POST('/api/files')
  @MultiPart()
  Future<void> uploadFiles(@Part() Map<String, File> files);
  
  // Usage - Upload multiple files with custom field names
  await client.uploadFiles({
    'image[0]': File('/path/to/photo1.jpg'),
    'image[1]': File('/path/to/photo2.jpg'),
    'document': File('/path/to/report.pdf'),
  });

This feature also supports:

  • Map<String, MultipartFile> - For files already wrapped in MultipartFile
  • Map<String, List<int>> - For raw byte data
  • Nullable maps: Map<String, File>?

Use cases:

  • Uploading arrays of files where each file needs a unique indexed name (e.g., image[0], image[1])
  • Uploading files to endpoints that require specific field names determined at runtime
  • Sending multiple files of different types in a single request

Get original HTTP response

  @GET('/tasks/{id}')
  Future<HttpResponse<Task>> getTask(@Path('id') String id);

  @GET('/tasks')
  Future<HttpResp

Related Skills

View on GitHub
GitHub Stars1.2k
CategoryDevelopment
Updated2d ago
Forks289

Languages

Dart

Security Score

100/100

Audited on Apr 2, 2026

No findings