-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ferry): add IsolateClient (#405)
* feat(ferry): add IsolateClient * chore(pokemon_explorer): format * feat(ferry): add more methods to IsolateClient * feat(ferry): refactor IsolateClient, add more commands * feat(ferry): more IsolateClient features * chore(ferry): format * feat(ferry): refactor single response commands * docs(ferry): add documentation for IsolateClient in website * test(ferry): add tests * chore(ferry): format * refactor(ferry): cleanup request response communication * chore(ferry): format * refactor(ferry): refactore handleCommand * refactor(ferry): refactore handleCommand * docs(ferry): add example for token refresh over isolate * refactor(ferry): make params map non null in pokemon example * chore(ferry): format * chore(ferry): remove ignored lint, add unawaited * refactor(ferry): use a generic type parameter for the init params instead of a Map<String,dynamic> * docs(ferry): mention isolates in README * chore(release): publish packages - ferry@0.11.1 - ferry_exec@0.1.6 - ferry_flutter@0.6.1+2 - ferry_generator@0.6.0+1 - ferry_cache@0.6.2+1 - ferry_test_graphql@0.1.5-dev.4 Co-authored-by: Martin Kamleithner <martin+kamleithner@diagnosia.com>
- Loading branch information
1 parent
21c80b5
commit 35a434c
Showing
44 changed files
with
2,572 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
--- | ||
id: isolates | ||
title: Running the client in a separate isolate | ||
sidebar_label: Isolates | ||
--- | ||
|
||
## Running the client in a separate isolate | ||
|
||
The default setup should be sufficient for most use cases. | ||
However, normalization and denormalization of big, nested responses can be quite computation heavy | ||
and can lead to dropped frames on frontends. | ||
|
||
Ferry can help you run your graphql-related code on a separate isolate so that the UI thread does | ||
not get blocked when executing big queries. | ||
|
||
Since the Ferry `Client` already is Stream based, the differences in the API between the default `Client` and the `IsolateClient` are minimal. | ||
If you are just using the `request()` method of the `Client` or `Operation` widgets of ferry_flutter, | ||
then the `IsolateClient` is a drop-in replacement! | ||
|
||
The `IsolateClient` does not, however, offer direct access to the `Cache` object, but instead offers | ||
indirect access via methods like `readQuery()` or `writeFragment()`. These methods are asynchronous, | ||
as all communication over isolates is asynchrounous. | ||
|
||
### Setup | ||
|
||
To run Ferry on a separate Isolate, use the static `IsolateClient.create<InitParams>()` method. | ||
This method receives three parameters and one generic type parameter: | ||
|
||
- `<InitParams>`: this generic type parameter defines the type of the parameters object which is used | ||
to initialze the client. If you don't want to write a separate class for this, you can just use `Map<String, dynamic>` here. | ||
If you don't need parameters to initialize the Client, you can also use the `Null` type | ||
- `initClient`: This function is called on a separate isolate and is responsible for creating the real Ferry `Client`. | ||
This must be a top-level or static function (otherwise it will not be possible to send it to another isolate). | ||
When migrating from the standard setup to to isolate-based setup, move the initialization of the `Client` class | ||
to this function. initClient is also called with a `SendPort` parameter, which can be used to establish custom | ||
communication between the two isolates. You can use this for example for synchronizing authentication tokens when | ||
the are refreshed. | ||
- `params`: a type that contains the parameters used to initialize the client, which will be passed to `initClient`. | ||
Use this to pass endpoint urls, the path to the cache or a authentication token to the other isolate. | ||
- `messageHandler` (optional): a function which will be invoked on the main isolate if you send objects | ||
through the `SendPort` passed to `initClient`. If you want to establish two-way communication, create a new `ReceivePort` | ||
in `InitClient` and send its `SendPort` over the `SendPort` which is the third parameter of `initClient`. `messageHandler` will | ||
be called with the `sendPort` and this can be used to send custom messages from the main isolate the the ferry isolate. | ||
|
||
A example can be found in `examples/pokemon_explorer`. In this project. The same App can be run with ferry | ||
on the main isolate (`main.dart`) or a separate isolate (`main_isolate.dart`). | ||
|
||
### Caveats | ||
|
||
By default, you will not be able to run code that uses `MethodChannel`s underneath in the new isolate. | ||
This means: | ||
- no SharedPreferences | ||
- no Hive.initFlutter (Hive.init works, though, also in flutter apps.) | ||
- no path_provider | ||
|
||
If you want to use Hive for the cache, there is a workaround implemented in the pokemon_explorer example app: | ||
- call ` (await getApplicationDocumentsDirectory()).path` on the main isolate and pass the path to the ferry isolate in the `params` map | ||
- use `Hive.init` with the given path instead of `Hive.initFlutter()`. Note that Hive is single threaded and you cannot use the same box on multiple isolates, this would lead to data corruption. | ||
|
||
If you have an authenticated graphql api and need the auth token on both the main isolate and the ferry isolate, consider one of the following solutions: | ||
|
||
- use a persistence library that can be used across different isolates like `drift` or `isar`. | ||
- use a persistence library like `hive`, which does not support multiple isolate, can be used on non-main isolates also. However, | ||
With this approach, the Hive box needs to be opened on the ferry isolate only, the main isolate will not be able the read the auth token. | ||
- use the `SendPort` in the `InitClient` function that runs on the ferry isolate to establish communication between | ||
the main isolate and ferry. For example you can send the new authentication token via that sendPort. | ||
The main isolate would receive it in its `messageHandler` and could persist it, for example via `SharedPreferences`. | ||
You can also establish a two-way communication be creating a `ReceivePort` in the `InitClient` function and send its sendport to | ||
the main isolates messagehandler. | ||
|
||
Here's an example on how to wire up SharedPreferences to store | ||
the auth token on the main isolate, refresh it on the ferry isolate when needed, and | ||
send the new token the the main isolate for shared_preferences to store: | ||
|
||
https://gist.github.com/knaeckeKami/b11ad83e4b69aa44638815d1471c2ba3 | ||
|
||
If you implement another approach, feel free to send me a sample code so I can add it here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
PODS: | ||
- Flutter (1.0.0) | ||
- path_provider_ios (0.0.1): | ||
- Flutter | ||
|
||
DEPENDENCIES: | ||
- Flutter (from `Flutter`) | ||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) | ||
|
||
EXTERNAL SOURCES: | ||
Flutter: | ||
:path: Flutter | ||
path_provider_ios: | ||
:path: ".symlinks/plugins/path_provider_ios/ios" | ||
|
||
SPEC CHECKSUMS: | ||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a | ||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 | ||
|
||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 | ||
|
||
COCOAPODS: 1.11.3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
examples/pokemon_explorer/ios/Runner.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
import 'package:ferry/ferry_isolate.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:get_it/get_it.dart'; | ||
import 'package:ferry/ferry.dart'; | ||
|
||
import './src/client.dart'; | ||
import './src/app.dart'; | ||
|
||
const apiUrl = "https://pokeapi.dev"; | ||
|
||
void main() async { | ||
final client = await initClient(); | ||
GetIt.I.registerLazySingleton<Client>(() => client); | ||
GetIt.I.registerLazySingleton<TypedLink>(() => client); | ||
runApp(App()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import 'package:ferry/ferry.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:get_it/get_it.dart'; | ||
import 'package:pokemon_explorer/src/app.dart'; | ||
|
||
import 'src/client_isolate.dart'; | ||
|
||
// example for running the client in a separate isolate | ||
void main() async { | ||
WidgetsFlutterBinding.ensureInitialized(); | ||
final client = await initIsolateClient(); | ||
GetIt.I.registerLazySingleton<TypedLink>(() => client); | ||
runApp(App()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.