Giter Site home page Giter Site logo

trevorwang / retrofit.dart Goto Github PK

View Code? Open in Web Editor NEW
1.0K 10.0 231.0 1.38 MB

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

Home Page: https://mings.in/retrofit.dart/

License: MIT License

Dart 81.22% Shell 0.29% Kotlin 0.17% Swift 0.67% Objective-C 0.01% Python 0.14% CMake 7.28% C++ 8.94% C 0.55% HTML 0.72%
retrofit dart dio http source-gen build-runner chopper

retrofit.dart's Introduction

Retrofit For Dart

retrofit retrofit_generator Pub Likes Testing

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.0.0 <5.0.0'
  logger: any  #for logging purpose
  json_annotation: ^4.8.1

dev_dependencies:
  retrofit_generator: '>=7.0.0 <8.0.0'   // required dart >=2.19
  build_runner: '>=2.3.0 <4.0.0'
  json_serializable: ^6.6.2

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

then run the generator

# dart
dart pub run build_runner build

# flutter	
flutter pub run build_runner build

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

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

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')
  Future<void> createNewTaskFromFile(@Part() File file);
  
  @POST('http://httpbin.org/post')
  @FormUrlEncoded()
  Future<String> postUrlEncodedFormData(@Field() String hello);

Get original HTTP response

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

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

HTTP Header

  • Add a HTTP header from the parameter of the method
  @GET('/tasks')
  Future<Task> getTasks(@Header('Content-Type') String contentType);
  • Add static HTTP headers
  import 'package:dio/dio.dart' hide Headers;

  // ...
  
  @GET('/tasks')
  @Headers(<String, dynamic>{
    'Content-Type': 'application/json',
    'Custom-Header': 'Your header',
  })
  Future<Task> getTasks();

Error Handling

catchError(Object) should be used for capturing the exception and failed response. You can get the detailed response info from DioError.response.

client.getTask('2').then((it) {
  logger.i(it);
}).catchError((obj) {
  // non-200 error goes here.
  switch (obj.runtimeType) {
    case DioError:
      // Here's the sample to get the failed response error code and message
      final res = (obj as DioError).response;
      logger.e('Got error : ${res.statusCode} -> ${res.statusMessage}');
      break;
  default:
    break;
  }
});

Relative API baseUrl

If you want to use a relative baseUrl value in the RestApi annotation of the RestClient, you need to specify a baseUrl in dio.options.baseUrl.

@RestApi(baseUrl: '/tasks')
abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

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

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

dio.options.baseUrl = 'https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1';
final client = RestClient(dio);

Multiple endpoints support

If you want to use multiple endpoints to your RestClient, you should pass your base url when you initiate RestClient. Any value defined in RestApi will be ignored.

@RestApi(baseUrl: 'this url will be ignored if baseUrl is passed')
abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
}

final client = RestClient(dio, baseUrl: 'your base url');

If you want to use the base url from dio.option.baseUrl, which has lowest priority, please don't pass any parameter to RestApi annotation and RestClient's structure method.

Multithreading (Flutter only)

If you want to parse models on a separate thread, you can take advantage of the compute function, just like Dio does when converting String data responses into json objects.

For each model that you use you will need to define 2 top-level functions:

FutureOr<Task> deserializeTask(Map<String, dynamic> json);
FutureOr<dynamic> serializeTask(Task object);

If you want to handle lists of objects, either as return types or parameters, you should provide List counterparts:

FutureOr<List<Task>> deserializeTaskList(Map<String, dynamic> json);
FutureOr<dynamic> serializeTaskList(List<Task> objects);

Finally, make sure you set your @RestApi to use the Parser.FlutterCompute parser:

@RestApi(parser: Parser.FlutterCompute)

E.g.

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

  @GET('/task')
  Future<Task> getTask();

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

  @POST('/task')
  Future<void> updateTasks(Task task);

  @POST('/tasks')
  Future<void> updateTasks(List<Task> tasks);
}

Task deserializeTask(Map<String, dynamic> json) => Task.fromJson(json);

List<Task> deserializeTaskList(List<Map<String, dynamic>> json) =>
    json.map((e) => Task.fromJson(e)).toList();

Map<String, dynamic> serializeTask(Task object) => object.toJson();

List<Map<String, dynamic>> serializeTaskList(List<Task> objects) =>
    objects.map((e) => e.toJson()).toList();

N.B. Avoid using Map values, otherwise multiple background isolates will be spawned to perform the computation, which is extremely intensive for Dart.

abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

  // BAD
  @GET('/tasks')
  Future<Map<String, Task>> getTasks();

  @POST('/tasks')
  Future<void> updateTasks(Map<String, Task> tasks);

  // GOOD
  @GET('/tasks_names')
  Future<TaskNames> getTaskNames();

  @POST('/tasks_names')
  Future<void> updateTasks(TaskNames tasks);
}

TaskNames deserializeTaskNames(Map<String, dynamic> json) =>
    TaskNames.fromJson(json);

@JsonSerializable
class TaskNames {
  const TaskNames({required this.taskNames});

  final Map<String, Task> taskNames;

  factory TaskNames.fromJson(Map<String, dynamic> json) =>
      _$TaskNamesFromJson(json);
}

Hide generated files

For the project not to be confused with the files generated by the retrofit you can hide them.

Android studio

File -> Settings -> Editor -> File Types

Add "ignore files and folders"

*.g.dart

Credits

  • JetBrains. Thanks for providing the great IDE tools.

Contributors ✨

Thanks goes to these wonderful people:

Contributions of any kind welcome!

Activities

Alt

retrofit.dart's People

Contributors

