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

Commit

Permalink
Firebase database query and persistence improvements (#144)
Browse files Browse the repository at this point in the history
## 0.0.8

* Added missing offline persistence and query functionality on Android
* Fixed startAt query behavior on iOS
* Persistence methods no longer throw errors on failure, return false instead
* Updates to docs and tests
  • Loading branch information
collinjackson authored Jun 20, 2017
1 parent 516ac48 commit 2588a38
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 113 deletions.
7 changes: 7 additions & 0 deletions packages/firebase_database/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.0.8

* Added missing offline persistence and query functionality on Android
* Fixed startAt query behavior on iOS
* Persistence methods no longer throw errors on failure, return false instead
* Updates to docs and tests

## 0.0.7

* Fixed offline persistence on iOS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
Expand All @@ -31,7 +33,7 @@ public class FirebaseDatabasePlugin implements MethodCallHandler {

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

public static void registerWith(PluginRegistry.Registrar registrar) {
final MethodChannel channel =
Expand All @@ -43,14 +45,75 @@ private FirebaseDatabasePlugin(MethodChannel channel) {
this.channel = channel;
}

private static DatabaseReference getReference(Map<String, ?> arguments) {
@SuppressWarnings("unchecked")
private DatabaseReference getReference(Map<String, Object> arguments) {
String path = (String) arguments.get("path");
DatabaseReference reference = FirebaseDatabase.getInstance().getReference();
if (path != null) reference = reference.child(path);
return reference;
}

private Query getQuery(Map<String, Object> arguments) {
Query query = getReference(arguments);
@SuppressWarnings("unchecked")
Map<String, Object> parameters = (Map<String, Object>) arguments.get("parameters");
if (parameters == null) return query;
Object orderBy = parameters.get("orderBy");
if ("child".equals(orderBy)) {
query = query.orderByChild((String) parameters.get("orderByChildKey"));
} else if ("key".equals(orderBy)) {
query = query.orderByKey();
} else if ("value".equals(orderBy)) {
query = query.orderByValue();
} else if ("priority".equals(orderBy)) {
query = query.orderByPriority();
}
if (parameters.containsKey("startAt")) {
Object startAt = parameters.get("startAt");
String startAtKey = (String) parameters.get("startAtKey");
if (startAt instanceof Boolean) {
query = query.startAt((Boolean) startAt, startAtKey);
} else if (startAt instanceof String) {
query = query.startAt((String) startAt, startAtKey);
} else if (startAt instanceof Double) {
query = query.endAt((Double) startAt);
} else if (startAt instanceof Integer) {
query = query.startAt((int) startAt);
}
}
if (parameters.containsKey("endAt")) {
Object endAt = parameters.get("endAt");
String endAtKey = (String) parameters.get("endAtKey");
if (endAt instanceof Boolean) {
query = query.endAt((Boolean) endAt, endAtKey);
} else if (endAt instanceof String) {
query = query.endAt((String) endAt, endAtKey);
} else if (endAt instanceof Double) {
query = query.endAt((Double) endAt);
} else if (endAt instanceof Integer) {
query = query.endAt((int) endAt);
}
}
if (parameters.containsKey("equalTo")) {
Object equalTo = parameters.get("equalTo");
if (equalTo instanceof Boolean) {
query = query.equalTo((Boolean) equalTo);
} else if (equalTo instanceof String) {
query = query.equalTo((String) equalTo);
} else if (equalTo instanceof Double) {
query = query.equalTo((Double) equalTo);
} else if (equalTo instanceof Integer) {
query = query.equalTo((int) equalTo);
}
}
if (parameters.containsKey("limitToFirst")) {
query = query.limitToFirst((int) parameters.get("limitToFirst"));
}
if (parameters.containsKey("limitToLast")) {
query = query.limitToLast((int) parameters.get("limitToLast"));
}
return query;
}

private class DefaultCompletionListener implements DatabaseReference.CompletionListener {
private final Result result;

Expand Down Expand Up @@ -79,8 +142,8 @@ private class EventObserver implements ChildEventListener, ValueEventListener {

private void sendEvent(String eventType, DataSnapshot snapshot, String previousChildName) {
if (eventType.equals(requestedEventType)) {
Map<String, Object> arguments = new HashMap<String, Object>();
Map<String, Object> snapshotMap = new HashMap<String, Object>();
Map<String, Object> arguments = new HashMap<>();
Map<String, Object> snapshotMap = new HashMap<>();
snapshotMap.put("key", snapshot.getKey());
snapshotMap.put("value", snapshot.getValue());
arguments.put("handle", handle);
Expand Down Expand Up @@ -119,39 +182,59 @@ public void onDataChange(DataSnapshot snapshot) {
}
}

@SuppressWarnings("unchecked")
@Override
public void onMethodCall(MethodCall call, final Result result) {
Map<String, Object> arguments = (Map<String, Object>) call.arguments;
switch (call.method) {
case "FirebaseDatabase#goOnline":
FirebaseDatabase.getInstance().goOnline();
break;
{
FirebaseDatabase.getInstance().goOnline();
result.success(null);
break;
}

case "FirebaseDatabase#goOffline":
FirebaseDatabase.getInstance().goOffline();
break;
{
FirebaseDatabase.getInstance().goOffline();
result.success(null);
break;
}

case "FirebaseDatabase#purgeOutstandingWrites":
FirebaseDatabase.getInstance().purgeOutstandingWrites();
break;
{
FirebaseDatabase.getInstance().purgeOutstandingWrites();
result.success(null);
break;
}

case "FirebaseDatabase#setPersistenceEnabled":
{
boolean isEnabled = (boolean) arguments.get("enabled");
FirebaseDatabase.getInstance().setPersistenceEnabled(isEnabled);
Boolean isEnabled = (Boolean) call.arguments;
try {
FirebaseDatabase.getInstance().setPersistenceEnabled(isEnabled);
result.success(true);
} catch (DatabaseException e) {
// Database is already in use, e.g. after hot reload/restart.
result.success(false);
}
break;
}

case "FirebaseDatabase#setPersistenceCacheSizeBytes":
{
long cacheSize = (long) arguments.get("cacheSize");
FirebaseDatabase.getInstance().setPersistenceCacheSizeBytes(cacheSize);
long cacheSize = (Integer) call.arguments;
try {
FirebaseDatabase.getInstance().setPersistenceCacheSizeBytes(cacheSize);
result.success(true);
} catch (DatabaseException e) {
// Database is already in use, e.g. after hot reload/restart.
result.success(false);
}
break;
}

case "DatabaseReference#set":
{
Map<String, Object> arguments = call.arguments();
Object value = arguments.get("value");
Object priority = arguments.get("priority");
DatabaseReference reference = getReference(arguments);
Expand All @@ -165,37 +248,48 @@ public void onMethodCall(MethodCall call, final Result result) {

case "DatabaseReference#setPriority":
{
Map<String, Object> arguments = call.arguments();
Object priority = arguments.get("priority");
DatabaseReference reference = getReference(arguments);
reference.setPriority(priority, new DefaultCompletionListener(result));
break;
}

case "Query#keepSynced":
{
Map<String, Object> arguments = call.arguments();
boolean value = (Boolean) arguments.get("value");
getQuery(arguments).keepSynced(value);
break;
}

case "Query#observe":
{
Map<String, Object> arguments = call.arguments();
String eventType = (String) arguments.get("eventType");
int handle = nextHandle++;
EventObserver observer = new EventObserver(eventType, handle);
observers.put(handle, observer);
if (eventType.equals(EVENT_TYPE_VALUE)) {
getReference(arguments).addValueEventListener(observer);
getQuery(arguments).addValueEventListener(observer);
} else {
getReference(arguments).addChildEventListener(observer);
getQuery(arguments).addChildEventListener(observer);
}
result.success(handle);
break;
}

case "Query#removeObserver":
{
DatabaseReference reference = getReference(arguments);
Map<String, Object> arguments = call.arguments();
Query query = getQuery(arguments);
int handle = (Integer) arguments.get("handle");
EventObserver observer = observers.get(handle);
if (observer != null) {
if (observer.requestedEventType.equals(EVENT_TYPE_VALUE)) {
reference.removeEventListener((ValueEventListener) observer);
query.removeEventListener((ValueEventListener) observer);
} else {
reference.removeEventListener((ChildEventListener) observer);
query.removeEventListener((ChildEventListener) observer);
}
observers.delete(handle);
result.success(null);
Expand Down
6 changes: 5 additions & 1 deletion packages/firebase_database/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
FirebaseDatabase.instance.setPersistenceEnabled(true);
FirebaseDatabase.instance.setPersistenceCacheSizeBytes(10000000);
_counterRef.keepSynced(true);
_counterSubscription = _counterRef.onValue.listen((Event event) {
setState(() {
_counter = event.snapshot.value ?? 0;
});
});
_messagesSubscription = _messagesRef.onChildAdded.listen((Event event) {
_messagesSubscription =
_messagesRef.limitToLast(10).onChildAdded.listen((Event event) {
print('Child added: ${event.snapshot.value}');
});
}
Expand Down
33 changes: 28 additions & 5 deletions packages/firebase_database/ios/Classes/FirebaseDatabasePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ - (FlutterError *)flutterError {
}
id startAt = parameters[@"startAt"];
if (startAt) {
query = [query queryStartingAtValue:startAt childKey:parameters[@"endAtKey"]];
query = [query queryStartingAtValue:startAt childKey:parameters[@"startAtKey"]];
}
id endAt = parameters[@"endAt"];
if (endAt) {
Expand Down Expand Up @@ -109,16 +109,39 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
};
if ([@"FirebaseDatabase#goOnline" isEqualToString:call.method]) {
[[FIRDatabase database] goOnline];
result(nil);
} else if ([@"FirebaseDatabase#goOffline" isEqualToString:call.method]) {
[[FIRDatabase database] goOffline];
result(nil);
} else if ([@"FirebaseDatabase#purgeOutstandingWrites" isEqualToString:call.method]) {
[[FIRDatabase database] purgeOutstandingWrites];
result(nil);
} else if ([@"FirebaseDatabase#setPersistenceEnabled" isEqualToString:call.method]) {
NSNumber *value = call.arguments[@"enabled"];
[FIRDatabase database].persistenceEnabled = value.boolValue;
NSNumber *value = call.arguments;
@try {
[FIRDatabase database].persistenceEnabled = value.boolValue;
result([NSNumber numberWithBool:YES]);
} @catch (NSException *exception) {
if ([@"FIRDatabaseAlreadyInUse" isEqualToString:exception.name]) {
// Database is already in use, e.g. after hot reload/restart.
result([NSNumber numberWithBool:NO]);
} else {
@throw;
}
}
} else if ([@"FirebaseDatabase#setPersistenceCacheSizeBytes" isEqualToString:call.method]) {
NSNumber *value = call.arguments[@"cacheSize"];
[FIRDatabase database].persistenceCacheSizeBytes = value.unsignedIntegerValue;
NSNumber *value = call.arguments;
@try {
[FIRDatabase database].persistenceCacheSizeBytes = value.unsignedIntegerValue;
result([NSNumber numberWithBool:YES]);
} @catch (NSException *exception) {
if ([@"FIRDatabaseAlreadyInUse" isEqualToString:exception.name]) {
// Database is already in use, e.g. after hot reload/restart.
result([NSNumber numberWithBool:NO]);
} else {
@throw;
}
}
} else if ([@"DatabaseReference#set" isEqualToString:call.method]) {
[getReference(call.arguments) setValue:call.arguments[@"value"]
andPriority:call.arguments[@"priority"]
Expand Down
4 changes: 2 additions & 2 deletions packages/firebase_database/lib/src/database_reference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ class DatabaseReference extends Query {
/// priority (including no priority), they are sorted by key. Numeric keys
/// come first (sorted numerically), followed by the remaining keys (sorted
/// lexicographically).
///
/// Note that priorities are parsed and ordered as IEEE 754 double-precision
/// floating-point numbers. Keys are always stored as strings and are treated
/// as numbers only when they can be parsed as a 32-bit integer
/// as numbers only when they can be parsed as a 32-bit integer.
Future<Null> setPriority(dynamic priority) async {
return _database._channel.invokeMethod(
'DatabaseReference#setPriority',
Expand Down
Loading

0 comments on commit 2588a38

Please sign in to comment.