Skip to content

Commit 3d0f2d7

Browse files
SQLite implementation
# Conflicts: # packages/firebase_data_connect/firebase_data_connect/pubspec.yaml
1 parent 24f4e60 commit 3d0f2d7

19 files changed

+935
-303
lines changed

packages/firebase_data_connect/firebase_data_connect/lib/firebase_data_connect.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ export 'src/timestamp.dart' show Timestamp;
4242
export 'src/cache/cache_data_types.dart' show CacheSettings, QueryFetchPolicy;
4343
export 'src/cache/cache_manager.dart' show Cache;
4444
export 'src/cache/cache_provider.dart' show CacheProvider;
45+
export 'src/cache/sqlite_cache_provider.dart' show SQLite3CacheProvider;

packages/firebase_data_connect/firebase_data_connect/lib/src/cache/cache_data_types.dart

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
// Copyright 2025 Google LLC
32
//
43
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,23 +12,31 @@
1312
// See the License for the specific language governing permissions and
1413
// limitations under the License.
1514

15+
import 'dart:convert';
16+
17+
import 'package:firebase_data_connect/src/cache/cache_provider.dart';
18+
import 'package:flutter/foundation.dart' show kIsWeb;
19+
1620
/// Type of storage to use for the cache
17-
enum CacheStorage {
18-
persistent,
19-
memory
20-
}
21+
enum CacheStorage { persistent, memory }
2122

2223
const String GlobalIDKey = 'cacheId';
2324

2425
/// Configuration for the cache
2526
class CacheSettings {
26-
/// The type of storage to use (e.g., "persistent", "ephemeral")
27+
/// The type of storage to use (e.g., "persistent", "memory")
2728
final CacheStorage storage;
2829

2930
/// The maximum size of the cache in bytes
3031
final int maxSizeBytes;
3132

32-
const CacheSettings({this.storage = CacheStorage.memory, this.maxSizeBytes = 100000000});
33+
/// Duration for which cache is used before revalidation with server
34+
final Duration maxAge;
35+
36+
const CacheSettings(
37+
{this.storage = kIsWeb ? CacheStorage.memory : CacheStorage.persistent,
38+
this.maxSizeBytes = kIsWeb ? 40000000 : 100000000,
39+
this.maxAge = Duration.zero});
3340
}
3441

3542
/// Enum to control the fetch policy for a query
@@ -59,31 +66,45 @@ class ResultTree {
5966
DateTime lastAccessed;
6067

6168
/// A reference to the root `EntityNode` of the dehydrated tree.
62-
final EntityNode rootObject;
69+
//final EntityNode rootObject;
6370

6471
/// Checks if cached data is stale
6572
bool isStale() {
6673
if (DateTime.now().difference(cachedAt) > ttl) {
6774
return true; // stale
6875
} else {
6976
return false;
70-
}
77+
}
7178
}
7279

73-
7480
ResultTree(
7581
{required this.data,
7682
required this.ttl,
7783
required this.cachedAt,
78-
required this.lastAccessed,
79-
required this.rootObject});
84+
required this.lastAccessed});
85+
86+
factory ResultTree.fromJson(Map<String, dynamic> json) => ResultTree(
87+
data: Map<String, dynamic>.from(json['data'] as Map),
88+
ttl: Duration(microseconds: json['ttl'] as int),
89+
cachedAt: DateTime.parse(json['cachedAt'] as String),
90+
lastAccessed: DateTime.parse(json['lastAccessed'] as String),
91+
);
92+
93+
Map<String, dynamic> toJson() => {
94+
'data': data,
95+
'ttl': ttl.inMicroseconds,
96+
'cachedAt': cachedAt.toIso8601String(),
97+
'lastAccessed': lastAccessed.toIso8601String(),
98+
};
99+
100+
factory ResultTree.fromRawJson(String source) =>
101+
ResultTree.fromJson(json.decode(source) as Map<String, dynamic>);
102+
103+
String toRawJson() => json.encode(toJson());
80104
}
81105

