mirror of
https://github.com/appwrite/sdk-for-flutter.git
synced 2026-04-07 19:27:41 +00:00
Add offline support for relationships
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
@@ -82,6 +83,10 @@ class OfflineCallHandler extends CallHandler
|
||||
final endpoint = getEndpoint(params);
|
||||
final offlinePersistency = getOfflinePersistency(params);
|
||||
|
||||
if (offlinePersistency) {
|
||||
params.headers['X-SDK-Offline'] = 'true';
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
// if offline, do offline stuff
|
||||
@@ -96,19 +101,36 @@ class OfflineCallHandler extends CallHandler
|
||||
|
||||
final pathValues = routeMatch?.getPathValues(params.path);
|
||||
|
||||
final model = modelPattern.split('/').map((part) {
|
||||
if (!part.startsWith('{') || !part.endsWith('}')) {
|
||||
return part;
|
||||
String replacePlaceholder(
|
||||
String input, Map<String, String>? pathValues) {
|
||||
if (!input.startsWith('{') || !input.endsWith('}')) {
|
||||
return input;
|
||||
}
|
||||
return pathValues![part.substring(1, part.length - 1)];
|
||||
return pathValues![input.substring(1, input.length - 1)]!;
|
||||
}
|
||||
|
||||
final model = modelPattern.split('/').map((part) {
|
||||
return replacePlaceholder(part, pathValues);
|
||||
}).join('/');
|
||||
|
||||
final keyPattern = routeMatch?.getLabel('offline.key') ?? '';
|
||||
final key = replacePlaceholder(
|
||||
keyPattern,
|
||||
pathValues,
|
||||
);
|
||||
|
||||
final containerKeyPattern =
|
||||
routeMatch?.getLabel('offline.container-key') ?? '';
|
||||
final containerKey = replacePlaceholder(
|
||||
containerKeyPattern,
|
||||
pathValues,
|
||||
);
|
||||
|
||||
final cacheParams = CacheParams(
|
||||
model: model,
|
||||
key: routeMatch?.getLabel('offline.key') as String,
|
||||
key: key,
|
||||
responseIdKey: routeMatch?.getLabel('offline.response-key') as String,
|
||||
responseContainerKey:
|
||||
routeMatch?.getLabel('offline.container-key') as String,
|
||||
responseContainerKey: containerKey,
|
||||
);
|
||||
|
||||
final uri = Uri.parse(endpoint + params.path);
|
||||
@@ -145,15 +167,23 @@ class OfflineCallHandler extends CallHandler
|
||||
|
||||
final response = await next.handleCall(params);
|
||||
|
||||
// cache stuff
|
||||
print('cached stuff...');
|
||||
if (offlinePersistency) {
|
||||
// cache stuff
|
||||
print('cached stuff...');
|
||||
|
||||
final relationsHeader = response.headers['x-appwrite-relations'];
|
||||
if (relationsHeader != null) {
|
||||
final relations = (jsonDecode(relationsHeader) as List)
|
||||
.cast<Map<String, dynamic>>();
|
||||
await cacheCollections(relations);
|
||||
}
|
||||
cacheResponse(
|
||||
cacheModel: cacheParams.model,
|
||||
cacheKey: cacheParams.key,
|
||||
cacheResponseIdKey: cacheParams.responseIdKey,
|
||||
request: request,
|
||||
response: response,
|
||||
cacheResponseContainerKey: cacheParams.responseContainerKey,
|
||||
requestMethod: request.method,
|
||||
responseData: response.data,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,7 +200,8 @@ class OfflineCallHandler extends CallHandler
|
||||
rethrow;
|
||||
}
|
||||
isOnline.value = false;
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
print(s);
|
||||
throw AppwriteException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +374,10 @@ class ClientIO extends ClientBase with ClientMixin {
|
||||
|
||||
@override
|
||||
Future<Response> call(CallParams params) async {
|
||||
while (!_initialized) {
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
params.headers.addAll(this._headers!);
|
||||
final response = await _handler.handleCall(
|
||||
withOfflinePersistency(
|
||||
|
||||
@@ -366,45 +366,122 @@ class ClientOfflineMixin {
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void cacheRelated({
|
||||
required Map<String, Object?> document,
|
||||
}) {
|
||||
// iterate over each attribute to see if it's nested data
|
||||
document.entries.forEach((entry) {
|
||||
if (entry.value is Map) {
|
||||
final nestedDocument = entry.value as Map<String, Object?>;
|
||||
final nestedDatabaseId = nestedDocument['\$databaseId'] as String?;
|
||||
final nestedCollectionId = nestedDocument['\$collectionId'] as String?;
|
||||
final nestedDocumentId = nestedDocument['\$id'] as String?;
|
||||
if (nestedDatabaseId == null ||
|
||||
nestedCollectionId == null ||
|
||||
nestedDocumentId == null) return;
|
||||
final nestedModel =
|
||||
"/databases/$nestedDatabaseId/collections/$nestedCollectionId/documents";
|
||||
cacheResponse(
|
||||
cacheModel: nestedModel,
|
||||
cacheKey: nestedDocumentId,
|
||||
cacheResponseIdKey: "\$id",
|
||||
cacheResponseContainerKey: '',
|
||||
requestMethod: 'GET',
|
||||
responseData: entry.value,
|
||||
);
|
||||
document[entry.key] = {
|
||||
'\$id': nestedDocumentId,
|
||||
'\$databaseId': nestedDatabaseId,
|
||||
'\$collectionId': nestedCollectionId,
|
||||
};
|
||||
} else if (entry.value is List &&
|
||||
entry.value != null &&
|
||||
(entry.value as List).isNotEmpty) {
|
||||
final values = (entry.value as List);
|
||||
if (!(values.first is Map<String, Object?>)) return;
|
||||
final nestedDocument = values.first;
|
||||
final nestedDatabaseId = nestedDocument['\$databaseId'] as String?;
|
||||
final nestedCollectionId = nestedDocument['\$collectionId'] as String?;
|
||||
if (nestedDatabaseId == null || nestedCollectionId == null) return;
|
||||
final nestedModel =
|
||||
"/databases/$nestedDatabaseId/collections/$nestedCollectionId/documents";
|
||||
cacheResponse(
|
||||
cacheModel: nestedModel,
|
||||
cacheKey: '',
|
||||
cacheResponseIdKey: "\$id",
|
||||
cacheResponseContainerKey: 'documents',
|
||||
requestMethod: 'GET',
|
||||
responseData: {
|
||||
'documents': entry.value,
|
||||
},
|
||||
);
|
||||
document[entry.key] = values.map((value) {
|
||||
final nestedDocumentId = value['\$id'] as String?;
|
||||
return {
|
||||
'\$id': nestedDocumentId,
|
||||
'\$databaseId': nestedDatabaseId,
|
||||
'\$collectionId': nestedCollectionId,
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> cacheCollections(List<Map<String, dynamic>> relations) {
|
||||
final futures = <Future>[];
|
||||
for (var collection in relations) {
|
||||
futures.add(_modelData.cacheModel(collection));
|
||||
}
|
||||
return Future.wait(futures);
|
||||
}
|
||||
|
||||
void cacheResponse({
|
||||
required String cacheModel,
|
||||
required String cacheKey,
|
||||
required String cacheResponseIdKey,
|
||||
required http.BaseRequest request,
|
||||
required Response response,
|
||||
required String cacheResponseContainerKey,
|
||||
required String requestMethod,
|
||||
required dynamic responseData,
|
||||
}) {
|
||||
if (cacheModel.isEmpty) return;
|
||||
|
||||
switch (request.method) {
|
||||
switch (requestMethod) {
|
||||
case 'GET':
|
||||
final clone = cloneMap(response.data);
|
||||
final clone = cloneMap(responseData);
|
||||
if (cacheKey.isNotEmpty) {
|
||||
cacheRelated(document: clone);
|
||||
_modelData.upsert(model: cacheModel, data: clone, key: cacheKey);
|
||||
} else {
|
||||
clone.forEach((key, value) {
|
||||
if (key == 'total') return;
|
||||
_modelData.batchUpsert(
|
||||
model: cacheModel,
|
||||
dataList: value as List,
|
||||
idKey: cacheResponseIdKey,
|
||||
);
|
||||
final values = clone[cacheResponseContainerKey] as List;
|
||||
values.forEach((value) {
|
||||
cacheRelated(document: value);
|
||||
});
|
||||
_modelData.batchUpsert(
|
||||
model: cacheModel,
|
||||
dataList: values,
|
||||
idKey: cacheResponseIdKey,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'POST':
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
Map<String, Object?> clone = cloneMap(response.data);
|
||||
Map<String, Object?> clone = cloneMap(responseData);
|
||||
if (cacheKey.isEmpty) {
|
||||
cacheKey = clone['\$id'] as String;
|
||||
}
|
||||
if (cacheModel.endsWith('/prefs')) {
|
||||
clone = response.data['prefs'];
|
||||
clone = responseData['prefs'];
|
||||
}
|
||||
_modelData.upsert(model: cacheModel, data: clone, key: cacheKey);
|
||||
break;
|
||||
case 'DELETE':
|
||||
if (cacheKey.isNotEmpty) {
|
||||
// _modelData.get(model: cacheModel, key: cacheKey).then((cachedData) {
|
||||
// if (cachedData == null) {
|
||||
// return;
|
||||
// }
|
||||
// });
|
||||
_modelData.delete(model: cacheModel, key: cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ final routes = [
|
||||
"offline": {
|
||||
"model":
|
||||
"\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
|
||||
"key": "{documentId}",
|
||||
"key": "",
|
||||
"response-key": "\$id",
|
||||
"container-key": "",
|
||||
},
|
||||
|
||||
@@ -19,23 +19,33 @@ class CacheSize {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
Future<void> applyChange(int change) async {
|
||||
if (change == 0) return;
|
||||
Future<int?> applyChange(Transaction txn, int change) async {
|
||||
if (change == 0) return null;
|
||||
|
||||
final record = getCacheSizeRecordRef();
|
||||
|
||||
final currentSize = await record.get(_db) ?? 0;
|
||||
await record.put(_db, currentSize + change);
|
||||
final currentSize = await record.get(txn) ?? 0;
|
||||
return await record.put(txn, currentSize + change);
|
||||
}
|
||||
|
||||
Future<void> update({
|
||||
Map<String, dynamic>? oldData,
|
||||
required RecordRef<String, Map<String, Object?>> recordRef,
|
||||
required Transaction txn,
|
||||
Map<String, dynamic>? newData,
|
||||
}) async {
|
||||
final oldData = await recordRef.get(txn);
|
||||
final oldSize = oldData != null ? encode(oldData).length : 0;
|
||||
final newSize = newData != null ? encode(newData).length : 0;
|
||||
final change = newSize - oldSize;
|
||||
await applyChange(change);
|
||||
final cacheSize = await applyChange(txn, change);
|
||||
|
||||
if (change != 0) {
|
||||
print([
|
||||
'${recordRef.key}: oldSize: $oldSize',
|
||||
'newSize: $newSize',
|
||||
'change: $change',
|
||||
'cacheSize: $cacheSize',
|
||||
].join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
void onChange(void callback(int? currentSize)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:appwrite/src/offline/services/cache_size.dart';
|
||||
import 'package:sembast/sembast.dart';
|
||||
import 'package:sembast/utils/value_utils.dart';
|
||||
|
||||
import '../../../appwrite.dart';
|
||||
import 'accessed_at.dart';
|
||||
@@ -8,6 +9,9 @@ class ModelData {
|
||||
final Database _db;
|
||||
final AccessedAt _accessedAt;
|
||||
final CacheSize _cacheSize;
|
||||
final int maxDepth = 3;
|
||||
final documentModelRegex = RegExp(
|
||||
r'^/databases/([a-zA-Z0-9\-]*)/collections/([a-zA-Z0-9\-]*)/documents$');
|
||||
|
||||
ModelData(this._db)
|
||||
: _accessedAt = AccessedAt(_db),
|
||||
@@ -17,15 +21,128 @@ class ModelData {
|
||||
return stringMapStoreFactory.store(model);
|
||||
}
|
||||
|
||||
Future<void> cacheModel(Map<String, dynamic> collection) {
|
||||
final store = stringMapStoreFactory.store('collections');
|
||||
|
||||
return store
|
||||
.record("${collection['databaseId']}|${collection['collectionId']}")
|
||||
.put(_db, collection);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> get({
|
||||
required String model,
|
||||
required String key,
|
||||
}) async {
|
||||
final immutableRecord = await _getRecord(model: model, key: key);
|
||||
|
||||
if (immutableRecord == null) return null;
|
||||
|
||||
final record = cloneMap(immutableRecord);
|
||||
await _populateRelated(record, 0);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> _getRecord({
|
||||
required String model,
|
||||
required String key,
|
||||
}) async {
|
||||
final store = getModelStore(model);
|
||||
final recordRef = store.record(key);
|
||||
return recordRef.get(_db);
|
||||
}
|
||||
|
||||
bool _isNestedDocument(Map<String, dynamic> record) {
|
||||
return record.containsKey('\$databaseId') &&
|
||||
record.containsKey('\$collectionId') &&
|
||||
record.containsKey('\$id');
|
||||
}
|
||||
|
||||
/// Given a record with $databaseId, $collectionId and $id, populate the rest
|
||||
/// of the attributes from the cache and then populate the related records.
|
||||
Future<Map<String, dynamic>?> _populateRecord(
|
||||
Map<String, dynamic>? record, int depth) async {
|
||||
if (record == null) return record;
|
||||
|
||||
if (!_isNestedDocument(record)) return record;
|
||||
|
||||
final databaseId = record['\$databaseId'] as String;
|
||||
final collectionId = record['\$collectionId'] as String;
|
||||
final documentId = record['\$id'] as String;
|
||||
|
||||
final nestedModel =
|
||||
"/databases/$databaseId/collections/$collectionId/documents";
|
||||
|
||||
final cached = await _getRecord(
|
||||
model: nestedModel,
|
||||
key: documentId,
|
||||
);
|
||||
|
||||
if (cached == null) return record;
|
||||
|
||||
record.addAll(cloneMap(cached));
|
||||
|
||||
await _populateRelated(record, depth + 1);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// Iterate over every attribute of a record and fetch related records from
|
||||
/// the cache.
|
||||
Future<void> _populateRelated(Map<String, dynamic>? record, int depth) {
|
||||
if (record == null) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
// iterate over each attribute and check if it is a relation
|
||||
final futures = <Future>[];
|
||||
for (final attribute in record.entries) {
|
||||
if (attribute.value is Map<String, Object?>) {
|
||||
final map = attribute.value as Map<String, Object?>;
|
||||
if (_isNestedDocument(record)) {
|
||||
if (depth >= maxDepth) {
|
||||
record[attribute.key] = null;
|
||||
} else {
|
||||
final future = _populateRecord(map, depth).then((populated) {
|
||||
record[attribute.key] = populated;
|
||||
});
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
} else if (attribute.value is List) {
|
||||
final List list = attribute.value as List;
|
||||
final futureList = <Future<Map<String, dynamic>?>>[];
|
||||
if (list.isEmpty) continue;
|
||||
|
||||
if (depth >= maxDepth &&
|
||||
list.first is Map<String, Object?> &&
|
||||
_isNestedDocument(list.first)) {
|
||||
record[attribute.key] = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final map in list) {
|
||||
if (map is! Map<String, Object?>) {
|
||||
continue;
|
||||
}
|
||||
futureList.add(_populateRecord(map, depth));
|
||||
}
|
||||
|
||||
if (futureList.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final future = Future.wait(futureList).then((populated) {
|
||||
record[attribute.key] = populated;
|
||||
});
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
return Future.wait(futures);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> list({
|
||||
required String model,
|
||||
required String cacheResponseContainerKey,
|
||||
@@ -50,6 +167,7 @@ class ModelData {
|
||||
final List<Filter> equalFilters = [];
|
||||
value.forEach((v) {
|
||||
equalFilters.add(Filter.equals(q.params[0], v));
|
||||
equalFilters.add(Filter.equals("${q.params[0]}.\$id", v));
|
||||
});
|
||||
filters.add(Filter.or(equalFilters));
|
||||
});
|
||||
@@ -82,6 +200,30 @@ class ModelData {
|
||||
filters.add(Filter.matches(q.params[0], r'${q.params[1]}+'));
|
||||
break;
|
||||
|
||||
case 'isNull':
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
|
||||
case 'isNotNull':
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
|
||||
case 'between':
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
|
||||
case 'startsWith':
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
|
||||
case 'endsWith':
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
|
||||
case 'orderAsc':
|
||||
sortOrders.add(SortOrder(q.params[0] as String));
|
||||
break;
|
||||
@@ -116,19 +258,27 @@ class ModelData {
|
||||
final records = await store.find(_db, finder: finder);
|
||||
final count = await store.count(_db, filter: filter);
|
||||
|
||||
final list = records.map((record) {
|
||||
// convert to Map<String, dynamic>
|
||||
final map = Map<String, dynamic>();
|
||||
record.value.entries.forEach((entry) {
|
||||
map[entry.key] = entry.value;
|
||||
});
|
||||
return map;
|
||||
}).toList();
|
||||
|
||||
final futures = <Future<Map<String, dynamic>?>>[];
|
||||
for (final record in list) {
|
||||
futures.add(_populateRecord(record, 0));
|
||||
}
|
||||
|
||||
final keys = records.map((record) => record.key).toList();
|
||||
|
||||
_accessedAt.update(model: store.name, keys: keys);
|
||||
|
||||
return {
|
||||
'total': count,
|
||||
cacheResponseContainerKey: records.map((record) {
|
||||
final map = Map<String, dynamic>();
|
||||
record.value.entries.forEach((entry) {
|
||||
map[entry.key] = entry.value;
|
||||
});
|
||||
return map;
|
||||
}).toList(),
|
||||
cacheResponseContainerKey: await Future.wait(futures),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,13 +287,90 @@ class ModelData {
|
||||
required Map<String, dynamic> data,
|
||||
required String key,
|
||||
}) async {
|
||||
final store = getModelStore(model);
|
||||
final match = documentModelRegex.firstMatch(model);
|
||||
if (match?.groupCount == 2) {
|
||||
// data is a document
|
||||
|
||||
final recordRef = store.record(key);
|
||||
final record = await recordRef.get(_db);
|
||||
_cacheSize.update(oldData: record, newData: data);
|
||||
// match starting at 1 since 0 is the full match
|
||||
final databaseId = match!.group(1)!;
|
||||
final collectionId = match.group(2)!;
|
||||
|
||||
final result = await recordRef.put(_db, data, merge: true);
|
||||
final collectionStore = getModelStore('collections');
|
||||
final recordRef = collectionStore.record('$databaseId|$collectionId');
|
||||
final collection = await recordRef.get(_db);
|
||||
final attributes = (collection?['attributes'] ?? <Map<String, Object?>>{})
|
||||
as Map<String, Object?>;
|
||||
for (final attributeEntry in attributes.entries) {
|
||||
final key = attributeEntry.key;
|
||||
final attribute = attributeEntry.value as Map<String, Object?>;
|
||||
final relatedCollection = attribute['relatedCollection'] as String;
|
||||
final relationType = attribute['relationType'] as String;
|
||||
final side = attribute['side'] as String;
|
||||
|
||||
if (!data.containsKey(key)) continue;
|
||||
|
||||
final nestedModel =
|
||||
"/databases/$databaseId/collections/$relatedCollection/documents";
|
||||
|
||||
if (relationType == 'oneToOne' ||
|
||||
(relationType == 'oneToMany' && side == 'child') ||
|
||||
(relationType == 'manyToOne' && side == 'parent')) {
|
||||
// data[key] is a single document
|
||||
String documentId = '';
|
||||
if (data[key] is String) {
|
||||
// data[key] is a document ID
|
||||
documentId = data[key] as String;
|
||||
} else if (data[key] is Map<String, Object?>) {
|
||||
// data[key] is a nested document
|
||||
final related = data[key] as Map<String, Object?>;
|
||||
documentId = (related['\$id'] ?? ID.unique()) as String;
|
||||
await upsert(model: nestedModel, key: documentId, data: related);
|
||||
}
|
||||
data[key] = {
|
||||
'\$databaseId': databaseId,
|
||||
'\$collectionId': relatedCollection,
|
||||
'\$id': documentId
|
||||
};
|
||||
} else {
|
||||
// data[key] is a list of documents
|
||||
final result = <Map<String, Object?>>[];
|
||||
final relatedList = data[key] as List;
|
||||
for (final related in relatedList) {
|
||||
String documentId = '';
|
||||
if (related is String) {
|
||||
// related is a document ID
|
||||
documentId = related;
|
||||
} else if (related is Map<String, Object?>) {
|
||||
// related is a nested document
|
||||
documentId = (related['\$id'] ?? ID.unique()) as String;
|
||||
await upsert(model: nestedModel, key: documentId, data: related);
|
||||
}
|
||||
result.add({
|
||||
'\$databaseId': databaseId,
|
||||
'\$collectionId': relatedCollection,
|
||||
'\$id': documentId
|
||||
});
|
||||
}
|
||||
data[key] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final result = await _db.transaction((txn) async {
|
||||
final store = getModelStore(model);
|
||||
|
||||
final recordRef = store.record(key);
|
||||
final oldData = await recordRef.get(txn);
|
||||
final oldSize = oldData != null ? _cacheSize.encode(oldData).length : 0;
|
||||
|
||||
final result = await recordRef.put(txn, data, merge: true);
|
||||
|
||||
final newSize = _cacheSize.encode(result).length;
|
||||
final change = newSize - oldSize;
|
||||
|
||||
await _cacheSize.applyChange(txn, change);
|
||||
return result;
|
||||
});
|
||||
await _accessedAt.update(model: model, keys: [key]);
|
||||
return result;
|
||||
}
|
||||
@@ -167,16 +394,19 @@ class ModelData {
|
||||
Future<void> delete({required String model, required String key}) async {
|
||||
final store = getModelStore(model);
|
||||
RecordSnapshot<String, Map<String, Object?>>? record;
|
||||
final recordRef = store.record(key);
|
||||
|
||||
record = await store.record(key).getSnapshot(_db);
|
||||
record = await recordRef.getSnapshot(_db);
|
||||
|
||||
if (record == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cacheSize.update(oldData: record.value);
|
||||
|
||||
await record.ref.delete(_db);
|
||||
await _db.transaction((txn) async {
|
||||
final oldSize = _cacheSize.encode(record!.value).length;
|
||||
await _cacheSize.applyChange(txn, oldSize * -1);
|
||||
await record.ref.delete(_db);
|
||||
});
|
||||
|
||||
await _accessedAt.delete(model: model, key: record.key);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:io';
|
||||
import 'package:sembast/sembast.dart';
|
||||
import 'package:sembast_sqflite/sembast_sqflite.dart';
|
||||
import 'package:sqflite/sqflite.dart' as sqflite;
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart' hide Database;
|
||||
|
||||
class OfflineDatabase {
|
||||
static final OfflineDatabase instance = OfflineDatabase._internal();
|
||||
|
||||
Reference in New Issue
Block a user