2zerosix avatar akash98sky avatar alexaf2000 avatar alexeybukin avatar allcontributors[bot] avatar artem-zaitsev avatar betternormal avatar carapacik avatar czocher avatar devkabiir avatar devon avatar ebwood avatar ekuleshov avatar elenaferr0 avatar emintolgahanpolat avatar gauravmehta13 avatar gfranks avatar iamriajul avatar ilovelinux avatar ipcjs avatar jiechic avatar kernelpanic92 avatar lucathehacker avatar mohn93 avatar nicolaverbeeck avatar sbntt avatar sooxt98 avatar trevorwang avatar via-guy avatar woprandi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

retrofit.dart's Issues

Generator doesn't work if file contains extension

Describe the bug
Generator silently fails when file with RestApi class contains extension method. The generated file contains only class stub without any methods, e.g.:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'client.dart';

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

class _RestClient implements RestClient {
  _RestClient(this._dio, {this.baseUrl}) {
    ArgumentError.checkNotNull(_dio, '_dio');
  }

  final Dio _dio;

  String baseUrl;
}

To Reproduce
Steps to reproduce the behavior:

  1. Create file with RestApi class, e.g.:
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';

part 'client.g.dart';

@RestApi()
abstract class RestClient {
  factory RestClient(Dio dio) = _RestClient;

  @POST('/api/general/v1/users/signIn')
  Future<Map<String, dynamic>> signIn();
}
  1. Launch generator and ensure that proper implementation class was generated.
  2. Add any extension method to the file, e.g.:
extension on Future {
  String test() => 'test';
}
  1. Re-launch generator and ensure that only stub implementation was generated without any methods. No errors were found in generator output.

Expected behavior
Implementation class should be properly generated.

Environment:

  • OS: macOS 10.15.1
  • flutter --version:
Flutter 1.12.15-pre.29 • channel master • https://github.com/flutter/flutter.git
Framework • revision 84a1de5c74 (13 days ago) • 2019-11-27 07:40:51 -0500
Engine • revision fad1b23c42
Tools • Dart 2.7.0

Allow base url switching during runtime

When constructing the RestApi annotation, which is required after some testing, you must specify a baseUrl. After creating your RestClient instance, you modify the dio.baseUrl to use the annotated baseUrl. Could you also have a priority to use the baseUrl already set on the dio instance?

I have always built in an environment switch into all of my apps so testers or developers can quickly change environments without having to re-build and then re-deploy.

For the time being, a workaround would be:

static RestClient _instance(Dio dio) {
    final String baseUrl = dio.options.baseUrl;
    _ RestClient service = _ RestClient(dio);
    service._dio.options.baseUrl = baseUrl;
    return service;
}

Much appreciated.

General improvements from my fork

I have made some general improvements in my fork. If you like those improvements, I'll send you a PR :).

