Skip to content

Commit

Permalink
Run response mapper in isolate
Browse files Browse the repository at this point in the history
  • Loading branch information
scrfrk committed Mar 2, 2023
1 parent a3433c9 commit 4e2c1b8
Show file tree
Hide file tree
Showing 15 changed files with 1,175 additions and 401 deletions.
7 changes: 7 additions & 0 deletions ISOLATE_PERFORMANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Isolate parsing performance

**Parsing in isolate took 2400-2500 microseconds.**

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

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.
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
8 changes: 5 additions & 3 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.dart'
if (dart.library.html) 'package:dash_kit_network/src/isolate_manager/isolate_manager_web.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,7 +15,7 @@ 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
/// Component for communication with an API. Includes functionality
/// for updating tokens if they expired.
// ignore_for_file: long-parameter-list
abstract class ApiClient {
Expand All @@ -39,6 +40,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 +229,7 @@ abstract class ApiClient {
final performRequest = (tokenPair) async {
final response = await _createRequest(params, tokenPair);

return params.responseMapper.call(response);
return await isolateManager.send(response, params.responseMapper);
};

if (params.isAuthorisedRequest) {
Expand Down
56 changes: 56 additions & 0 deletions lib/src/isolate_manager/isolate_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'dart:async';

import 'package:dash_kit_network/dash_kit_network.dart';

// ignore: prefer-match-file-name
abstract class IsolateManagerInterface {
Future<void> start();

void stop();

Future<FutureOr<T>> send<T>(
Response<dynamic> response,
FutureOr<T> Function(Response<dynamic>) mapper,
);
}

class Task<T> {
const Task(
this.id,
this.response,
this.mapper,
);

final String id;
final Response<dynamic> response;
final T Function(Response<dynamic>) mapper;

@override
String toString() {
return 'Task{id: $id, response: $response, mapper: $mapper}';
}
}

class Result<T> {
const Result({
required this.id,
this.result,
this.error,
});

final String id;
final T? result;
final dynamic error;

@override
String toString() {
return 'Result{id: $id, result: $result, error: $error}';
}
}

enum IsolateStatus {
created,
initializing,
initialized,
stopped,
}
Loading

0 comments on commit 4e2c1b8

Please sign in to comment.