Skip to content

Commit

Permalink
Use Flutter compute for parsing models (#408)
Browse files Browse the repository at this point in the history
* Format code

* Fix pubspec

* Generate compute calls

* Add tests

* Update parser name

* Format code

* Fix merge conflict

* Add examples for post methods

* Define a new parser annotation for using the Flutter  function

* Increment to version 3.0.0 with documentation for implementing the FlutterCompute parser option

* Upgrade to version 3.0.0

* Use serialize and deserialize functions

* Update documentation to match implementation better

* Fix generating compute for requests body

* Handle queries

* Add tests for FlutterCompute parser

* Update documentation

* Ignore computing unserialised objects

* Fix nullable bodies and add warnings for spawning for collections

* Serialise lists

* Handle lists for objects in one function

* Fix casting lists

* Fix tests

* Remove failed check

* Revert "Remove failed check"

This reverts commit 91b8037.

* Fix tests

* Update CHANGELOG

Co-authored-by: Trevor Wang <[email protected]>
  • Loading branch information
via-guy and trevorwang authored Nov 13, 2021
1 parent 330e2d3 commit df374c7
Show file tree
Hide file tree
Showing 10 changed files with 1,020 additions and 112 deletions.
43 changes: 32 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,14 @@ If you want to parse models on a separate thread, you can take advantage of the
For each model that you use you will need to define 2 top-level functions:
```dart
FutureOr<Task> deserializeTask(Map<String, dynamic> json);
FutureOr<Map<String, dynamic>> serializeTask(Task object);
FutureOr<dynamic> serializeTask(Task object);
```

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

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

E.g.
Expand All @@ -235,40 +242,54 @@ E.g.
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);
Map<String, dynamic> serializeTask(User object) => object.toJson();
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.
It is recommended to use just a single object, if possible, as then only one background thread will be spawned to perform the computation. If you use a list or a map it will spawn a thread for each element.
Avoid using Map values, otherwise multiple background isolates will be spawned to perform the computation, which is extremely intensive for Dart.

```dart
abstract class RestClient {
factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
// BAD
@GET("/tasks")
Future<List<Task>> getTasks(); // BAD
Future<Map<String, Task>> getTasks();
@POST("/tasks")
Future<void> updateTasks(Map<String, Task> tasks);
@GET("/tasks_list")
Future<TaskList> getTasksList(); // GOOD
// GOOD
@GET("/tasks_names")
Future<TaskNames> getTaskNames();
@POST("/tasks_names")
Future<void> updateTasks(TaskNames tasks);
}
TaskList deserializeTaskList(Map<String, dynamic> json) => TaskList.fromJson(json);
TaskNames deserializeTaskNames(Map<String, dynamic> json) => TaskNames.fromJson(json);
@JsonSerializable
class TaskList {
const TaskList({required this.tasks});
class TaskNames {
const TaskNames({required this.tasks});
final List<Task> tasks;
final Map<String, Task> taskNames;
factory TaskList.fromJson(Map<String, dynamic> json) => _$TaskListFromJson(json);
factory TaskNames.fromJson(Map<String, dynamic> json) => _$TaskNamesFromJson(json);
}
```