82106
/// Target encoding mode
83-
enum EncodingMode {
84-
hydrated,
85-
dehydrated
86-
}
107+
enum EncodingMode { hydrated, dehydrated }
87108

88109
/// Represents a normalized data entity.
89110
class EntityDataObject {
@@ -109,8 +130,23 @@ class EntityDataObject {
109130
return _serverValues;
110131
}
111132

112-
EntityDataObject(
113-
{required this.guid});
133+
EntityDataObject({required this.guid});
134+
135+
factory EntityDataObject.fromRawJson(String source) =>
136+
EntityDataObject.fromJson(json.decode(source) as Map<String, dynamic>);
137+
138+
String toRawJson() => json.encode(toJson());
139+
140+
factory EntityDataObject.fromJson(Map<String, dynamic> json) =>
141+
EntityDataObject(
142+
guid: json[GlobalIDKey] as String,
143+
).._serverValues =
144+
Map<String, dynamic>.from(json['_serverValues'] as Map);
145+
146+
Map<String, dynamic> toJson() => {
147+
GlobalIDKey: guid,
148+
'_serverValues': _serverValues,
149+
};
114150
}
115151

116152
/// A tree-like data structure that represents the dehydrated or hydrated query result.
@@ -120,19 +156,90 @@ class EntityNode {
120156

121157
/// A dictionary of scalar values (if the node does not represent a normalized entity).
122158
final Map<String, dynamic>? scalarValues;
159+
static const String scalarsKey = 'scalars';
123160

124161
/// A dictionary of references to other `EntityNode`s (for nested objects).
125162
final Map<String, EntityNode>? nestedObjects;
163+
static const String objectsKey = 'objects';
126164

127165
/// A dictionary of lists of other `EntityNode`s (for arrays of objects).
128166
final Map<String, List<EntityNode>>? nestedObjectLists;
167+
static const String listsKey = 'lists';
129168

130169
EntityNode(
131170
{this.entity,
132171
this.scalarValues,
133172
this.nestedObjects,
134173
this.nestedObjectLists});
135174

175+
factory EntityNode.fromJson(
176+
Map<String, dynamic> json, CacheProvider cacheProvider) {
177+
EntityDataObject? entity = null;
178+
if (json[GlobalIDKey] != null) {
179+
entity = cacheProvider.getEntityDataObject(json[GlobalIDKey]);
180+
}
181+
182+
Map<String, dynamic>? scalars = null;
183+
if (json[scalarsKey] != null) {
184+
scalars = json[scalarsKey];
185+
}
186+
187+
Map<String, EntityNode>? objects;
188+
if (json[objectsKey] != null) {
189+
Map<String, dynamic> srcObjMap = json[objectsKey] as Map<String, dynamic>;
190+
objects = {};
191+
srcObjMap.forEach((key, value) {
192+
Map<String, dynamic> objValue = value as Map<String, dynamic>;
193+
EntityNode node = EntityNode.fromJson(objValue, cacheProvider);
194+
objects?[key] = node;
195+
});
196+
}
197+
198+
Map<String, List<EntityNode>>? objLists;
199+
if (json[listsKey] != null) {
200+
Map<String, dynamic> srcListMap = json[listsKey] as Map<String, dynamic>;
201+
objLists = {};
202+
srcListMap.forEach((key, value) {
203+
List<EntityNode> enodeList = [];
204+
List<dynamic> jsonList = value as List<dynamic>;
205+
jsonList.forEach((jsonObj) {
206+
Map<String, dynamic> jmap = jsonObj as Map<String, dynamic>;
207+
EntityNode en = EntityNode.fromJson(jmap, cacheProvider);
208+
enodeList.add(en);
209+
});
210+
objLists?[key] = enodeList;
211+
});
212+
}
213+
return EntityNode(
214+
entity: entity,
215+
scalarValues: scalars,
216+
nestedObjects: objects,
217+
nestedObjectLists: objLists);
218+
}
219+
220+
/*
221+
factory EntityNode.fromJson(Map<String, dynamic> json, CacheProvider cacheProvider) => EntityNode(
222+
entity: json[GlobalIDKey] == null
223+
? null
224+
: cacheProvider.getEntityDataObject(json[GlobalIDKey]),
225+
scalarValues: json['scalars'] == null
226+
? null
227+
: Map<String, dynamic>.from(json['scalars'] as Map),
228+
nestedObjects: json['objects'] == null
229+
? null
230+
: (json['objects'] as Map<String, dynamic>).map(
231+
(k, e) => MapEntry(
232+
k, EntityNode.fromJson(e as Map<String, dynamic>, cacheProvider))),
233+
nestedObjectLists: json['lists'] == null
234+
? null
235+
: (json['lists'] as Map<String, dynamic>).map((k, e) =>
236+
MapEntry(
237+
k,
238+
List<EntityNode>.from((e as List<dynamic>).map((x) =>
239+
EntityNode.fromJson(x as Map<String, dynamic>, cacheProvider))))),
240+
);
241+
*/
242+
136243
Map<String, dynamic> toJson({EncodingMode mode = EncodingMode.hydrated}) {
137244
Map<String, dynamic> jsonData = {};
138245
if (mode == EncodingMode.hydrated) {
@@ -153,46 +260,42 @@ class EntityNode {
153260
if (nestedObjectLists != null) {
154261
nestedObjectLists!.forEach((key, edoList) {
155262
List<Map<String, dynamic>> jsonList = [];
156-
edoList.forEach((edo){
263+
edoList.forEach((edo) {
157264
jsonList.add(edo.toJson(mode: mode));
158265
});
159266
jsonData[key] = jsonList;
160267
});
161268
}
162-
163-
} // if hydrated
269+
} // if hydrated
164270
else if (mode == EncodingMode.dehydrated) {
165271
// encode the guid so we can extract the EntityDataObject
166272
if (entity != null) {
167273
jsonData[GlobalIDKey] = entity!.guid;
168274
}
169275

170276
if (scalarValues != null) {
171-
jsonData['scalars'] = scalarValues;
277+
jsonData[scalarsKey] = scalarValues;
172278
}
173279

174280
if (nestedObjects != null) {
175-
List<Map<String, dynamic>> nestedObjectsJson = [];
176-
nestedObjects!.forEach((key, edo){
177-
Map<String, dynamic> obj = {};
178-
obj[key] = edo.toJson(mode: mode);
179-
nestedObjectsJson.add(obj);
180-
});
181-
jsonData['objects'] = nestedObjectsJson;
281+
Map<String, dynamic> nestedObjectsJson = {};
282+
nestedObjects!.forEach((key, edo) {
283+
nestedObjectsJson[key] = edo.toJson(mode: mode);
284+
});
285+
jsonData[objectsKey] = nestedObjectsJson;
182286
}
183287

184288
if (nestedObjectLists != null) {
185-
List<Map<String, dynamic>> nestedObjectListsJson = [];
186-
nestedObjectLists!.forEach((key, edoList){
289+
Map<String, dynamic> nestedObjectListsJson = {};
290+
nestedObjectLists!.forEach((key, edoList) {
187291
List<Map<String, dynamic>> jsonList = [];
188-
edoList.forEach((edo){
292+
edoList.forEach((edo) {
189293
jsonList.add(edo.toJson(mode: mode));
190294
});
191-
nestedObjectListsJson.add({key: jsonList});
295+
nestedObjectListsJson[key] = jsonList;
192296
});
193-
jsonData['lists'] = nestedObjectListsJson;
297+
jsonData[listsKey] = nestedObjectListsJson;
194298
}
195-
196299
}
197300
return jsonData;
198301
}

0 commit comments

Comments
 (0)