Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 87749aa

Browse files
committed
Firebase database query and persistence improvements
1 parent 9678a13 commit 87749aa

File tree

9 files changed

+258
-110
lines changed

9 files changed

+258
-110
lines changed

packages/firebase_database/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.0.8
2+
3+
* Added missing offline persistence and query functionality on Android
4+
* Fixed startAt query behavior on iOS
5+
* Persistence methods no longer throw errors on failure, return false instead
6+
* Updates to docs and tests
7+
18
## 0.0.7
29

310
* Fixed offline persistence on iOS

packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/FirebaseDatabasePlugin.java

Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import com.google.firebase.database.ChildEventListener;
99
import com.google.firebase.database.DataSnapshot;
1010
import com.google.firebase.database.DatabaseError;
11+
import com.google.firebase.database.DatabaseException;
1112
import com.google.firebase.database.DatabaseReference;
1213
import com.google.firebase.database.FirebaseDatabase;
14+
import com.google.firebase.database.Query;
1315
import com.google.firebase.database.ValueEventListener;
1416
import io.flutter.plugin.common.MethodCall;
1517
import io.flutter.plugin.common.MethodChannel;
@@ -31,7 +33,7 @@ public class FirebaseDatabasePlugin implements MethodCallHandler {
3133

3234
// Handles are ints used as indexes into the sparse array of active observers
3335
private int nextHandle = 0;
34-
private final SparseArray<EventObserver> observers = new SparseArray<EventObserver>();
36+
private final SparseArray<EventObserver> observers = new SparseArray<>();
3537

3638
public static void registerWith(PluginRegistry.Registrar registrar) {
3739
final MethodChannel channel =
@@ -43,14 +45,68 @@ private FirebaseDatabasePlugin(MethodChannel channel) {
4345
this.channel = channel;
4446
}
4547

46-
private static DatabaseReference getReference(Map<String, ?> arguments) {
47-
@SuppressWarnings("unchecked")
48+
private DatabaseReference getReference(Map<String, Object> arguments) {
4849
String path = (String) arguments.get("path");
4950
DatabaseReference reference = FirebaseDatabase.getInstance().getReference();
5051
if (path != null) reference = reference.child(path);
5152
return reference;
5253
}
5354

55+
private Query getQuery(Map<String, Object> arguments) {
56+
Query query = getReference(arguments);
57+
@SuppressWarnings("unchecked")
58+
Map<String, Object> parameters = (Map<String, Object>) arguments.get("parameters");
59+
Object orderBy = parameters.get("orderBy");
60+
if ("child".equals(orderBy)) {
61+
query = query.orderByChild((String) parameters.get("orderByChildKey"));
62+
} else if ("key".equals(orderBy)) {
63+
query = query.orderByKey();
64+
} else if ("value".equals(orderBy)) {
65+
query = query.orderByValue();
66+
} else if ("priority".equals(orderBy)) {
67+
query = query.orderByPriority();
68+
}
69+
if (parameters.containsKey("startAt")) {
70+
Object startAt = parameters.get("startAt");
71+
String startAtKey = (String) parameters.get("startAtKey");
72+
if (startAt instanceof Boolean) {
73+
query = query.startAt((Boolean) startAt, startAtKey);
74+
} else if (startAt instanceof String) {
75+
query = query.startAt((String) startAt, startAtKey);
76+
} else {
77+
query = query.startAt((Double) startAt, startAtKey);
78+
}
79+
}
80+
if (parameters.containsKey("endAt")) {
81+
Object endAt = parameters.get("endAt");
82+
String endAtKey = (String) parameters.get("endAtKey");
83+
if (endAt instanceof Boolean) {
84+
query = query.endAt((Boolean) endAt, endAtKey);
85+
} else if (endAt instanceof String) {
86+
query = query.endAt((String) endAt, endAtKey);
87+
} else {
88+
query = query.endAt((Double) endAt, endAtKey);
89+
}
90+
}
91+
if (arguments.containsKey("equalTo")) {
92+
Object equalTo = arguments.get("equalTo");
93+
if (equalTo instanceof Boolean) {
94+
query = query.equalTo((Boolean) equalTo);
95+
} else if (equalTo instanceof String) {
96+
query = query.equalTo((String) equalTo);
97+
} else {
98+
query = query.equalTo((Double) equalTo);
99+
}
100+
}
101+
if (arguments.containsKey("limitToFirst")) {
102+
query = query.limitToFirst((int) arguments.get("limitToFirst"));
103+
}
104+
if (arguments.containsKey("limitToLast")) {
105+
query = query.limitToLast((int) arguments.get("limitToLast"));
106+
}
107+
return query;
108+
}
109+
54110
private class DefaultCompletionListener implements DatabaseReference.CompletionListener {
55111
private final Result result;
56112

@@ -79,8 +135,8 @@ private class EventObserver implements ChildEventListener, ValueEventListener {
79135

80136
private void sendEvent(String eventType, DataSnapshot snapshot, String previousChildName) {
81137
if (eventType.equals(requestedEventType)) {
82-
Map<String, Object> arguments = new HashMap<String, Object>();
83-
Map<String, Object> snapshotMap = new HashMap<String, Object>();
138+
Map<String, Object> arguments = new HashMap<>();
139+
Map<String, Object> snapshotMap = new HashMap<>();
84140
snapshotMap.put("key", snapshot.getKey());
85141
snapshotMap.put("value", snapshot.getValue());
86142
arguments.put("handle", handle);
@@ -119,34 +175,54 @@ public void onDataChange(DataSnapshot snapshot) {
119175
}
120176
}
121177

122-
@SuppressWarnings("unchecked")
123178
@Override
124179
public void onMethodCall(MethodCall call, final Result result) {
125-
Map<String, Object> arguments = (Map<String, Object>) call.arguments;
180+
Map<String, Object> arguments = call.arguments();
126181
switch (call.method) {
127182
case "FirebaseDatabase#goOnline":
128-
FirebaseDatabase.getInstance().goOnline();
129-
break;
183+
{
184+
FirebaseDatabase.getInstance().goOnline();
185+
result.success(null);
186+
break;
187+
}
130188

131189
case "FirebaseDatabase#goOffline":
132-
FirebaseDatabase.getInstance().goOffline();
133-
break;
190+
{
191+
FirebaseDatabase.getInstance().goOffline();
192+
result.success(null);
193+
break;
194+
}
134195

135196
case "FirebaseDatabase#purgeOutstandingWrites":
136-
FirebaseDatabase.getInstance().purgeOutstandingWrites();
137-
break;
197+
{
198+
FirebaseDatabase.getInstance().purgeOutstandingWrites();
199+
result.success(null);
200+
break;
201+
}
138202

139203
case "FirebaseDatabase#setPersistenceEnabled":
140204
{
141-
boolean isEnabled = (boolean) arguments.get("enabled");
142-
FirebaseDatabase.getInstance().setPersistenceEnabled(isEnabled);
205+
Boolean isEnabled = (Boolean) arguments.get("enabled");
206+
try {
207+
FirebaseDatabase.getInstance().setPersistenceEnabled(isEnabled);
208+
result.success(true);
209+
} catch (DatabaseException e) {
210+
// Database is already in use, e.g. after hot reload/restart.
211+
result.success(false);
212+
}
143213
break;
144214
}
145215

146216
case "FirebaseDatabase#setPersistenceCacheSizeBytes":
147217
{
148-
long cacheSize = (long) arguments.get("cacheSize");
149-
FirebaseDatabase.getInstance().setPersistenceCacheSizeBytes(cacheSize);
218+
Long cacheSize = (Long) arguments.get("cacheSize");
219+
try {
220+
FirebaseDatabase.getInstance().setPersistenceCacheSizeBytes(cacheSize);
221+
result.success(true);
222+
} catch (DatabaseException e) {
223+
// Database is already in use, e.g. after hot reload/restart.
224+
result.success(false);
225+
}
150226
break;
151227
}
152228

@@ -171,31 +247,38 @@ public void onMethodCall(MethodCall call, final Result result) {
171247
break;
172248
}
173249

250+
case "Query#keepSynced":
251+
{
252+
boolean value = (Boolean) arguments.get("value");
253+
getQuery(arguments).keepSynced(value);
254+
break;
255+
}
256+
174257
case "Query#observe":
175258
{
176259
String eventType = (String) arguments.get("eventType");
177260
int handle = nextHandle++;
178261
EventObserver observer = new EventObserver(eventType, handle);
179262
observers.put(handle, observer);
180263
if (eventType.equals(EVENT_TYPE_VALUE)) {
181-
getReference(arguments).addValueEventListener(observer);
264+
getQuery(arguments).addValueEventListener(observer);
182265
} else {
183-
getReference(arguments).addChildEventListener(observer);
266+
getQuery(arguments).addChildEventListener(observer);
184267
}
185268
result.success(handle);
186269
break;
187270
}
188271

189272
case "Query#removeObserver":
190273
{
191-
DatabaseReference reference = getReference(arguments);
274+
Query query = getQuery(arguments);
192275
int handle = (Integer) arguments.get("handle");
193276
EventObserver observer = observers.get(handle);
194277
if (observer != null) {
195278
if (observer.requestedEventType.equals(EVENT_TYPE_VALUE)) {
196-
reference.removeEventListener((ValueEventListener) observer);
279+
query.removeEventListener((ValueEventListener) observer);
197280
} else {
198-
reference.removeEventListener((ChildEventListener) observer);
281+
query.removeEventListener((ChildEventListener) observer);
199282
}
200283
observers.delete(handle);
201284
result.success(null);

packages/firebase_database/example/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class _MyHomePageState extends State<MyHomePage> {
4444
@override
4545
void initState() {
4646
super.initState();
47+
FirebaseDatabase.instance.setPersistenceEnabled(true);
48+
_counterRef.keepSynced(true);
4749
_counterSubscription = _counterRef.onValue.listen((Event event) {
4850
setState(() {
4951
_counter = event.snapshot.value ?? 0;

packages/firebase_database/ios/Classes/FirebaseDatabasePlugin.m

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ - (FlutterError *)flutterError {
4040
}
4141
id startAt = parameters[@"startAt"];
4242
if (startAt) {
43-
query = [query queryStartingAtValue:startAt childKey:parameters[@"endAtKey"]];
43+
query = [query queryStartingAtValue:startAt childKey:parameters[@"startAtKey"]];
4444
}
4545
id endAt = parameters[@"endAt"];
4646
if (endAt) {
@@ -109,16 +109,41 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
109109
};
110110
if ([@"FirebaseDatabase#goOnline" isEqualToString:call.method]) {
111111
[[FIRDatabase database] goOnline];
112+
result(nil);
112113
} else if ([@"FirebaseDatabase#goOffline" isEqualToString:call.method]) {
113114
[[FIRDatabase database] goOffline];
115+
result(nil);
114116
} else if ([@"FirebaseDatabase#purgeOutstandingWrites" isEqualToString:call.method]) {
115117
[[FIRDatabase database] purgeOutstandingWrites];
118+
result(nil);
116119
} else if ([@"FirebaseDatabase#setPersistenceEnabled" isEqualToString:call.method]) {
117120
NSNumber *value = call.arguments[@"enabled"];
118-
[FIRDatabase database].persistenceEnabled = value.boolValue;
121+
@try {
122+
[FIRDatabase database].persistenceEnabled = value.boolValue;
123+
result([NSNumber numberWithBool:YES]);
124+
}
125+
@catch (NSException *exception) {
126+
if ([@"FIRDatabaseAlreadyInUse" isEqualToString:exception.name]) {
127+
// Database is already in use, e.g. after hot reload/restart.
128+
result([NSNumber numberWithBool:NO]);
129+
} else {
130+
@throw;
131+
}
132+
}
119133
} else if ([@"FirebaseDatabase#setPersistenceCacheSizeBytes" isEqualToString:call.method]) {
120134
NSNumber *value = call.arguments[@"cacheSize"];
121-
[FIRDatabase database].persistenceCacheSizeBytes = value.unsignedIntegerValue;
135+
@try {
136+
[FIRDatabase database].persistenceCacheSizeBytes = value.unsignedIntegerValue;
137+
result([NSNumber numberWithBool:YES]);
138+
}
139+
@catch (NSException *exception) {
140+
if ([@"FIRDatabaseAlreadyInUse" isEqualToString:exception.name]) {
141+
// Database is already in use, e.g. after hot reload/restart.
142+
result([NSNumber numberWithBool:NO]);
143+
} else {
144+
@throw;
145+
}
146+
}
122147
} else if ([@"DatabaseReference#set" isEqualToString:call.method]) {
123148
[getReference(call.arguments) setValue:call.arguments[@"value"]
124149
andPriority:call.arguments[@"priority"]

packages/firebase_database/lib/src/database_reference.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ class DatabaseReference extends Query {
9797
/// priority (including no priority), they are sorted by key. Numeric keys
9898
/// come first (sorted numerically), followed by the remaining keys (sorted
9999
/// lexicographically).
100-
100+
///
101101
/// Note that priorities are parsed and ordered as IEEE 754 double-precision
102102
/// floating-point numbers. Keys are always stored as strings and are treated
103-
/// as numbers only when they can be parsed as a 32-bit integer
103+
/// as numbers only when they can be parsed as a 32-bit integer.
104104
Future<Null> setPriority(dynamic priority) async {
105105
return _database._channel.invokeMethod(
106106
'DatabaseReference#setPriority',

packages/firebase_database/lib/src/firebase_database.dart

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,52 +32,65 @@ class FirebaseDatabase {
3232
/// Gets a DatabaseReference for the root of your Firebase Database.
3333
DatabaseReference reference() => new DatabaseReference._(this, <String>[]);
3434

35+
/// Attempts to sets the database persistence to [enabled].
36+
///
37+
/// This property must be set before calling methods on database references
38+
/// and only needs to be called once per application. The returned `Future`
39+
/// will complete with `true` if the operation was successful or `false` if
40+
/// the persistence could not be set (because database references have
41+
/// already been created).
42+
///
3543
/// The Firebase Database client will cache synchronized data and keep track
3644
/// of all writes you’ve initiated while your application is running. It
3745
/// seamlessly handles intermittent network connections and re-sends write
3846
/// operations when the network connection is restored.
3947
///
4048
/// However by default your write operations and cached data are only stored
41-
/// in-memory and will be lost when your app restarts. By setting this value
42-
/// to YES, the data will be persisted to on-device (disk) storage and will
49+
/// in-memory and will be lost when your app restarts. By setting [enabled]
50+
/// to `true`, the data will be persisted to on-device (disk) storage and will
4351
/// thus be available again when the app is restarted (even when there is no
44-
/// network connectivity at that time). Note that this property must be set
45-
/// before creating your first Database reference and only needs to be called
46-
/// once per application.
47-
Future<Null> setPersistenceEnabled(bool enabled) {
52+
/// network connectivity at that time).
53+
Future<bool> setPersistenceEnabled(bool enabled) {
4854
return _channel.invokeMethod(
49-
"FirebaseDatabase#setPersistenceEnabled",
50-
<String, dynamic>{'enabled': enabled},
55+
'FirebaseDatabase#setPersistenceEnabled',
56+
<String, bool>{ 'enabled': enabled },
5157
);
5258
}
5359

60+
/// Attempts to set the size of the persistence cache.
61+
///
5462
/// By default the Firebase Database client will use up to 10MB of disk space
5563
/// to cache data. If the cache grows beyond this size, the client will start
5664
/// removing data that hasn’t been recently used. If you find that your
5765
/// application caches too little or too much data, call this method to change
58-
/// the cache size. This property must be set before creating your first
59-
/// FIRDatabaseReference and only needs to be called once per application.
66+
/// the cache size.
67+
///
68+
/// This property must be set before calling methods on database references
69+
/// and only needs to be called once per application. The returned `Future`
70+
/// will complete with `true` if the operation was successful or `false` if
71+
/// the value could not be set (because database references have already been
72+
/// created).
6073
///
6174
/// Note that the specified cache size is only an approximation and the size
6275
/// on disk may temporarily exceed it at times. Cache sizes smaller than 1 MB
6376
/// or greater than 100 MB are not supported.
64-
Future<Null> setPersistenceCacheSizeBytes(int cacheSize) {
77+
Future<bool> setPersistenceCacheSizeBytes(int cacheSize) {
6578
return _channel.invokeMethod(
66-
"FirebaseDatabase#setPersistenceCacheSizeBytes",
67-
<String, dynamic>{'cacheSize': cacheSize},
79+
'FirebaseDatabase#setPersistenceCacheSizeBytes',
80+
<String, int>{ 'cacheSize': cacheSize },
6881
);
6982
}
7083

7184
/// Resumes our connection to the Firebase Database backend after a previous
72-
/// goOffline call.
85+
/// [goOffline] call.
7386
Future<Null> goOnline() {
74-
return _channel.invokeMethod("FirebaseDatabase#goOnline");
87+
return _channel.invokeMethod('FirebaseDatabase#goOnline');
7588
}
7689

77-
/// Shuts down our connection to the Firebase Database backend until goOnline
78-
/// is called.
90+
/// Shuts down our connection to the Firebase Database backend until
91+
/// [goOnline] is called.
7992
Future<Null> goOffline() {
80-
return _channel.invokeMethod("FirebaseDatabase#goOffline");
93+
return _channel.invokeMethod('FirebaseDatabase#goOffline');
8194
}
8295

8396
/// The Firebase Database client automatically queues writes and sends them to
@@ -91,6 +104,6 @@ class FirebaseDatabase {
91104
/// affected event listeners, and the client will not (re-)send them to the
92105
/// Firebase Database backend.
93106
Future<Null> purgeOutstandingWrites() {
94-
return _channel.invokeMethod("FirebaseDatabase#purgeOutstandingWrites");
107+
return _channel.invokeMethod('FirebaseDatabase#purgeOutstandingWrites');
95108
}
96109
}

0 commit comments

Comments
 (0)