Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run response mapper in isolate #44

Merged
merged 1 commit into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions ISOLATE_PERFORMANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Isolate parsing performance

Parsing JSON on an isolate allows you to perform the operation in the background without blocking the main thread, which can be useful for large JSON files or when you need to perform other operations while parsing.

**Parse on main thread took 20-60 microseconds.**

**Parse using IsolateManager took 2400-2500 microseconds.**

**Parse using Isolate.run took 110000-130000 microseconds.**

```dart
Future<T> executeInIsolate<T>({
required T Function(Response) mapper,
required Response response,
}) {
return Isolate.run(() => mapper.call(response));
}
```

These results were obtained :

```dart
final performRequest = (tokenPair) async {
final response = await _createRequest(params, tokenPair);

final timer = Stopwatch()..start();
params.responseMapper.call(response);
log('Sync mapper: ${timer.elapsedMicroseconds}');
timer.stop();

final timer2 = Stopwatch()..start();
final result = await isolateManager.send(response, params.responseMapper);
log('isolateManager.send: ${timer2.elapsedMicroseconds}');
timer2.stop();

final timer3 = Stopwatch()..start();
await executeInIsolate(mapper: params.responseMapper, response: response);
log('execute in Isolate.run: ${timer3.elapsedMicroseconds}');
timer3.stop();

return result;
};
```
12 changes: 10 additions & 2 deletions example/lib/api/application_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class ApplicationApi extends ApiClient {
dio: dio,
);

Future<UsersResponseModel> getUserList() => get(
path: 'users',
Future<UsersResponseModel> getUserList({int? page}) => get(
path: 'users${page != null ? '?page=$page' : ''}',
responseMapper: response_mappers.users,
validate: false,
receiveTimeout: Duration(seconds: 30),
Expand All @@ -30,4 +30,12 @@ class ApplicationApi extends ApiClient {
return Future.error(error!);
});
}

Future<void> getErrorRequest() => get(
path: 'users/23',
responseMapper: response_mappers.users,
validate: false,
receiveTimeout: Duration(seconds: 30),
sendTimeout: Duration(seconds: 30),
);
}
1 change: 1 addition & 0 deletions example/lib/api/models/errors/error_response_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class ResponseErrorModel extends Error {
factory ResponseErrorModel.fromJson(Map<String, dynamic> json) =>
_$ResponseErrorModelFromJson(json);

@JsonKey(defaultValue: {})
final Map<String, List<String>> errors;

Map<String, dynamic> toJson() => _$ResponseErrorModelToJson(this);
Expand Down
16 changes: 8 additions & 8 deletions example/lib/api/models/errors/error_response_model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions example/lib/api/models/login_response_model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 8 additions & 9 deletions example/lib/api/models/user_response_model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 6 additions & 7 deletions example/lib/api/models/users_response_model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 61 additions & 3 deletions example/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class _MyHomePageState extends State<MyHomePage> {
bool isLoading = false;
StreamSubscription? subscription;
List<UserResponseModel> users = [];
int currentPage = 1;

@override
void initState() {
Expand All @@ -61,9 +62,36 @@ class _MyHomePageState extends State<MyHomePage> {
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: isLoading ? _getProgressWidget() : getUsersWidget(users),
),
body: LayoutBuilder(builder: (context, constraints) {
return isLoading
? _getProgressWidget()
: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: constraints.maxHeight * 0.8,
child: getUsersWidget(users),
),
Spacer(),
MaterialButton(
child: Text("Get next users"),
padding: EdgeInsets.all(10),
color: Colors.blueAccent,
onPressed: () => _loadNextPage(),
minWidth: constraints.maxWidth * 0.9,
),
SizedBox(height: 8),
MaterialButton(
child: Text("Error request"),
padding: EdgeInsets.all(10),
color: Colors.red,
onPressed: _loadErrorPage,
minWidth: constraints.maxWidth * 0.9,
),
Spacer(),
],
);
}),
floatingActionButton: FloatingActionButton(
onPressed: _loadUserList,
tooltip: 'Load a user list',
Expand Down Expand Up @@ -152,4 +180,34 @@ class _MyHomePageState extends State<MyHomePage> {

setState(() => isLoading = false);
}

Future<void> _loadNextPage() async {
await subscription?.cancel();

setState(() => isLoading = true);

try {
currentPage++;
final response = await widget.apiClient.getUserList(page: currentPage);
users.addAll(response.data);
} catch (e) {
showErrorDialog();
}

setState(() => isLoading = false);
}

Future<void> _loadErrorPage() async {
await subscription?.cancel();

setState(() => isLoading = true);

try {
await widget.apiClient.getErrorRequest();
} catch (e) {
showErrorDialog();
}

setState(() => isLoading = false);
}
}
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ packages:
path: ".."
relative: true
source: path
version: "3.5.2"
version: "3.6.0"
dio:
dependency: transitive
description:
Expand Down
14 changes: 10 additions & 4 deletions lib/src/api_client.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'dart:async';
import 'dart:io';

import 'package:dash_kit_network/src/error_handler_delegate.dart';
import 'package:dash_kit_network/src/exceptions/network_connection_exception.dart';
import 'package:dash_kit_network/src/exceptions/refresh_tokens_delegate_missing_exception.dart';
import 'package:dash_kit_network/src/exceptions/request_error_exception.dart';
import 'package:dash_kit_network/src/isolate_manager/isolate_manager_io/isolate_manager.dart'
if (dart.library.html) 'package:dash_kit_network/src/isolate_manager/isolate_manager_web/isolate_manager.dart';
import 'package:dash_kit_network/src/models/api_environment.dart';
import 'package:dash_kit_network/src/models/http_header.dart';
import 'package:dash_kit_network/src/models/request_params.dart';
Expand All @@ -14,8 +15,9 @@ import 'package:dash_kit_network/src/refresh_tokens_delegate.dart';
import 'package:dash_kit_network/src/token_manager.dart';
import 'package:dio/dio.dart';

/// Componet for communication with an API. Includes functionality
/// for updating tokens if they expired.
/// Component for communication with an API.
///
/// Includes functionality for updating tokens if they are expired.
// ignore_for_file: long-parameter-list
abstract class ApiClient {
ApiClient({
Expand All @@ -39,6 +41,7 @@ abstract class ApiClient {
dio.options.baseUrl = environment.baseUrl;
}

final isolateManager = IsolateManager()..start();
final ApiEnvironment environment;
final Dio dio;
final List<HttpHeader> commonHeaders;
Expand Down Expand Up @@ -227,7 +230,10 @@ abstract class ApiClient {
final performRequest = (tokenPair) async {
final response = await _createRequest(params, tokenPair);

return params.responseMapper.call(response);
return await isolateManager.sendTask(
response: response,
mapper: params.responseMapper,
);
};

if (params.isAuthorisedRequest) {
Expand Down
Loading