Skip to content

Commit aed6ad6

Browse files
committed
improve isolate runner, deserialization
1 parent 7a55b33 commit aed6ad6

22 files changed

+166
-147
lines changed

example/lib/main.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ void main() async {
2828

2929
print('Using temporary directory: ${_dir.path}');
3030

31-
await container.read(initializeWith(adapterProviders).future);
31+
container.read(adapterProviders.notifier).state = adapterProvidersMap;
32+
await container.read(initializeAdapters.future);
3233
container.users.logLevel = 2;
3334
container.tasks.logLevel = 2;
3435

example/lib/main.data.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/lib/models/task.g.dart

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/lib/models/user.g.dart

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/builders/adapter_builder.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,13 @@ mixin _\$${className}Adapter on Adapter<$className> {
256256
Map<String, RelationshipMeta> get relationshipMetas => _k${className}RelationshipMetas;
257257
258258
@override
259-
$className deserialize(map, {String? key}) {
259+
$className deserializeLocal(map, {String? key}) {
260260
map = transformDeserialize(map);
261261
return internalWrapStopInit(() => $fromJson, key: key);
262262
}
263263
264264
@override
265-
Map<String, dynamic> serialize(model, {bool withRelationships = true}) {
265+
Map<String, dynamic> serializeLocal(model, {bool withRelationships = true}) {
266266
final map = $toJson;
267267
return transformSerialize(map, withRelationships: withRelationships);
268268
}

lib/builders/data_library_builder.dart

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,9 @@ class DataExtensionBuilder implements Builder {
8686

8787
// imports
8888

89-
final isFlutter = await isDependency('flutter', b);
90-
final hasPathProvider = await isDependency('path_provider', b);
9189
final hasFlutterRiverpod = await isDependency('flutter_riverpod', b) ||
9290
await isDependency('hooks_riverpod', b);
9391

94-
final flutterFoundationImport = isFlutter
95-
? "import 'package:flutter/foundation.dart' show kIsWeb;"
96-
: '';
97-
final pathProviderImport = hasPathProvider
98-
? "import 'package:path_provider/path_provider.dart';"
99-
: '';
10092
final riverpodFlutterImport = hasFlutterRiverpod
10193
? "import 'package:flutter_riverpod/flutter_riverpod.dart';"
10294
: '';
@@ -130,13 +122,11 @@ ${classes.map((clazz) => ' Adapter<${clazz['className']}> get ${clazz['classNam
130122
// ignore_for_file: directives_ordering, top_level_function_literal_block, depend_on_referenced_packages
131123
132124
import 'package:flutter_data/flutter_data.dart';
133-
$flutterFoundationImport
134-
$pathProviderImport
135125
$riverpodFlutterImport
136126
137127
$modelImports
138128
139-
final adapterProviders = <String, Provider<Adapter<DataModelMixin>>>{
129+
final adapterProvidersMap = <String, Provider<Adapter<DataModelMixin>>>{
140130
${classes.map((clazz) => '\'' + clazz['type']! + '\': ' + clazz['classNameLower']! + 'AdapterProvider').join(',\n')}
141131
};
142132

lib/src/adapter/adapter.dart

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ part of flutter_data;
66
///
77
/// - Remote methods such as [_RemoteAdapter.findAll] or [_RemoteAdapter.save]
88
/// - Configuration methods and getters like [_RemoteAdapter.baseUrl] or [_RemoteAdapter.urlForFindAll]
9-
/// - Serialization methods like [_SerializationAdapter.serializeAsync]
9+
/// - Serialization methods like [_SerializationAdapter.serialize]
1010
/// - Watch methods such as [_WatchAdapter.watchOneNotifier]
1111
/// - Access to the [_BaseAdapter.core] for subclasses or mixins
1212
///
@@ -45,6 +45,8 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
4545
@visibleForTesting
4646
final LocalStorage storage;
4747

48+
@protected
49+
@visibleForTesting
4850
Database get db => storage.db;
4951

5052
bool _stopInitialization = false;
@@ -122,7 +124,7 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
122124
/// Returns all models of type [T] in local storage.
123125
List<T> findAllLocal() {
124126
final result = db.select('SELECT key, data FROM $internalType');
125-
return _deserializeFromResult(result);
127+
return deserializeFromResult(result);
126128
}
127129

128130
/// Finds many models of type [T] by [keys] in local storage.
@@ -135,13 +137,14 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
135137
final result = db.select(
136138
'SELECT key, data FROM $internalType WHERE key IN (${intKeys.map((_) => '?').join(', ')})',
137139
intKeys);
138-
return _deserializeFromResult(result);
140+
return deserializeFromResult(result);
139141
}
140142

141-
List<T> _deserializeFromResult(ResultSet result) {
143+
@protected
144+
List<T> deserializeFromResult(ResultSet result) {
142145
return result.map((r) {
143146
final map = Map<String, dynamic>.from(jsonDecode(r['data']));
144-
return deserialize(map,
147+
return deserializeLocal(map,
145148
key: r['key'].toString().typifyWith(internalType));
146149
}).toList();
147150
}
@@ -173,7 +176,7 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
173176
throw Exception("Model must be initialized:\n\n$model");
174177
}
175178
final key = model._key!.detypifyKey()!;
176-
final map = serialize(model, withRelationships: false);
179+
final map = serializeLocal(model, withRelationships: false);
177180
final data = jsonEncode(map);
178181
db.execute(
179182
'REPLACE INTO $internalType (key, data) VALUES (?, ?)', [key, data]);
@@ -183,35 +186,43 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
183186
return model;
184187
}
185188

186-
Future<R> _runInIsolate<R>(
187-
FutureOr<R> fn(ProviderContainer container)) async {
188-
final ppath = Directory(storage.path).parent.path;
189-
final ip = _internalProviders!;
189+
@protected
190+
Future<R> runInIsolate<R>(FutureOr<R> fn(Adapter adapter)) async {
191+
final _path = Directory(storage.path).parent.path;
192+
final _internalProviders = ref.read(adapterProviders)!;
193+
final _internalType = internalType;
190194

191195
return await Isolate.run(() async {
192196
late final ProviderContainer container;
193197
try {
194198
container = ProviderContainer(
195199
overrides: [
196200
localStorageProvider.overrideWith(
197-
(ref) => LocalStorage(baseDirFn: () => ppath),
201+
(ref) => LocalStorage(baseDirFn: () => _path),
198202
),
199203
],
200204
);
201205

202-
await container.read(initializeWith(ip).future);
206+
// TODO improve initializer API
207+
// set providers from outer context and start initialization
208+
container.read(adapterProviders.notifier).state = _internalProviders;
209+
await container.read(initializeAdapters.future);
203210

204-
return fn(container);
211+
final adapter = _internalProviders[_internalType]!;
212+
return fn(container.read(adapter));
205213
} finally {
206-
container.read(localStorageProvider).db.dispose();
214+
for (final provider in container.read(adapterProviders)!.values) {
215+
container.read(provider).dispose();
216+
}
217+
container.read(localStorageProvider).dispose();
207218
}
208219
});
209220
}
210221

211222
Future<void> saveManyLocal(Iterable<DataModelMixin> models,
212223
{bool notify = true}) async {
213-
final savedKeys = await _runInIsolate((container) async {
214-
final db = container.read(localStorageProvider).db;
224+
final savedKeys = await runInIsolate((adapter) async {
225+
final db = adapter.db;
215226
final savedKeys = <String>[];
216227

217228
final grouped = models.groupSetsBy((e) => e._adapter);
@@ -222,7 +233,7 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
222233
'REPLACE INTO ${adapter.internalType} (key, data) VALUES (?, ?) RETURNING key;');
223234
for (final model in e.value) {
224235
final key = model._key!.detypifyKey();
225-
final map = adapter.serialize(model, withRelationships: false);
236+
final map = adapter.serializeLocal(model, withRelationships: false);
226237
final data = jsonEncode(map);
227238
final result = ps.select([key, data]);
228239
savedKeys.add(
@@ -365,13 +376,6 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
365376
return model;
366377
}
367378

368-
// // (before -> after remote save)
369-
// // (1) if noid -> noid => `key` is the key we want to keep
370-
// // (2) if id -> noid => use autogenerated key (`key` should be the previous (derived))
371-
// // so we can migrate rels
372-
// // (3) if noid -> id => use derived key (`key` should be the previous (autogen'd))
373-
// // so we can migrate rels
374-
375379
if (model._key == null) {
376380
model._key = key ?? core.getKeyForId(internalType, model.id);
377381
if (model._key != key && key != null) {
@@ -411,9 +415,9 @@ abstract class _BaseAdapter<T extends DataModelMixin<T>> with _Lifecycle {
411415
}
412416
}
413417

414-
Map<String, dynamic> serialize(T model, {bool withRelationships = true});
418+
Map<String, dynamic> serializeLocal(T model, {bool withRelationships = true});
415419

416-
T deserialize(Map<String, dynamic> map, {String? key});
420+
T deserializeLocal(Map<String, dynamic> map, {String? key});
417421

418422
Map<String, RelationshipMeta> get relationshipMetas;
419423

lib/src/adapter/remote_adapter.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ mixin _RemoteAdapter<T extends DataModelMixin<T>> on _SerializationAdapter<T> {
210210
return model;
211211
}
212212

213-
final serialized = await serializeAsync(model);
213+
final serialized = await serialize(model);
214214
final body = json.encode(serialized);
215215

216216
final uri = baseUrl.asUri / urlForSave(model.id, params) & params;
@@ -499,7 +499,7 @@ mixin _RemoteAdapter<T extends DataModelMixin<T>> on _SerializationAdapter<T> {
499499
}
500500

501501
print('1');
502-
final data = await deserializeAsync(body as Map<String, dynamic>,
502+
final data = await deserialize(body as Map<String, dynamic>,
503503
key: label.model!._key);
504504
final model = data.model!;
505505

@@ -533,7 +533,7 @@ mixin _RemoteAdapter<T extends DataModelMixin<T>> on _SerializationAdapter<T> {
533533
}
534534

535535
// TODO test: not properly deserializing findAll with relationship references (see example app)
536-
final deserialized = await deserializeAsync(body);
536+
final deserialized = await deserialize(body);
537537

538538
if (isFindAll || (isCustom && deserialized.model == null)) {
539539
await _saveDeserialized(deserialized);

lib/src/adapter/serialization_adapter.dart

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ mixin _SerializationAdapter<T extends DataModelMixin<T>> on _BaseAdapter<T> {
55
/// as a [Map<String, dynamic>] ready to be JSON-encoded.
66
@protected
77
@visibleForTesting
8-
Future<Map<String, dynamic>> serializeAsync(T model,
8+
Future<Map<String, dynamic>> serialize(T model,
99
{bool withRelationships = true}) async {
10-
final map = serialize(model, withRelationships: withRelationships);
10+
final map = serializeLocal(model, withRelationships: withRelationships);
1111

1212
// essentially converts keys to IDs
1313
for (final key in relationshipMetas.keys) {
@@ -24,24 +24,30 @@ mixin _SerializationAdapter<T extends DataModelMixin<T>> on _BaseAdapter<T> {
2424
return map;
2525
}
2626

27-
/// Returns a [DeserializedData] object when deserializing a given [data].
28-
///
29-
@protected
30-
@visibleForTesting
31-
Future<DeserializedData<T>> deserializeAsync(Object? data,
32-
{String? key}) async {
33-
final result = DeserializedData<T>([], included: []);
34-
35-
Future<Object?> _processIdAndAddInclude(id, Adapter? adapter) async {
36-
if (id is Map && adapter != null) {
37-
final data = await adapter.deserializeAsync(id as Map<String, dynamic>);
38-
result.included
39-
..add(data.model as DataModelMixin<DataModelMixin>)
40-
..addAll(data.included);
41-
id = data.model!.id;
27+
// Future<DeserializedData<T>> deserialize(Object? data,
28+
// {String? key, async = true}) async {
29+
// final z = await runInIsolate((container) async {
30+
31+
// return <DataModelMixin>[];
32+
// });
33+
// return DeserializedData([]);
34+
// }
35+
36+
(List<DataModelMixin>, List<DataModelMixin>) _deserialize(
37+
Adapter adapter, Object? data,
38+
{String? key}) {
39+
final result = (<DataModelMixin>[], <DataModelMixin>[]);
40+
41+
Object? _processIdAndAddInclude(id, String relType) {
42+
final relAdapter = adapter.adapters[relType];
43+
if (id is Map && relAdapter != null) {
44+
final data =
45+
relAdapter._deserialize(relAdapter, id as Map<String, dynamic>);
46+
result.$2.addAll([...data.$1, ...data.$2]);
47+
id = data.$1.first.id;
4248
}
43-
if (id != null && adapter != null) {
44-
return core.getKeyForId(adapter.internalType, id);
49+
if (id != null && relAdapter != null) {
50+
return relAdapter.core.getKeyForId(relAdapter.internalType, id);
4551
}
4652
return null;
4753
}
@@ -62,7 +68,7 @@ mixin _SerializationAdapter<T extends DataModelMixin<T>> on _BaseAdapter<T> {
6268
// - process includes
6369
// - transform ids into keys to pass to the local deserializer
6470
for (final mapKey in mapIn.keys) {
65-
final metadata = relationshipMetas[mapKey];
71+
final metadata = adapter.relationshipMetas[mapKey];
6672

6773
if (metadata != null) {
6874
final relType = metadata.type;
@@ -72,15 +78,17 @@ mixin _SerializationAdapter<T extends DataModelMixin<T>> on _BaseAdapter<T> {
7278
}
7379

7480
if (metadata.kind == 'BelongsTo') {
75-
final key = await _processIdAndAddInclude(
76-
mapIn[mapKey], adapters[relType]!);
81+
// NOTE: when _process was async, a sqlite bug
82+
// appears when awaiting it (db turns to inMemory and closed)
83+
// and leaving everything sync works for now
84+
final key = _processIdAndAddInclude(mapIn[mapKey], relType);
7785
if (key != null) mapOut[mapKey] = key;
7886
}
7987

8088
if (metadata.kind == 'HasMany') {
8189
mapOut[mapKey] = [
8290
for (final id in (mapIn[mapKey] as Iterable))
83-
await _processIdAndAddInclude(id, adapters[relType]!)
91+
_processIdAndAddInclude(id, relType)
8492
].nonNulls;
8593
}
8694
} else {
@@ -90,14 +98,26 @@ mixin _SerializationAdapter<T extends DataModelMixin<T>> on _BaseAdapter<T> {
9098
}
9199

92100
// Force key only if this is a single-model deserialization
93-
final model = deserialize(mapOut,
94-
key: key != null && data.length == 1 ? key : null);
95-
result.models.add(model);
101+
final model = adapter.deserializeLocal(mapOut,
102+
key: (key != null && data.length == 1) ? key : null);
103+
result.$1.add(model as DataModelMixin);
96104
}
97105
}
98-
99106
return result;
100107
}
108+
109+
/// Returns a [DeserializedData] object when deserializing a given [data].
110+
///
111+
@protected
112+
@visibleForTesting
113+
Future<DeserializedData<T>> deserialize(Object? data,
114+
{String? key, async = true}) async {
115+
final record = async
116+
? await runInIsolate(
117+
(adapter) => adapter._deserialize(adapter, data, key: key))
118+
: _deserialize(this as Adapter, data, key: key);
119+
return DeserializedData<T>(record.$1.cast<T>(), included: record.$2);
120+
}
101121
}
102122

103123
/// A utility class used to return deserialized main [models] AND [included] models.

0 commit comments

Comments
 (0)