Edit: my improvements include

  1. Using const for things that are constants instead of final

  2. Add @override annotation as a convention
    Example:

    + @override
      Future<Response<Account>> createAccount({Account data}) async {...}
  3. Removed type information from generated methods, Dart can automatically infer the types from the super class devkabiir/retrofit.dart@ea051356ac24d85a932bf68f80c3a5fdc37c5073
    Example:

    - Future<Response<Account>> createAccount({Account data}) async {...}
    + createAccount({data}) async {...}
  4. Use forwarding/redirecting constructor instead of instance() static method
    Example:

    -   static RestClient instance([Dio dio]) => _RestClient(dio);
    +   factory RestClient([Dio dio]) = _RestClient;
  5. Don't add check for baseUrl being null because it's a constant and we can know that at build-time devkabiir/retrofit.dart@f5664c7a58d12ec396bb0b83d7993ca3f48ab1da
    Example:

      _RestClient([this._dio]) {
    -    final baseUrl = 'https://httpbin.org/';
    -    if (baseUrl != null && baseUrl.isNotEmpty) {
    -      _dio.options.baseUrl = baseUrl;
    +      _dio.options.baseUrl = 'https://httpbin.org/';
    -    }
      }
  6. Add ArgumentError.checkNotNull for required parameters devkabiir/retrofit.dart@6c675756e2eedf943d0263b0f940a99373a0bedb

  7. Generate *.g.dart shared parts instead of *.retrofit.dart parts

  8. Implement the annotated class instead of extending it
    Example:

    -    class _RestClient extends RestClient {
    +    class _RestClient implements RestClient {
  9. Don't assign defaultValue if it's null for parameters devkabiir/retrofit.dart@a610e2675c84376c56f9568b9c5f1476b9995596

  10. Add @Extra for passing extra options to requestOptions devkabiir/retrofit.dart@1d470c296efc46c0e500e60287ed187a3244b60a
    Example:
    myapi.dart

      @http.POST('/account/')
    + @dio.Extra({'myKey': 'myValue'})
      Future<Response<Account>> createAccount(
          {@required @http.Body() Account data});

    myapi.g.dart

      @override
      createAccount({data}) async {
        ArgumentError.checkNotNull(data, 'data');
    +   const _extra = <String, dynamic>{'myKey': 'myValue'};
        final queryParameters = <String, dynamic>{};
        final _data = data;
        return _dio.request('/account/',
            queryParameters: queryParameters,
    -       options: RequestOptions(method: 'POST', headers: {}),
    +       options: RequestOptions(method: 'POST', headers: {}, extra: _extra),
            data: _data);
      }

dio 3.0 changed 'contentType' argument type

Describe the bug
Options.contentType is now a String, not a ContentType object.

To Reproduce

  @POST('/')
  @FormUrlEncoded()
  Future<String> a();
@override
  a() async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = <String, dynamic>{};
    final Response<String> _result = await _dio.request('/',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'POST',
            headers: <String, dynamic>{},
            extra: _extra,
            contentType: ContentType.parse('application/x-www-form-urlencoded'),
            baseUrl: baseUrl),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

contentType should be 'application/x-www-form-urlencoded', or use Headers.formUrlEncodedContentType.

Add support to get response headers

Describe the bug
A clear and concise description of what the bug is.
I would like to read also response headers with values
Is it possible to get response together with parsed values?

Please support dio 3.0.0

We have released version 3.0 of Dio, could you support it ? We will archive this project in the Dio documentation after it supports Dio 3.0. thanks.

Need a HEAD request type

Hi, I love your lib, just has I loved using retrofit on Android.
I do need one thing though,
could you make a small change to the annotations on http.dart to allow HEAD request type please?

Thank you so much!
Regards

Generated service method for String is incorrect

Describe the bug
When defining methods with base classe return types such as String the generator creates incorrect implementations.

To Reproduce

  1. Create a service with a method such as:
  @GET("/api/tags")
  Future<List<String>> getTags();

The generator creates a given incorrect implementation:

  @override
  getTags() async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = <String, dynamic>{};
    final Response<List<dynamic>> _result = await _dio.request('/api/tags',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'GET', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    var value = _result.data
        .map((dynamic i) => String.fromJson(i as Map<String, dynamic>))
        .toList();
    return Future.value(value);
  }

There is no such method as String.fromJson and you can't really create it in any way.

Expected behavior
The expected code should be:

  @override
  getTags() async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = <String, dynamic>{};
    final Response<List<String>> _result = await _dio.request('/api/tags',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'GET', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    var value = _result.data;
    return Future.value(value);
  }

Additional context
If this is not a bug and there's some way to make it work, can you please describe it? I'd be very grateful, thanks!

Allow Nullable @Field()

In my case, I have this api:
submitComplaint(@Field() String bin, @Field() String taxpayer, @Field() File photo, @Field() String details)
Here, the photo is not a required parameter.
Solutions can be:

  1. add Annotation for Nullable.
  2. add an optional parameter to @field(), like @field(nullable: true)

Unhandled Exception: DioError [DioErrorType.DEFAULT]: type 'String' is not a subtype of type 'List<dynamic>'

Hello,
Thank you for the awesome library..
I'm unable to convert below JSON structure.

{
	"total": "14006",
	"page": 1,
	"pages": 701,
	"tv_shows": [{
		"id": 35624,
		"name": "The Flash",
		"permalink": "the-flash"
	}]
}

I'm getting following exception :
E/flutter (23069): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: DioError ```
[DioErrorType.DEFAULT]: type 'String' is not a subtype of type 'List'
E/flutter (23069): #0 DioMixin.assureResponse (package:dio/src/dio.dart:1120:9)
E/flutter (23069): #1 DioMixin._request. (package:dio/src/dio.dart:883:14)
E/flutter (23069): #2 _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (23069): #3 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (23069): #4 _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
E/flutter (23069): #5 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
E/flutter (23069): #6 Future._propagateToListeners (dart:async/future_impl.dart:707:32)
E/flutter (23069): #7 Future._complete (dart:async/future_impl.dart:512:7)
E/flutter (23069): #8 _SyncCompleter.complete (dart:async/future_impl.dart:51:12)
E/flutter (23069): #9 Future.any. (dart:async/future.dart:459:45)
E/flutter (23069): #10 _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (23069): #11 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (23069): #12 _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
E/flutter (23069): #13 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
E/flutter (23069): #14 Future._propagateToListeners (dart:async/future_impl.dart:707:32)
E/flutter (23069): #15 Future._completeWithValue (dart:async/future_impl.dart:522:5)
E/flutter (23069): #16 _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:30:15)
E/flutter (23069): #17 _completeOnAsyncReturn (dart:async-patch/async_patch.dart:288:13)
E/flutter (23069): #18 DioMixin._dispatchRequest (package:dio/src/dio.dart)
E/flutter (23069):
E/flutter (23069): #19 DioMixin._request._interceptorWrapper... (package:dio/src/dio.dart:828:37)
E/flutter (23069): #20 DioMixin.checkIfNeedEnqueue (package:dio/src/dio.dart:1099:22)
E/flutter (23069): #21 DioMixin._request._interceptorWrapper.. (package:dio/src/dio.dart:825:22)
E/flutter (23069): #22 new Future. (dart:async/future.dart:176:37)
E/flutter (23069): #23 _rootRun (dart:async/zone.dart:1120:38)
E/flutter (23069): #24 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter (23069): #25 _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter (23069): #26 _CustomZone.bindCallbackGuarded. (dart:async/zone.dart:963:23)
E/flutter (23069): #27 _rootRun (dart:async/zone.dart:1124:13)
E/flutter (23069): #28 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter (23069): #29 _CustomZone.bindCallback. (dart:async/zone.dart:947:23)
E/flutter (23069): #30 Timer._createTimer. (dart:async-patch/timer_patch.dart:21:15)
E/flutter (23069): #31 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:382:19)
E/flutter (23069): #32 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:416:5)
E/flutter (23069): #33 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
E/flutter (23069):
E/flutter (23069): #0 DioMixin._request. (package:dio/src/dio.dart:886:9)
E/flutter (23069): #1 _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (23069): #2 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (23069): #3 _FutureListener.handleError (dart:async/future_impl.dart:155:20)
E/flutter (23069): #4 Future._propagateToListeners.handleError (dart:async/future_impl.dart:690:47)
E/flutter (23069): #5 Future._propagateToListeners (dart:async/future_impl.dart:711:24)
E/flutter (23069): #6 Future._complete (dart:async/future_impl.dart:512:7)
E/flutter (23069): #7 _SyncCompleter.complete (dart:async/future_impl.dart:51:12)
E/flutter (23069): #8 Future.any. (dart:async/future.dart:459:45)
E/flutter (23069): #9 _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (23069): #10 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (23069): #11 _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
E/flutter (23069): #12 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
E/flutter (23069): #13 Future._propagateToListeners (dart:async/future_impl.dart:707:32)
E/flutter (23069): #14 Future._completeWithValue (dart:async/future_impl.dart:522:5)
E/flutter (23069): #15 _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:30:15)
E/flutter (23069): #16 _completeOnAsyncReturn (dart:async-patch/async_patch.dart:288:13)
E/flutter (23069): #17 DioMixin._dispatchRequest (package:dio/src/dio.dart)
E/flutter (23069):
E/flutter (23069): #18 DioMixin._request._interceptorWrapper...<anonym

Add a link to the project github to the pub.dev page

Is your feature request related to a problem? Please describe.
It's hard to find this Github from the pub.dev page of the project: https://pub.dev/packages/retrofit

Describe the solution you'd like
A Github link should be added same as in other packages.

Describe alternatives you've considered
A link in the documentation can also be provided, but as far as I'm aware pub.dev allows for Github links in the sidebar.

Bean class support for @Body() annotation

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Nested generics support

Hi there! Please add nested generic types supporting. Like this case:

Future<ObjectResponse<List<Item>>> getItems();

class ObjectResponse<T> {
  T value;
  bool success;
  String message;
}
class Item {
  String val1;
  String val2;
}

Cannot use the same Dio client for two APIs with different baseUrls

Describe the bug
I have two RestApis with different baseUrls which use the same Dio client. Whichever one gets created first breaks because the second one overwrites the _dio.options.baseUrl in the generated .g.dart constructor.

To Reproduce
Steps to reproduce the behavior:

  1. Create two RestApis with different baseUrls
  2. Build them with the same Dio client
  3. Whichever RestApi is built first will use the baseUrl for the RestApi built second.

Example

@RestApi(baseUrl: "https://my.baseurl.com/")
abstract class MyBaseUrlService {
   factory MyBaseUrlService(Dio dio) = _MyBaseUrlService;

    ...
}
@RestApi(baseUrl: "https://myother.baseurl.com/")
abstract class MyOtherBaseUrlService {
   factory MyOtherBaseUrlService(Dio dio) = _MyOtherBaseUrlService;

    ...
}
final dio = Dio();
final myBaseUrlService = MyBaseUrlService(dio);
final myOtherBaseUrlService = MyOtherBaseUrlService(dio);

// myBaseUrlService will use the baseUrl from MyOtherBaseUrlService

Expected behavior
RestApis with different baseUrls should be able to use the same Dio client to benefit from shared Dio options, interceptors, etc.

Possible solution
Instead of changing the dio.options.baseUrl, prepend it to the path before calling _dio.request

Generator incompatible with Dio v.2.2.1

Describe the bug
After upgrading my Flutter project to 1.9.1 and running flutter pub upgrade, my retrofit generator began throwing the following error:

[SEVERE] Failed to snapshot build script .dart_tool/build/entrypoint/build.dart.
This is likely caused by a misconfigured builder definition.
[SEVERE] ../Downloads/flutter/.pub-cache/hosted/pub.dartlang.org/retrofit_generator-0.6.3/lib/src/generator.dart:499:38: Error: Getter not found: 'UploadFileInfo'.      final uploadFileInfo = refer('$UploadFileInfo')                                     ^^^^^^^^^^^^^^../Downloads/flutter/.pub-cache/hosted/pub.dartlang.org/retrofit_generator-0.6.3/lib/src/generator.dart:499:38: Error: The getter 'UploadFileInfo' isn't defined for the class 'RetrofitGenerator'. - 'RetrofitGenerator' is from 'package:retrofit_generator/src/generator.dart' ('../Downloads/flutter/.pub-cache/hosted/pub.dartlang.org/retrofit_generator-0.6.3/lib/src/generator.dart').Try correcting the name to the name of an existing getter, or defining a getter or field named 'UploadFileInfo'.      final uploadFileInfo = refer('$UploadFileInfo')                                     ^^^^^^^^^^^^^^
pub failed (78)

After playing with a few dependencies which had been upgraded, I singled out Dio (2.1.16 -> 2.2.1) as being the culprit. By setting dio: 2.1.16 instead of dio: ^2.1.16 in my pubspec.yaml file, I was able to run the generator with no problems.

I don't know how refer('$UploadFileInfo') works, but it seems like the Dio upgrade has broken compatibility with refer.

To Reproduce
Steps to reproduce the behavior:

  1. Use Dio v.2.2.1
  2. Use retrofit and retrofit_generator v.0.6.3
  3. Run flutter pub run build_runner build --delete-conflicting-outputs

Desktop (please complete the following information):

  • OS: iOS

Add integration tests checking basic use-cases

Is your feature request related to a problem? Please describe.
After developing the last PR I noticed, since I haven't taken some dynamic type cast problems into consideration, my lack of Dart expertise caused a new bug.

Describe the solution you'd like
The basic library use-cases should be tested not only by the generator unit tests but also with proper integration tests with a mocked example service.

Some potential use cases which should be taken into consideration include:

  • Returning basic types (String, int, bool, double)
  • Returning a collection of basic types (List, Map?)
  • Returning a single self-made type (the inner properties shouldn't matter since that part is handled by json_serialization anyway, but we can add some basic types plus one self-made type)
  • Returning a collection of self-made types (List, Map?)

This should not only allow us to have better test coverage (another thing which can be checked!) of edge cases but also grant us peace of mind when developing in the future as well as guard us from potential regressions in runtime situations (such as the one described above).

Describe alternatives you've considered
I should really learn Dart better before providing PRs :D.

Does not generate strong mode compatible source (breaks hot reload)

Description
The source code generated by the retrofit generator breaks both strong mode options implicit-casts: false and implicit-dynamic: false. Currently there is no way to exclude generated files from strong mode analysis and subsequently breaks hot reload (unless the generated source is fixed manually). There is an open issue here to be able to exclude files from strong mode analysis. It still compiles and runs the app, however it does prevent hot reload from working which is the main issue here.

To Reproduce

  1. Using the retrofit example project, enable the strong mode options to your analysis_options.yaml file as described here.
  2. Fix typing on example.dart by: adding map type to @Headers as <String, dynamic>. Also add <String, dynamic> to profile method's @Queries parameter, e.g.map = const <String, dynamic>{}. See additional information for updated example.dart with strong typing.
  3. Re-generate file for sanity.
  4. Open generated example.g.dart.

Expected behaviour
Ideally, the generated code should conform to both strong mode options.

Actual behaviour
See annotated screenshot:
image

  1. As a @Queries parameter is provided, this causes a dynamically typed map literal to be created and thus breaks the implicit-dynamic: false option. Should be initialised as <String, dynamic>{}.
  2. Not having a @Body parameter, causes _data to be initialised to null without an explicit type and thus breaks the implicit-dynamic: false option. Should be initialised as Map<String, dynamic>.
  3. dio.request should be dio.request<Map<String, dynamic>> when using a custom type with a fromJson method (such as in ip method). For types like String etc, the type should be that (as in profile method).
  4. headers map should be initialised as <String, dynamic> {...
  5. If 3 is fixed, then this error disappears as the _result is typed to Map<String, dynamic> for the ip method, and String for the profile method.

Additional information
Unfortunately it gets more complicated with the groupedUser and groupedUsers` methods which I've decided not to elaborate on, however I've included modified generated source below with strong typing.

Here is the updated example.dart file with strong typing:

import 'dart:io';

import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/http.dart';
import 'package:dio/dio.dart';
import 'package:retrofit_example/http_get.dart';

part 'example.g.dart';

@RestApi(baseUrl: "https://httpbin.org/")
abstract class RestClient {
  factory RestClient(Dio dio) = _RestClient;

  @GET("/get")
  @Headers(<String, dynamic>{
    "Header-One": " header 1",
  })
  Future<HttpGet> ip(@Query('query1') String query,
      {@Queries() Map<String, dynamic> queryies,
        @Header("Header-Two") String header});

  @GET("/profile/{id}")
  Future<String> profile(@Path("id") String id,
      {@Query("role") String role = "user",
        @Queries() Map<String, dynamic> map = const <String, dynamic>{},
        @Body() Map<String, dynamic> map2});

  @POST("/post")
  @Headers(<String, dynamic>{
    "Accept": "application/json",
  })
  Future<String> createProfile(@Query('query2') String query,
      {@Queries() Map<String, dynamic> queryies,
        @Header("Header-One") String header,
        @Body() Map<String, dynamic> map2,
        @Field() int field,
        @Field("field-g") String ffff});

  @PUT("/put")
  Future<String> updateProfile2(@Query('query3') String query,
      {@Queries() Map<String, dynamic> queryies,
        @Header("Header-One") String header,
        @Field() int field,
        @Field("field-g") String ffff});

  @PATCH("/patch")
  Future<String> updateProfile(@Query('query4') String query,
      {@Queries() Map<String, dynamic> queryies,
        @Field() int field,
        @Field("field-g") String ffff});

  @POST("/profile")
  Future<String> setProfile(@Field('image', 'my_profile_image.jpg') File image);

  /// This will add the image name from `image.path.split(Platform.pathSeperator).last`
  @POST("/profile")
  Future<String> setProfileImage(@Field() File image);

  /// This will automatically work too.
  @POST("/profile")
  Future<String> setProfileImageWithInfo(@Field() UploadFileInfo image);

  @POST("/users")
  Future<String> createUser(@Body() User user);

  @GET("/users")
  Future<Map<String, List<User>>> groupedUsers();

  @GET("/users")
  Future<Map<String, User>> groupedUser();
}

@JsonSerializable()
class User {
  User();

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

The generated file should look like this:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'example.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

User _$UserFromJson(Map<String, dynamic> json) {
  return User();
}

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{};

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

class _RestClient implements RestClient {
  _RestClient(this._dio) {
    ArgumentError.checkNotNull(_dio, '_dio');
    _dio.options.baseUrl = 'https://httpbin.org/';
  }

  final Dio _dio;

  @override
  ip(query, {queryies, header}) async {
    ArgumentError.checkNotNull(query, 'query');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{'query1': query};
    queryParameters.addAll(queryies ?? <String, dynamic>{});
    const Map<String, dynamic> _data = null;
    final _result = await _dio.request<Map<String, dynamic>>('/get',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'GET',
            headers: <String, dynamic>{'Header-One': ' header 1', 'Header-Two': header},
            extra: _extra),
        data: _data);
    final value = HttpGet.fromJson(_result.data);
    return Future.value(value);
  }

  @override
  profile(id, {role = "user", map = const <String, dynamic>{}, map2}) async {
    ArgumentError.checkNotNull(id, 'id');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{'role': role};
    queryParameters.addAll(map ?? <String, dynamic>{});
    final _data = <String, dynamic>{};
    _data.addAll(map2 ?? <String, dynamic>{});
    final _result = await _dio.request<String>('/profile/$id',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'GET', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  createProfile(query, {queryies, header, map2, field, ffff}) async {
    ArgumentError.checkNotNull(query, 'query');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{'query2': query};
    queryParameters.addAll(queryies ?? <String, dynamic>{});
    final _data = <String, dynamic>{};
    _data.addAll(map2 ?? <String, dynamic>{});
    final _result = await _dio.request<String>('/post',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'POST',
            headers: <String, dynamic>{'Accept': 'application/json', 'Header-One': header},
            extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  updateProfile2(query, {queryies, header, field, ffff}) async {
    ArgumentError.checkNotNull(query, 'query');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{'query3': query};
    queryParameters.addAll(queryies ?? <String, dynamic>{});
    final _data = FormData.from(<String, dynamic>{'field': field, 'field-g': ffff});
    final _result = await _dio.request<String>('/put',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'PUT', headers: <String, dynamic>{'Header-One': header}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  updateProfile(query, {queryies, field, ffff}) async {
    ArgumentError.checkNotNull(query, 'query');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{'query4': query};
    queryParameters.addAll(queryies ?? <String, dynamic>{});
    final _data = FormData.from(<String, dynamic>{'field': field, 'field-g': ffff});
    final _result = await _dio.request<String>('/patch',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'PATCH', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  setProfile(image) async {
    ArgumentError.checkNotNull(image, 'image');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data =
        FormData.from(<String, dynamic>{'image': UploadFileInfo(image, 'my_profile_image.jpg')});
    final _result = await _dio.request<String>('/profile',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'POST', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  setProfileImage(image) async {
    ArgumentError.checkNotNull(image, 'image');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = FormData.from(<String, dynamic>{
      'image':
          UploadFileInfo(image, image.path.split(Platform.pathSeparator).last)
    });
    final _result = await _dio.request<String>('/profile',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'POST', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  setProfileImageWithInfo(image) async {
    ArgumentError.checkNotNull(image, 'image');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = FormData.from(<String, dynamic>{'image': image});
    final _result = await _dio.request<String>('/profile',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'POST', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  createUser(user) async {
    ArgumentError.checkNotNull(user, 'user');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = <String, dynamic>{};
    _data.addAll(user.toJson() ?? <String, dynamic>{});
    final _result = await _dio.request<String>('/users',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'POST', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data;
    return Future.value(value);
  }

  @override
  groupedUsers() async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    const Map<String, dynamic> _data = null;
    final _result = await _dio.request<Map<String, List<Map<String, dynamic>>>>('/users',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'GET', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data.map((k, v) => MapEntry(k, v.map((i) => User.fromJson(i)).toList()));
    return Future.value(value);
  }

  @override
  groupedUser() async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    const Map<String, dynamic> _data = null;
    final _result = await _dio.request<Map<String, Map<String, dynamic>>>('/users',
        queryParameters: queryParameters,
        options: RequestOptions(method: 'GET', headers: <String, dynamic>{}, extra: _extra),
        data: _data);
    final value = _result.data.map((k, v) => MapEntry(k, User.fromJson(v)));
    return Future.value(value);
  }
}

Map of basic types

Describe the bug
Future<Map<String, [Basic type]>> a(); does not work.

  @POST('/')
  Future<Map<String, String>> a();

a() async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = <String, dynamic>{};
    final Response<Map<String, dynamic>> _result = await _dio.request('/',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'POST',
            headers: <String, dynamic>{},
            extra: _extra,
            baseUrl: baseUrl),
        data: _data);
    return Future.value(value);
  }

the value is not defined.

Build Bug

In comand : flutter pub run build_runner build

Error:
[SEVERE] Failed to snapshot build script .dart_tool/build/entrypoint/build.dart. This is likely caused by a misconfigured builder definition. [SEVERE] /D:/tools/flutter/.pub-cache/hosted/pub.dartlang.org/retrofit_generator-0.6.3/lib/src/generator.dart:499:38: Error: Getter not found: 'UploadFileInfo'. final uploadFileInfo = refer('$UploadFileInfo') ^^^^^^^^^^^^^^/ D:/tools/flutter/.pub-cache/hosted/pub.dartlang.org/retrofit_generator-0.6.3/lib/src/generator.dart:499:38: Error: The getter 'UploadFileInfo' isn't defined for the class 'RetrofitGenerator'. - 'RetrofitGenerator' is from 'package:retrofit_generator/src/generator.d art' ('/D:/tools/flutter/.pub-cache/hosted/pub.dartlang.org/retrofit_generator-0.6.3/lib/src/generator.dart').Try correcting the name to the name of an existing getter, or defining a getter or field named 'UploadFileInfo'. final uploadFileInfo = refer('$Upload FileInfo') ^^^^^^^^^^^^^^ pub failed (78)

depends failed

Because every version of flutter from sdk depends on meta 1.1.6 and every version of retrofit depends on meta ^1.1.7, flutter from sdk is incompatible with retrofit.
So, because demo depends on both flutter any from sdk and retrofit any, version solving failed.
pub get failed (1)
exit code 1

Unhandled Exception: DioError [DioErrorType.DEFAULT]: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'List<dynamic>'

Describe the bug

When I try to get this JSON

{
    "lessons": [
        {
            "evtId": 4748798,
            "evtDate": "2019-11-08",
            "evtCode": "LSF0",
            "evtHPos": 1,
            "evtDuration": 1,
            "classDesc": "4IA INFORMATICA",
            "authorName": "xx",
            "subjectId": 215877,
            "subjectCode": "",
            "subjectDesc": "SCIENZE MOTORIE E SPORTIVE",
            "lessonType": "Spiegazione",
            "lessonArg": "Continuazione analisi file sul P.S. fino alle distorsioni"
        },
        {
            "evtId": 4748866,
            "evtDate": "2019-11-08",
            "evtCode": "LSF0",
            "evtHPos": 2,
            "evtDuration": 1,
            "classDesc": "4IA INFORMATICA",
            "authorName": "xx",
            "subjectId": 215877,
            "subjectCode": "",
            "subjectDesc": "SCIENZE MOTORIE E SPORTIVE",
            "lessonType": "Spiegazione",
            "lessonArg": "P.S. simulazioni manovre,"
        }
    ]
}

with:

 @GET("/students/{studentId}/lessons/20191108/20191109")
  Future<List<Lesson>> getTodayLessons(@Path() String studentId);`

I get

E/flutter (23023): #28     _CustomZone.run  (dart:async/zone.dart:1021:19)
E/flutter (23023): #29     _CustomZone.bindCallback.<anonymous closure>  (dart:async/zone.dart:947:23)
E/flutter (23023): #30     Timer._createTimer.<anonymous closure>  (dart:async-patch/timer_patch.dart:21:15)
E/flutter (23023): #31     _Timer._runTimers  (dart:isolate-patch/timer_impl.dart:382:19)
E/flutter (23023): #32     _Timer._handleMessage  (dart:isolate-patch/timer_impl.dart:416:5)
E/flutter (23023): #33     _RawReceivePortImpl._handleMessage  (dart:isolate-patch/isolate_patch.dart:172:12)
E/flutter (23023):

This is the lesson class

import 'package:json_annotation/json_annotation.dart';

part 'lesson.g.dart';

@JsonSerializable()
class Lesson {
  int evtId;
  String evtDate;
  String evtCode;
  int evtHPos;
  int evtDuration;
  String classDesc;
  String authorName;
  int subjectId;
  String subjectCode;
  String subjectDesc;
  String lessonType;
  String lessonArg;

  Lesson(
      {this.evtId,
      this.evtDate,
      this.evtCode,
      this.evtHPos,
      this.evtDuration,
      this.classDesc,
      this.authorName,
      this.subjectId,
      this.subjectCode,
      this.subjectDesc,
      this.lessonType,
      this.lessonArg});

  factory Lesson.fromJson(Map<String, dynamic> json) => _$LessonFromJson(json);
  Map<String, dynamic> toJson() => _$LessonToJson(this);
}

Versions:
retrofit: ^1.0.1+1 dio: ^3.0.7

the only options i set in Dio:
`
dio.options.headers["Content-Type"] = Headers.jsonContentType;
dio.options.headers["User-Agent"] = "xx;
dio.options.headers["Z-Dev-Apikey"] = "xx";

`

Thanks 👍🏻

add converter plugin to handle data converting

对比了chopper,这个库还是过于简单,很多自定义的逻辑没有实现,不知道作者有没有计划做
Converter?比如参数Converter,还有body Converter
我们这边业务服务到需要自定义数据转换器来实现请求的通用化。

Combine few versions of api in one Client

Hello! Is there any elegant solution to combine a few API versions in one Client?
For example:

Dio dio = Dio(BaseOptions(baseUrl: "https://site.ru/api/v1/");

abstract class GClient {
  factory GClient(Dio dio) = _GClient;

  @GET("shops/{shopId}/products") //it's v1 because it's relevant
  Future<List<Product>> getProductsV1(@Path() String shopId, @Query('categoryId') String categoryId);

  @GET("/api/v2/shops/{shopId}/products") //it's v2 because it starts with /, so it's absoulte
  Future<List<Product>> getProductsV2(@Path() String shopId, @Query('categoryId') String categoryId);
}

I don't want to make baseUrl https://site.ru/api/ and then specify api version for all requests, because many of requests are v1 and only few of them v2.

Support @Options annotation

Is your feature request related to a problem? Please describe.
Support @Options annotation per request to allow setting RequestOptions for a single request without changing for all requests (at the Dio level)

Describe the solution you'd like

  @GET("/file/{fileId}")
  @Headers({"Accept": "application/json"})
  @Options(responseType: ResponseType.bytes)
  Future<List<int>> getFile(
      @Path("fileId") int fileId
  );

Compiles to

getFile(fileId) async {
    ArgumentError.checkNotNull(fileId, 'fileId');
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _data = <String, dynamic>{};

    final Response<Map<String, dynamic>> _result = await _dio.request(
        'file/{fileId}',
        queryParameters: queryParameters,
        options: RequestOptions(
            responseType: ResponseType.bytes, // <--- HERE IS CHANGE
            method: 'GET',
            headers: <String, dynamic>{'Accept': 'application/json'},
            extra: _extra,
            baseUrl: baseUrl),
        data: _data);
  .....
  }

Describe alternatives you've considered

Annotation per field

  @GET("/file/{fileId}")
  @Headers({"Accept": "application/json"})
  @ResponseType(ResponseType.bytes)
  Future<List<int>> getFile(
      @Path("fileId") int fileId
  );

One example for built_value immutable models

We only have one example with json_serializable, it will be great if we have another example with built_value
I already created an example for this case, if you like I would create a new pull request, just tell me how.

Auto generic type casting does not work in certain cases

The following code:

  @GET("/user/teacher/list/grouped")
  @Headers({
    "Accept": "application/json",
  })
  Future<Map<String, List<Teacher>>> teacherListGet({
    @Header("Authorization") String token
  });

generates

  @override
  teacherListGet({token}) async {
  ...
  var value = Map<String, List<Teacher>>.fromJson(_result.data);
  //The class 'Map' doesn't have a constructor named 'fromJson'.
  return Future.value(value);

expected:

 @override
  teacherListGet({token}) async {
  ...
  var value = (_result.data as Map<String, dynamic>)
    .map((k, v) =>
      MapEntry(
        k, (v as List)
          .map((i) => Teacher.fromJson(i))
          .toList()
      )
    );  
  return Future.value(value);

Do you have opinions on supporting the annotations server side?

Is your feature request related to a problem? Please describe.
There are some frameworks (e.g. micronaut in the jvm space) that let you define an interface with a set of annotations that can be used on the client and on the server side. I was curious if you were interested in this?
I am not talking about turning retrofit into a server side framework but maybe extracting the annotations into it's own package (e.g. GET etc) and then have a second package that offers something similar to RestAPI that actually works on the server. I would be down to working on the server side but figured I'd check out your interest in cooperating on something like this.
This would have the nice benefit that if you have a backend and frontend app in dart you can use shared api declarations and use them on both sides.

Sorry for braking the issue template. And thank you for making retrofit, it really is a joy to use!

Option to Cancel Request

There is search feature in my project.
This feature do call request every I type a letter.
How to cancel previous requests..??

thanks.

A way to handle non-200 status codes again

It seems that the Dio "Response" type is no longer supported as return type (the generated code tries to call Response.fromJson() which does not exist). How are server responses with a status other than "200" supposed to be handled from version 0.3.0 onwards?

File does not upload when using @Field() File avatar, but works when using @Field() MultipartFile avatar and passing: MultipartFile.fromFileSync(avatar.path)

Describe the bug
File does not upload when using @field() File avatar, but works when using @field() MultipartFile avatar and passing: MultipartFile.fromFileSync(avatar.path)

To Reproduce
pass a file to retrofit api method @field() File avatar; parameter.

Expected behavior
passing File to retrofit should upload the file to the server. but it doesn't.

Smartphone (please complete the following information):

  • Device: Emulator
  • OS: Android Oreo 28 API Level
  • Version 28 API Level

retrofit and json_serializable

Hey! Sorry if raising an issue isn't the best way to talk about this, but I wasn't sure where else I could ask.

I see in your example you are using json_serializable to create a Result class, but that isn't used anywhere yet. Do you know if it's possible to use the Result class as the generic type for the Response class? Or does retrofit only work in returning strings? I'm trying to do that in my project with my own api result class and I get the error:

I/flutter ( 9176): DioError [DioErrorType.DEFAULT]: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'LaunchPage'#0      Dio._assureResponse (package:dio/src/dio.dart:1006:9)

@SerializedName option (or similar)

Is your feature request related to a problem? Please describe.
The auto generated toJson functions are great, but they require your fields to be named exactly as the API keys. It'd be great if we could annotate our fields with @SerializedName (or something similar) to provide a different key to look for while maintaining naming conventions on our end.

Describe the solution you'd like

@JsonSerializable()
class Task {
  String id;
  @SerializedName("Name") String name;
  @SerializedName("TaskAvatar") String avatar;
  String createdAt;

  Task({this.id, this.name, this.avatar, this.createdAt});

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

for an API response which looks like:

{
    "id": "123123",
    "Name": "Bobby",
    "TaskAvatar": "Avatar",
    "createdAt": "111"
}

Wrong line in example code

The line

static final RestClient instance([Dio dio]) = _RestClient(dio);

does not compile. The keyword "final" can't be used and the "equals" operation does not make sense (method on LHS, constructor on RHS).

return type Future<Void> support

I envolpe server reponse use interceptor , after that final data model is null. Just like in retrofit , I want to return void , but generate code still require void.fromJson method

No MultiPart FormData support

Any usage of a MultiPart aka Dio's FormData option, fails when supplying this as the Body() argument.

Here's an example block of the generated code:

Future<Response> uploadPhoto( int userId, FormData formData) async {
    final queryParameters = <String, dynamic>{};
    final data = <String, dynamic>{}; <------- my FormData should be set here
    data.addAll(formData ?? {}); <----- this is wrong
    return _dio.request<dynamic>('users/$userId/photos',
        queryParameters: queryParameters,
        options: RequestOptions(
            method: 'POST', headers: {'Content-Type': 'multipart/form-data'}),
        data: data); 
}

Consider adding an annotation for FormData that then sets the data: <formdata> value appropriately on the request or adjusting the Body annotation to take into account the argument class type.

Edit: I've created a PR for this change (#8)

Optional and optional named query parameters are not handled properly

Description
This is arguably a feature request, however it can be considered a bug as well as the optional and optional named parameters are accepted.

To Reproduce
Implement something like:

import 'package:dio/dio.dart';
import 'package:retrofit/http.dart';
import 'package:meta/meta.dart';

part 'my_client.g.dart';

@RestApi()
abstract class MyClient {

  factory MyClient(Dio dio) = _MyClient;

  @GET("content/api")
  Future<Result> namedExample(@Query("apikey") String apiKey,
      @Query("scope") String scope,
      @Query("type") String type, {
        @required @Query("from") int from
      });

  @GET("content/api")
  Future<Result> optionalNamedExample(@Query("apikey") String apiKey,
      @Query("scope") String scope,
      @Query("type") String type, {
        @Query("from") int from
      });

  @GET("content/api")
  Future<Result> optionalExample(@Query("apikey") String apiKey,
      @Query("scope") String scope,
      @Query("type") String type, [
        @Query("from") int from
      ]);
}

Expected behavior
In the case of namedExample, I would expect a null assertion to occur for the from parameter as of the existence of the @required annotation, however this is not the case.

In the case of optionalNamedExample, I would expect no null assertion for the from parameter as @required is not present. This is correct, however I would expect it to only be added to the queryParameters if from is not null (I think Square's Retrofit handles this). This could be done simply with Dart 2.3's control flow collections by doing:

final queryParameters = <String, dynamic>{
      'apikey': apiKey,
      'scope': scope,
      'type': type,
      if(from != null) 'from': from
    };

In the case of optionalExample, I would expect the same sort of behaviour as above.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.