Expand Down
13 changes: 12 additions & 1 deletion annotation/lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,26 @@ enum Parser {
/// Each model class must define a top-level function, taking the form
/// ```
/// FutureOr<T> deserializeT(Map<String, dynamic> json);
/// FutureOr<Map<String, dynamic>> serializeTask(T object);
/// FutureOr<dynamic> serializeTask(T object);
/// ```
///
/// If you want to handle lists of objects, either as return types or parameters, you should provide List counterparts.
///
/// ```
/// FutureOr<List<T>> deserializeTList(Map<String, dynamic> json);
/// FutureOr<dynamic> serializeTList(List<T> objects);
/// ```
///
/// E.g.
/// ----
/// _In file user.dart_
/// ```
/// User deserializeUser(Map<String, dynamic> json) => User.fromJson(json);
/// List<User> deserializeUserList(List<Map<String, dynamic>> json) =>
/// json.map((e) => User.fromJson(e)).toList();
/// Map<String, dynamic> serializeUser(User object) => object.toJson();
/// List<Map<String, dynamic>> serializeUserList(List<User> objects) =>
/// objects.map((e) => e.toJson()).toList();
///
/// @JsonSerializable()
/// class User {
Expand Down
2 changes: 1 addition & 1 deletion example_dartmapper/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies:
dev_dependencies:
test: 1.16.5
retrofit_generator:
build_runner: ^1.12.2
build_runner: ^2.0.1
json_serializable: ^4.0.3
mock_web_server:

Expand Down
74 changes: 70 additions & 4 deletions flutter_example/lib/example.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,80 @@
import 'mock_adapter.dart';
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart' hide Headers;
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';

import 'mock_adapter.dart';

part 'example.g.dart';

@RestApi(baseUrl: "http://baidu.com")
User deserializeUser(Map<String, dynamic> json) => User.fromJson(json);
List<User> deserializeUserList(List<Map<String, dynamic>> json) =>
json.map((e) => User.fromJson(e)).toList();
Map<String, dynamic> serializeUser(User object) => object.toJson();
List<Map<String, dynamic>> serializeUserList(List<User> objects) =>
objects.map((e) => e.toJson()).toList();

@JsonSerializable()
class User {
User({required this.id});

final String id;

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

@RestApi(baseUrl: "http://baidu.com", parser: Parser.FlutterCompute)
abstract class RestClient {
factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

@GET('/tags')
Future<List<String>> getTags({@DioOptions() Options? options});
@GET('/tagsNullable')
Future<List<String>?> getTagsNullable({@DioOptions() Options? options});
@GET('/tagByKey')
Future<Map<String, String>> getTagByKey({@DioOptions() Options? options});
@GET('/tagByKeyNullable')
Future<Map<String, String>?> getTagByKeyNullable(
{@DioOptions() Options? options});
@GET('/tag')
Future<String> getTag({@DioOptions() Options? options});
@GET('/tagNullable')
Future<String?> getTagNullable({@DioOptions() Options? options});

@GET('/users')
Future<List<User>> getUsers({@DioOptions() Options? options});
@GET('/usersNullable')
Future<List<User>?> getUsersNullable({@DioOptions() Options? options});
@GET('/userByKey')
Future<Map<String, User>> getUserByKey({@DioOptions() Options? options});
@GET('/userByKeyNullable')
Future<Map<String, User>?> getUserByKeyNullable(
{@DioOptions() Options? options});
@GET('/usersByKey')
Future<Map<String, List<User>>> getUsersByKey(
{@DioOptions() Options? options});
@GET('/user')
Future<User> getUser({@DioOptions() Options? options});
@GET('/userNullable')
Future<User?> getUserNullable({@DioOptions() Options? options});

@PATCH('/user/{user}')
Future<void> patchUser(
{@Query('u') required User user, @DioOptions() Options? options});
@PATCH('/userMap/{user}')
Future<void> patchUserMap(
{@Queries() required User user, @DioOptions() Options? options});

@POST('/users')
Future<void> postUsers(
{@Body() required List<User> users, @DioOptions() Options? options});
@POST('/user')
Future<void> postUser(
{@Body() required User user, @DioOptions() Options? options});
@POST('/userNullable')
Future<void> postUserNullable(
{@Body() required User? user, @DioOptions() Options? options});
}

void test() {
Expand All @@ -23,7 +89,7 @@ void test() {
handler.next(options);
}));
final api = RestClient(dio, baseUrl: MockAdapter.mockBase);
api.getTags().then((it) {
api.getUsers().then((it) {
print(it.length);
});
}
Loading

0 comments on commit df374c7

Please sign in to comment.