- Main isolate: Takes care of the UI and reading and writing data to the drift DB.
- Second isolate: Takes care of syncing the local DB to the backend. Drift migrations will also run in this isolate.
Even for reading data for UI you may want to use separate isolate so that data serialization does not affect rendering. Depending on your data size it may not have noticeable effect.
We are currently not using rxdart and therefore no BehaviorSubject.
My personal view is that rxdart is close to standard Dart library, so I donāt have reservations when introducing that. Similarly I donāt feel like adding package:collection is a 3rd party dependency. Riverpod does a bit of magic to make streams synchronous, so perhaps it wonāt be compatible with BehaviourSubject.
Can you please share your example for cupertino_http?
This will be helpful if you ever would like to monitor traffic using e.g. Proxyman
Using this adapter the initialization of our graphql client looks as follows:
void initializeClient() {
final authLink = AuthLink(
getToken: _getToken,
);
Client? httpClient;
if (Platform.isIOS) {
final config = URLSessionConfiguration.defaultSessionConfiguration()
..cache = URLCache.withCapacity(memoryCapacity: 2 * 1024 * 1024)
..httpAdditionalHeaders = {
'User-Agent': 'flutter',
// any extra
};
httpClient = CupertinoClient.fromSessionConfiguration(config);
}
final baseLink = HttpLink(
baseUrl,
httpResponseDecoder: isolateHttpResponseDecoder,
defaultHeaders: {
'Content-Type': 'application/json',
// any extra
},
httpClient: httpClient,
);
final wsLink = gql_ws.TransportWebSocketLink(
gql_ws.TransportWsClientOptions(
socketMaker: gql_ws.WebSocketMaker.generator(() async {
if (Platform.isIOS) {
return AdapterWebSocketChannel(
CupertinoWebSocket.connect(
Uri.parse(baseWs),
protocols: ['graphql-transport-ws'],
config: URLSessionConfiguration.defaultSessionConfiguration(),
),
);
} else {
return ws.WebSocketChannel.connect(
Uri.parse(baseWs),
protocols: ['graphql-transport-ws'],
);
}
}),
graphQLSocketMessageDecoder: customDecoder,
retryWait: (retries) {
_log.i('Retrying websocket connection in 5 sec, attempt $retries');
return Future.delayed(const Duration(seconds: 5));
},
keepAlive: const Duration(seconds: 30),
connectionParams: () async {
var token = await _getToken();
return {
'headers': {
'Authorization': token,
'Hasura-Client-Name': 'flutter',
'Content-Type': 'application/json',
},
};
},
),
);
final httpsLink = authLink.concat(baseLink);
final link = Link.split(
(request) {
return request.isSubscription;
},
wsLink,
httpsLink,
);
_client = GraphQLClient(
link: link,
cache: GraphQLCache(
store: InMemoryStore(),
),
defaultPolicies: DefaultPolicies(
watchQuery: _policies,
query: _policies,
mutate: _policies,
subscribe: _policies,
watchMutation: _policies,
),
);
...
}
And isolate-converters
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:gql_websocket_link/gql_websocket_link.dart';
import 'package:http/http.dart' as http;
Future<Map<String, dynamic>?> isolateHttpResponseDecoder(
http.Response httpResponse,
) async =>
Isolate.run(
() => const Utf8Decoder().fuse(const JsonDecoder()).convert(httpResponse.bodyBytes) as Map<String, dynamic>?,
);
// ignore: prefer_function_declarations_over_variables
final GraphQLSocketMessageDecoder customDecoder =
(dynamic message) async => await compute(jsonDecode, message as String) as Map<String, dynamic>;
What is e.g. a ācentral placeā
In my case itās a class (cubit in that case) that lives in a global scope of the app and has access to my data repositories and gets notified about lifecycle events. When the lifecycle changes to paused it stops all websocket connections, and when it returns to active it starts them again.
Should I query the backend for entries with a created or updated timestamp newer than the ālast updated timestampā?
Yes, thatās one of the strategies to only fetch data that has changed in the meantime. Imho itās better to offload this work to the client, so that client knows what is the most recent timestamp and just asks for anything that is newer. Probably you may want to have a way to synchronize all data occassionally. Also you need to consider cases of deleting data. In practice the easiest is to introduce soft-deletes so that when some entity is deleted, you get notified about it because its updated_at
changed as well as is_deleted
flag.
With āobserve the app lifecycleā you mean
By app lifecycle I mean this AppLifecycleListener class - widgets library - Dart API
Can you give an example or some references how to implement such a persistent event queue?
My presentation here at 25:00: From Network Failures to Offline Success: A Journey of Visible App - droidcon
Elianās part in this talk after about 15:00 Going offline first with Flutter: Best practices - droidcon