diff --git a/packages/firebase_database/CHANGELOG.md b/packages/firebase_database/CHANGELOG.md index 4e035330e2e5..16a9a963de67 100644 --- a/packages/firebase_database/CHANGELOG.md +++ b/packages/firebase_database/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.0.6 + +* Various APIs added to FirebaseDatabase and Query +* Added removal and priority to DatabaseReference +* Improved documentation +* Added unit tests + ## 0.0.5 * Fixed analyzer warnings diff --git a/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/FirebaseDatabasePlugin.java b/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/FirebaseDatabasePlugin.java index 41a8fb402075..6d6a372f442b 100755 --- a/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/FirebaseDatabasePlugin.java +++ b/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/FirebaseDatabasePlugin.java @@ -118,38 +118,95 @@ public void onDataChange(DataSnapshot snapshot) { @SuppressWarnings("unchecked") @Override public void onMethodCall(MethodCall call, final Result result) { - Map arguments = (Map) call.arguments; - if (call.method.equals("DatabaseReference#set")) { - Object value = arguments.get("value"); - getReference(arguments).setValue(value, new DefaultCompletionListener(result)); - } else if (call.method.equals("Query#observe")) { - 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); - } else { - getReference(arguments).addChildEventListener(observer); - } - result.success(handle); - } else if (call.method.equals("Query#removeObserver")) { - DatabaseReference reference = getReference(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); - } else { - reference.removeEventListener((ChildEventListener) observer); + Map arguments = (Map) call.arguments; + switch (call.method) { + case "FirebaseDatabase#goOnline": + FirebaseDatabase.getInstance().goOnline(); + break; + + case "FirebaseDatabase#goOffline": + FirebaseDatabase.getInstance().goOffline(); + break; + + case "FirebaseDatabase#purgeOutstandingWrites": + FirebaseDatabase.getInstance().purgeOutstandingWrites(); + break; + + case "FirebaseDatabase#setPersistenceEnabled": + { + boolean isEnabled = (boolean) arguments.get("enabled"); + FirebaseDatabase.getInstance().setPersistenceEnabled(isEnabled); + break; + } + + case "FirebaseDatabase#setPersistenceCacheSizeBytes": + { + long cacheSize = (long) arguments.get("cacheSize"); + FirebaseDatabase.getInstance().setPersistenceCacheSizeBytes(cacheSize); + break; + } + + case "DatabaseReference#set": + { + Object value = arguments.get("value"); + Object priority = arguments.get("priority"); + DatabaseReference reference = getReference(arguments); + if (priority != null) { + reference.setValue(value, priority, new DefaultCompletionListener(result)); + } else { + reference.setValue(value, new DefaultCompletionListener(result)); + } + break; + } + + case "DatabaseReference#setPriority": + { + Object priority = arguments.get("priority"); + DatabaseReference reference = getReference(arguments); + reference.setPriority(priority, new DefaultCompletionListener(result)); + break; + } + + case "Query#observe": + { + 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); + } else { + getReference(arguments).addChildEventListener(observer); + } + result.success(handle); + break; + } + + case "Query#removeObserver": + { + DatabaseReference reference = getReference(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); + } else { + reference.removeEventListener((ChildEventListener) observer); + } + observers.delete(handle); + result.success(null); + break; + } else { + result.error("unknown_handle", "removeObserver called on an unknown handle", null); + break; + } + } + + default: + { + result.notImplemented(); + break; } - observers.delete(handle); - result.success(null); - } else { - result.error("unknown_handle", "removeObserver called on an unknown handle", null); - } - } else { - result.notImplemented(); } } } diff --git a/packages/firebase_database/ios/Classes/FirebaseDatabasePlugin.m b/packages/firebase_database/ios/Classes/FirebaseDatabasePlugin.m index 6a6e4bc20841..bf4cbd9e8f5e 100644 --- a/packages/firebase_database/ios/Classes/FirebaseDatabasePlugin.m +++ b/packages/firebase_database/ios/Classes/FirebaseDatabasePlugin.m @@ -21,6 +21,42 @@ - (FlutterError *)flutterError { return ref; } +FIRDatabaseQuery *getQuery(NSDictionary *arguments) { + FIRDatabaseQuery *query = getReference(arguments); + NSDictionary *parameters = arguments[@"parameters"]; + NSString *orderBy = parameters[@"orderBy"]; + if ([orderBy isEqualToString:@"child"]) { + query = [query queryOrderedByChild:parameters[@"orderByChildKey"]]; + } else if ([orderBy isEqualToString:@"key"]) { + query = [query queryOrderedByKey]; + } else if ([orderBy isEqualToString:@"value"]) { + query = [query queryOrderedByValue]; + } else if ([orderBy isEqualToString:@"priority"]) { + query = [query queryOrderedByPriority]; + } + id startAt = parameters[@"startAt"]; + if (startAt) { + query = [query queryStartingAtValue:startAt childKey:parameters[@"endAtKey"]]; + } + id endAt = parameters[@"endAt"]; + if (endAt) { + query = [query queryEndingAtValue:endAt childKey:parameters[@"endAtKey"]]; + } + id equalTo = parameters[@"equalTo"]; + if (equalTo) { + query = [query queryEqualToValue:equalTo]; + } + NSNumber *limitToFirst = parameters[@"limitToFirst"]; + if (limitToFirst) { + query = [query queryLimitedToFirst:limitToFirst.intValue]; + } + NSNumber *limitToLast = parameters[@"limitToLast"]; + if (limitToLast) { + query = [query queryLimitedToLast:limitToLast.intValue]; + } + return query; +} + FIRDataEventType parseEventType(NSString *eventTypeString) { if ([@"_EventType.childAdded" isEqual:eventTypeString]) { return FIRDataEventTypeChildAdded; @@ -67,12 +103,28 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result ^(NSError *error, FIRDatabaseReference *ref) { result(error.flutterError); }; - if ([@"DatabaseReference#set" isEqualToString:call.method]) { + if ([@"FirebaseDatabase#goOnline" isEqualToString:call.method]) { + [[FIRDatabase database] goOnline]; + } else if ([@"FirebaseDatabase#goOffline" isEqualToString:call.method]) { + [[FIRDatabase database] goOffline]; + } else if ([@"FirebaseDatabase#purgeOutstandingWrites" isEqualToString:call.method]) { + [[FIRDatabase database] purgeOutstandingWrites]; + } else if ([@"FirebaseDatabase#setPersistenceEnabled" isEqualToString:call.method]) { + NSNumber *value = call.arguments[@"value"]; + [FIRDatabase database].persistenceEnabled = value.boolValue; + } else if ([@"FirebaseDatabase#setPersistenceCacheSizeBytes" isEqualToString:call.method]) { + NSNumber *value = call.arguments[@"value"]; + [FIRDatabase database].persistenceCacheSizeBytes = value.unsignedIntegerValue; + } else if ([@"DatabaseReference#set" isEqualToString:call.method]) { [getReference(call.arguments) setValue:call.arguments[@"value"] + andPriority:call.arguments[@"priority"] withCompletionBlock:defaultCompletionBlock]; + } else if ([@"DatabaseReference#setPriority" isEqualToString:call.method]) { + [getReference(call.arguments) setPriority:call.arguments[@"priority"] + withCompletionBlock:defaultCompletionBlock]; } else if ([@"Query#observe" isEqualToString:call.method]) { FIRDataEventType eventType = parseEventType(call.arguments[@"eventType"]); - __block FIRDatabaseHandle handle = [getReference(call.arguments) + __block FIRDatabaseHandle handle = [getQuery(call.arguments) observeEventType:eventType andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousSiblingKey) { [self.channel invokeMethod:@"Event" @@ -88,8 +140,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result result([NSNumber numberWithUnsignedInteger:handle]); } else if ([@"Query#removeObserver" isEqualToString:call.method]) { FIRDatabaseHandle handle = [call.arguments[@"handle"] unsignedIntegerValue]; - [getReference(call.arguments) removeObserverWithHandle:handle]; + [getQuery(call.arguments) removeObserverWithHandle:handle]; result(nil); + } else if ([@"Query#keepSynced" isEqualToString:call.method]) { + NSNumber *value = call.arguments[@"value"]; + [getQuery(call.arguments) keepSynced:value]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/firebase_database/lib/src/database_reference.dart b/packages/firebase_database/lib/src/database_reference.dart index 58f637c9cefb..3c48f7864794 100644 --- a/packages/firebase_database/lib/src/database_reference.dart +++ b/packages/firebase_database/lib/src/database_reference.dart @@ -14,7 +14,7 @@ part of firebase_database; /// `DatabaseReference`s (ie. `child`). class DatabaseReference extends Query { DatabaseReference._(FirebaseDatabase database, List pathComponents) - : super._(database, pathComponents); + : super._(database: database, pathComponents: pathComponents); /// Gets a DatabaseReference for the location at the specified relative /// path. The relative path can either be a simple child key (e.g. ‘fred’) or @@ -60,7 +60,7 @@ class DatabaseReference extends Query { return new DatabaseReference._(_database, childPath); } - /// Write data to this Firebase Database location. + /// Write `value` to the location with the specified `priority` if applicable. /// /// This will overwrite any data at this location and all child locations. /// @@ -72,12 +72,53 @@ class DatabaseReference extends Query { /// /// Passing null for the new value means all data at this location or any /// child location will be deleted. - Future set(dynamic value) async { - await _database._channel.invokeMethod( + Future set(dynamic value, { dynamic priority }) { + return _database._channel.invokeMethod( 'DatabaseReference#set', - { 'path': path, 'value': value }, + { 'path': path, 'value': value, 'priority': priority }, ); } + + /// Sets a priority for the data at this Firebase Database location. + /// + /// Priorities can be used to provide a custom ordering for the children at a + /// location (if no priorities are specified, the children are ordered by + /// key). + /// + /// You cannot set a priority on an empty location. For this reason + /// set() should be used when setting initial data with a specific priority + /// and setPriority() should be used when updating the priority of existing + /// data. + /// + /// Children are sorted based on this priority using the following rules: + /// + /// Children with no priority come first. Children with a number as their + /// priority come next. They are sorted numerically by priority (small to + /// large). Children with a string as their priority come last. They are + /// sorted lexicographically by priority. Whenever two children have the same + /// 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 + Future setPriority(dynamic priority) async { + return _database._channel.invokeMethod( + 'DatabaseReference#setPriority', + { 'path': path, 'priority': priority }, + ); + } + + /// Remove the data at this Firebase Database location. Any data at child + /// locations will also be deleted. + /// + /// The effect of the delete will be visible immediately and the corresponding + /// events will be triggered. Synchronization of the delete to the Firebase + /// Database servers will also be started. + /// + /// remove() is equivalent to calling set(null) + Future remove() => set(null); } class ServerValue { static const timestamp = const {'.sv' : 'timestamp'}; diff --git a/packages/firebase_database/lib/src/event.dart b/packages/firebase_database/lib/src/event.dart index 07bed7cbbfd7..f3b902a6b526 100644 --- a/packages/firebase_database/lib/src/event.dart +++ b/packages/firebase_database/lib/src/event.dart @@ -9,7 +9,7 @@ enum _EventType { childRemoved, childChanged, childMoved, - value + value, } /// `Event` encapsulates a DataSnapshot and possibly also the key of its diff --git a/packages/firebase_database/lib/src/firebase_database.dart b/packages/firebase_database/lib/src/firebase_database.dart index fbbcf89ec179..6e496f8c3405 100644 --- a/packages/firebase_database/lib/src/firebase_database.dart +++ b/packages/firebase_database/lib/src/firebase_database.dart @@ -5,15 +5,16 @@ part of firebase_database; /// The entry point for accessing a Firebase Database. You can get an instance -/// by calling `FirebaseDatabase.instance`. To access a location in the database and -/// read or write data, use `reference()`. +/// by calling `FirebaseDatabase.instance`. To access a location in the database +/// and read or write data, use `reference()`. class FirebaseDatabase { - final MethodChannel _channel; + final MethodChannel _channel = const MethodChannel( + 'plugins.flutter.io/firebase_database', + ); static final Map _observers = {}; - @visibleForTesting - FirebaseDatabase.private(this._channel) { + FirebaseDatabase._() { _channel.setMethodCallHandler((MethodCall call) { if (call.method == 'Event') { Event event = new Event._(call.arguments); @@ -22,17 +23,73 @@ class FirebaseDatabase { }); } - factory FirebaseDatabase() { - return new FirebaseDatabase.private( - const MethodChannel('plugins.flutter.io/firebase_database'), - ); - } + static FirebaseDatabase _instance = new FirebaseDatabase._(); - static FirebaseDatabase _instance = new FirebaseDatabase(); - - // Gets the instance of FirebaseDatabase for the default Firebase app. + /// Gets the instance of FirebaseDatabase for the default Firebase app. static FirebaseDatabase get instance => _instance; /// Gets a DatabaseReference for the root of your Firebase Database. DatabaseReference reference() => new DatabaseReference._(this, []); + + /// The Firebase Database client will cache synchronized data and keep track + /// of all writes you’ve initiated while your application is running. It + /// seamlessly handles intermittent network connections and re-sends write + /// operations when the network connection is restored. + /// + /// However by default your write operations and cached data are only stored + /// in-memory and will be lost when your app restarts. By setting this value + /// to YES, the data will be persisted to on-device (disk) storage and will + /// thus be available again when the app is restarted (even when there is no + /// network connectivity at that time). Note that this property must be set + /// before creating your first Database reference and only needs to be called + /// once per application. + Future setPersistenceEnabled(bool enabled) { + return _channel.invokeMethod( + "FirebaseDatabase#setPersistenceEnabled", + { 'enabled': enabled }, + ); + } + + /// By default the Firebase Database client will use up to 10MB of disk space + /// to cache data. If the cache grows beyond this size, the client will start + /// removing data that hasn’t been recently used. If you find that your + /// application caches too little or too much data, call this method to change + /// the cache size. This property must be set before creating your first + /// FIRDatabaseReference and only needs to be called once per application. + /// + /// Note that the specified cache size is only an approximation and the size + /// on disk may temporarily exceed it at times. Cache sizes smaller than 1 MB + /// or greater than 100 MB are not supported. + Future setPersistenceCacheSizeBytes(int cacheSize) { + return _channel.invokeMethod( + "FirebaseDatabase#setPersistenceCacheSizeBytes", + { 'cacheSize': cacheSize }, + ); + } + + /// Resumes our connection to the Firebase Database backend after a previous + /// goOffline call. + Future goOnline() { + return _channel.invokeMethod("FirebaseDatabase#goOnline"); + } + + /// Shuts down our connection to the Firebase Database backend until goOnline + /// is called. + Future goOffline() { + return _channel.invokeMethod("FirebaseDatabase#goOffline"); + } + + /// The Firebase Database client automatically queues writes and sends them to + /// the server at the earliest opportunity, depending on network connectivity. + /// In some cases (e.g. offline usage) there may be a large number of writes + /// waiting to be sent. Calling this method will purge all outstanding writes + /// so they are abandoned. + /// + /// All writes will be purged, including transactions and onDisconnect writes. + /// The writes will be rolled back locally, perhaps triggering events for + /// affected event listeners, and the client will not (re-)send them to the + /// Firebase Database backend. + Future purgeOutstandingWrites() { + return _channel.invokeMethod("FirebaseDatabase#purgeOutstandingWrites"); + } } diff --git a/packages/firebase_database/lib/src/query.dart b/packages/firebase_database/lib/src/query.dart index fdaa879977c9..9f78839aa71c 100644 --- a/packages/firebase_database/lib/src/query.dart +++ b/packages/firebase_database/lib/src/query.dart @@ -6,42 +6,70 @@ part of firebase_database; /// Represents a query over the data at a particular location. class Query { - Query._(this._database, this._pathComponents) - : path = _pathComponents.join('/'); + Query._({ + @required FirebaseDatabase database, + @required List pathComponents, + Map parameters + }): _database = database, + _pathComponents = pathComponents, + _parameters = parameters ?? new Map.unmodifiable({}), + assert(database != null); final FirebaseDatabase _database; final List _pathComponents; + final Map _parameters; /// Slash-delimited path representing the database location of this query. - final String path; + String get path => _pathComponents.join('/'); + + Query _copyWithParameters(Map parameters) { + return new Query._( + database: _database, + pathComponents: _pathComponents, + parameters: new Map.unmodifiable( + new Map.from(_parameters)..addAll(parameters), + ), + ); + } + + Map buildArguments() { + return new Map.from(_parameters)..addAll({ + 'path': path, + }); + } Stream _observe(_EventType eventType) { Future _handle; - // TODO(collinjackson): fix close_sinks analyzer warning. + // It's fine to let the StreamController be garbage collected once all the + // subscribers have cancelled; this analyzer warning is safe to ignore. StreamController controller; // ignore: close_sinks controller = new StreamController.broadcast( - onListen: () async { + onListen: () { _handle = _database._channel.invokeMethod( - 'Query#observe', - { 'path': path, 'eventType': eventType.toString() }, + 'Query#observe', { + 'path': path, + 'parameters': _parameters, + 'eventType': eventType.toString(), + }, ); _handle.then((int handle) { FirebaseDatabase._observers[handle] = controller; }); }, - onCancel: () async { - int handle = await _handle; - await _database._channel.invokeMethod( - 'Query#removeObserver', - { 'handle': handle }, - ); - FirebaseDatabase._observers.remove(handle); + onCancel: () { + _handle.then((int handle) async { + await _database._channel.invokeMethod( + 'Query#removeObserver', + { 'handle': handle }, + ); + FirebaseDatabase._observers.remove(handle); + }); }, ); return controller.stream; } - /// Gets a single value event. + /// Listens for a single value event and then stops listening. Future once() async => (await onValue.first).snapshot; /// Fires when children are added. @@ -58,4 +86,90 @@ class Query { /// Fires the data at this location is updated. `previousChildKey` is null. Stream get onValue => _observe(_EventType.value); + + /// Create a query constrained to only return child nodes with a value greater + /// than or equal to the given value, using the given orderBy directive or + /// priority as default, and optionally only child nodes with a key greater + /// than or equal to the given key. + Query startAt(dynamic value, { String key }) { + assert(!_parameters.containsKey('startAt')); + return _copyWithParameters({ 'startAt': value, 'startAtKey': key}); + } + + /// Create a query constrained to only return child nodes with a value less + /// than or equal to the given value, using the given orderBy directive or + /// priority as default, and optionally only child nodes with a key less + /// than or equal to the given key. + Query endAt(dynamic value, { String key }) { + assert(!_parameters.containsKey('endAt')); + return _copyWithParameters({ 'endAt': value, 'endAtKey': key}); + } + + /// Create a query constrained to only return child nodes with the given + /// `value` (and `key`, if provided). + /// + /// If a key is provided, there is at most one such child as names are unique. + Query equalTo(dynamic value, { String key }) { + assert(!_parameters.containsKey('equalTo')); + return _copyWithParameters({ 'equalTo': value, 'equalToKey': key }); + } + + /// Create a query with limit and anchor it to the start of the window. + Query limitToFirst(int limit) { + assert(!_parameters.containsKey('limitToFirst')); + return _copyWithParameters({ 'limitToFirst': limit }); + } + + /// Create a query with limit and anchor it to the end of the window. + Query limitToLast(int limit) { + assert(!_parameters.containsKey('limitToLast')); + return _copyWithParameters({ 'limitToLast': limit }); + } + + /// Generate a view of the data sorted by values of a particular child key. + /// + /// Intended to be used in combination with startAt(), endAt(), or equalTo(). + Query orderByChild(String key) { + assert(key != null); + assert(!_parameters.containsKey('orderBy')); + return _copyWithParameters({ 'orderBy': 'child', 'orderByChildKey': key }); + } + + /// Generate a view of the data sorted by key. + /// + /// Intended to be used in combination with startAt(), endAt(), or equalTo(). + Query orderByKey() { + assert(!_parameters.containsKey('orderBy')); + return _copyWithParameters({ 'orderBy': 'key' }); + } + + /// Generate a view of the data sorted by value. + /// + /// Intended to be used in combination with startAt(), endAt(), or equalTo(). + Query orderByValue() { + assert(!_parameters.containsKey('orderBy')); + return _copyWithParameters({ 'orderBy': 'value' }); + } + + /// Generate a view of the data sorted by priority. + /// + /// Intended to be used in combination with startAt(), endAt(), or equalTo(). + Query orderByPriority() { + assert(!_parameters.containsKey('orderBy')); + return _copyWithParameters({ 'orderBy': 'priority' }); + } + + /// Obtains a DatabaseReference corresponding to this query's location. + DatabaseReference reference() => new DatabaseReference._(_database, _pathComponents); + + /// By calling keepSynced(true) on a location, the data for that location will + /// automatically be downloaded and kept in sync, even when no listeners are + /// attached for that location. Additionally, while a location is kept synced, + /// it will not be evicted from the persistent disk cache. + Future keepSynced(bool value) { + return _database._channel.invokeMethod( + 'Query#keepSynced', + { 'path': path, 'parameters': _parameters, 'value': value }, + ); + } } diff --git a/packages/firebase_database/pubspec.yaml b/packages/firebase_database/pubspec.yaml index 0c8cfb18ce04..98b7cdfeeb30 100755 --- a/packages/firebase_database/pubspec.yaml +++ b/packages/firebase_database/pubspec.yaml @@ -3,7 +3,7 @@ name: firebase_database description: Firebase Database plugin for Flutter. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/firebase_database -version: 0.0.5 +version: 0.0.6 flutter: plugin: diff --git a/packages/firebase_database/test/firebase_database_test.dart b/packages/firebase_database/test/firebase_database_test.dart new file mode 100755 index 000000000000..f6de8e0d0f77 --- /dev/null +++ b/packages/firebase_database/test/firebase_database_test.dart @@ -0,0 +1,135 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import 'package:flutter/services.dart'; + +import 'package:firebase_database/firebase_database.dart'; + +void main() { + group('$FirebaseDatabase', () + { + const MethodChannel channel = const MethodChannel( + 'plugins.flutter.io/firebase_database', + ); + + int mockHandleId = 0; + final List log = []; + final FirebaseDatabase database = FirebaseDatabase.instance; + + setUp(() async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + if (methodCall.method == 'Query#observe') { + return mockHandleId++; + } + return null; + }); + log.clear(); + }); + + test('setPersistenceEnabled', () async { + await database.setPersistenceEnabled(false); + await database.setPersistenceEnabled(true); + expect(log, equals([ + new MethodCall('FirebaseDatabase#setPersistenceEnabled', { 'enabled': false }), + new MethodCall('FirebaseDatabase#setPersistenceEnabled', { 'enabled': true }), + ])); + }); + + test('setPersistentCacheSizeBytes', () async { + await database.setPersistenceCacheSizeBytes(42); + expect(log, equals([ + new MethodCall( + 'FirebaseDatabase#setPersistenceCacheSizeBytes', + { 'cacheSize': 42 }, + ), + ])); + }); + + test('goOnline', () async { + await database.goOnline(); + expect(log, equals([ + new MethodCall('FirebaseDatabase#goOnline'), + ])); + }); + + test('goOffline', () async { + await database.goOffline(); + expect(log, equals([ + new MethodCall('FirebaseDatabase#goOffline'), + ])); + }); + + test('purgeOutstandingWrites', () async { + await database.purgeOutstandingWrites(); + expect(log, equals([ + new MethodCall('FirebaseDatabase#purgeOutstandingWrites'), + ])); + }); + + group('$DatabaseReference', () { + test('set', () async { + dynamic value = {'hello': 'world'}; + int priority = 42; + await database.reference().child('foo').set(value); + await database.reference().child('bar').set(value, priority: priority); + expect(log, equals([ + new MethodCall( + 'DatabaseReference#set', + { 'path': 'foo', 'value': value, 'priority': null }, + ), + new MethodCall( + 'DatabaseReference#set', + { 'path': 'bar', 'value': value, 'priority': priority }, + ), + ])); + }); + + test('setPriority', () async { + int priority = 42; + await database.reference().child('foo').setPriority(priority); + expect(log, equals([ + new MethodCall( + 'DatabaseReference#setPriority', + { 'path': 'foo', 'priority': priority }, + ), + ])); + }); + }); + + group('$Query', () { + // TODO(jackson): Write more tests for queries + test('observing', () async { + Query query = database.reference().child('foo').orderByChild('bar'); + StreamSubscription subscription = query.onValue.listen((_) {}); + await query.keepSynced(true); + subscription.cancel(); + Map expectedParameters = { + 'orderBy': 'child', + 'orderByChildKey': 'bar', + }; + expect(log, equals([ + new MethodCall( + 'Query#observe', + { 'path': 'foo', + 'parameters': expectedParameters, + 'eventType': '_EventType.value' + }, + ), + new MethodCall( + 'Query#keepSynced', + { 'path': 'foo', 'parameters': expectedParameters, 'value': true}, + ), + ])); + }); + }); + }); +} + +class MockPlatformChannel extends Mock implements MethodChannel { } \ No newline at end of file