From 2513474410e6d9a8df94091e0329a2e2440e26f7 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 13 Nov 2019 22:03:02 -0800 Subject: [PATCH] [firebase_database] Support v2 android embedder. (#287) --- packages/firebase_database/CHANGELOG.md | 5 + .../firebase_database/android/build.gradle | 25 + .../database/FirebaseDatabasePlugin.java | 525 +---------------- .../database/MethodCallHandlerImpl.java | 542 ++++++++++++++++++ .../android/app/src/main/AndroidManifest.xml | 10 +- .../EmbeddingV1Activity.java | 17 + .../EmbeddingV1ActivityTest.java | 17 + .../firebasedatabaseexample/MainActivity.java | 17 +- .../MainActivityTest.java | 15 + .../example/android/gradle.properties | 3 +- .../firebase_database/example/lib/main.dart | 1 + .../firebase_database/example/pubspec.yaml | 7 +- ...tabase.dart => firebase_database_e2e.dart} | 9 +- .../firebase_database_e2e_test.dart | 11 + .../test_driver/firebase_database_test.dart | 10 - packages/firebase_database/pubspec.yaml | 5 +- 16 files changed, 682 insertions(+), 537 deletions(-) create mode 100644 packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/MethodCallHandlerImpl.java create mode 100644 packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1Activity.java create mode 100644 packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1ActivityTest.java create mode 100644 packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivityTest.java rename packages/firebase_database/example/test_driver/{firebase_database.dart => firebase_database_e2e.dart} (72%) create mode 100644 packages/firebase_database/example/test_driver/firebase_database_e2e_test.dart delete mode 100644 packages/firebase_database/example/test_driver/firebase_database_test.dart diff --git a/packages/firebase_database/CHANGELOG.md b/packages/firebase_database/CHANGELOG.md index aa9a08bdcb25..02ae8b6de0c3 100644 --- a/packages/firebase_database/CHANGELOG.md +++ b/packages/firebase_database/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.1.0 + +* Support Android V2 embedding. +* Migrate to using the new e2e test binding. + ## 3.0.9 * Updated README instructions for contributing for consistency with other Flutterfire plugins. diff --git a/packages/firebase_database/android/build.gradle b/packages/firebase_database/android/build.gradle index 4c60f6c9dcf0..857e550455fc 100755 --- a/packages/firebase_database/android/build.gradle +++ b/packages/firebase_database/android/build.gradle @@ -38,3 +38,28 @@ android { } apply from: file("./user-agent.gradle") + +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} \ No newline at end of file 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 18911914764f..e007f932e725 100644 --- 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 @@ -4,529 +4,34 @@ package io.flutter.plugins.firebase.database; -import android.os.Handler; -import android.util.Log; -import android.util.SparseArray; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.android.gms.tasks.Tasks; -import com.google.firebase.FirebaseApp; -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.MutableData; -import com.google.firebase.database.Query; -import com.google.firebase.database.Transaction; -import com.google.firebase.database.ValueEventListener; -import io.flutter.plugin.common.MethodCall; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** FirebaseDatabasePlugin */ -public class FirebaseDatabasePlugin implements MethodCallHandler { +public class FirebaseDatabasePlugin implements FlutterPlugin { - private static final String TAG = "FirebaseDatabasePlugin"; - - private final MethodChannel channel; - private final Handler handler = new Handler(); - private static final String EVENT_TYPE_CHILD_ADDED = "_EventType.childAdded"; - private static final String EVENT_TYPE_CHILD_REMOVED = "_EventType.childRemoved"; - private static final String EVENT_TYPE_CHILD_CHANGED = "_EventType.childChanged"; - private static final String EVENT_TYPE_CHILD_MOVED = "_EventType.childMoved"; - private static final String EVENT_TYPE_VALUE = "_EventType.value"; - - // Handles are ints used as indexes into the sparse array of active observers - private int nextHandle = 0; - private final SparseArray observers = new SparseArray<>(); + private MethodChannel channel; public static void registerWith(PluginRegistry.Registrar registrar) { - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/firebase_database"); - channel.setMethodCallHandler(new FirebaseDatabasePlugin(channel)); - } - - private FirebaseDatabasePlugin(MethodChannel channel) { - this.channel = channel; - } - - private DatabaseReference getReference(FirebaseDatabase database, Map arguments) { - String path = (String) arguments.get("path"); - DatabaseReference reference = database.getReference(); - if (path != null) reference = reference.child(path); - return reference; + FirebaseDatabasePlugin plugin = new FirebaseDatabasePlugin(); + plugin.setupMethodChannel(registrar.messenger()); } - private Query getQuery(FirebaseDatabase database, Map arguments) { - Query query = getReference(database, arguments); - @SuppressWarnings("unchecked") - Map parameters = (Map) 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"); - if (parameters.containsKey("startAtKey")) { - String startAtKey = (String) parameters.get("startAtKey"); - if (startAt instanceof Boolean) { - query = query.startAt((Boolean) startAt, startAtKey); - } else if (startAt instanceof Number) { - query = query.startAt(((Number) startAt).doubleValue(), startAtKey); - } else { - query = query.startAt((String) startAt, startAtKey); - } - } else { - if (startAt instanceof Boolean) { - query = query.startAt((Boolean) startAt); - } else if (startAt instanceof Number) { - query = query.startAt(((Number) startAt).doubleValue()); - } else { - query = query.startAt((String) startAt); - } - } - } - if (parameters.containsKey("endAt")) { - Object endAt = parameters.get("endAt"); - if (parameters.containsKey("endAtKey")) { - String endAtKey = (String) parameters.get("endAtKey"); - if (endAt instanceof Boolean) { - query = query.endAt((Boolean) endAt, endAtKey); - } else if (endAt instanceof Number) { - query = query.endAt(((Number) endAt).doubleValue(), endAtKey); - } else { - query = query.endAt((String) endAt, endAtKey); - } - } else { - if (endAt instanceof Boolean) { - query = query.endAt((Boolean) endAt); - } else if (endAt instanceof Number) { - query = query.endAt(((Number) endAt).doubleValue()); - } else { - query = query.endAt((String) endAt); - } - } - } - if (parameters.containsKey("equalTo")) { - Object equalTo = parameters.get("equalTo"); - if (parameters.containsKey("equalToKey")) { - String equalToKey = (String) parameters.get("equalToKey"); - if (equalTo instanceof Boolean) { - query = query.equalTo((Boolean) equalTo, equalToKey); - } else if (equalTo instanceof Number) { - query = query.equalTo(((Number) equalTo).doubleValue(), equalToKey); - } else { - query = query.equalTo((String) equalTo, equalToKey); - } - } else { - if (equalTo instanceof Boolean) { - query = query.equalTo((Boolean) equalTo); - } else if (equalTo instanceof Number) { - query = query.equalTo(((Number) equalTo).doubleValue()); - } else { - query = query.equalTo((String) 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; - - DefaultCompletionListener(Result result) { - this.result = result; - } - - @Override - public void onComplete(@Nullable DatabaseError error, @NonNull DatabaseReference ref) { - if (error != null) { - result.error(String.valueOf(error.getCode()), error.getMessage(), error.getDetails()); - } else { - result.success(null); - } - } - } - - private class EventObserver implements ChildEventListener, ValueEventListener { - private String requestedEventType; - private int handle; - - EventObserver(String requestedEventType, int handle) { - this.requestedEventType = requestedEventType; - this.handle = handle; - } - - private void sendEvent( - String eventType, @NonNull DataSnapshot snapshot, String previousChildName) { - if (eventType.equals(requestedEventType)) { - Map arguments = new HashMap<>(); - Map snapshotMap = new HashMap<>(); - snapshotMap.put("key", snapshot.getKey()); - snapshotMap.put("value", snapshot.getValue()); - arguments.put("handle", handle); - arguments.put("snapshot", snapshotMap); - arguments.put("previousSiblingKey", previousChildName); - channel.invokeMethod("Event", arguments); - } - } - - @Override - public void onCancelled(@NonNull DatabaseError error) { - Map arguments = new HashMap<>(); - arguments.put("handle", handle); - arguments.put("error", asMap(error)); - channel.invokeMethod("Error", arguments); - } - - @Override - public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChildName) { - sendEvent(EVENT_TYPE_CHILD_ADDED, snapshot, previousChildName); - } - - @Override - public void onChildRemoved(@NonNull DataSnapshot snapshot) { - sendEvent(EVENT_TYPE_CHILD_REMOVED, snapshot, null); - } - - @Override - public void onChildChanged(@NonNull DataSnapshot snapshot, String previousChildName) { - sendEvent(EVENT_TYPE_CHILD_CHANGED, snapshot, previousChildName); - } - - @Override - public void onChildMoved(@NonNull DataSnapshot snapshot, String previousChildName) { - sendEvent(EVENT_TYPE_CHILD_MOVED, snapshot, previousChildName); - } - - @Override - public void onDataChange(@NonNull DataSnapshot snapshot) { - sendEvent(EVENT_TYPE_VALUE, snapshot, null); - } + private void setupMethodChannel(BinaryMessenger messenger) { + channel = new MethodChannel(messenger, "plugins.flutter.io/firebase_database"); + MethodCallHandlerImpl handler = new MethodCallHandlerImpl(channel); + channel.setMethodCallHandler(handler); } @Override - public void onMethodCall(final MethodCall call, @NonNull final Result result) { - final Map arguments = call.arguments(); - FirebaseDatabase database; - String appName = call.argument("app"); - String databaseURL = call.argument("databaseURL"); - if (appName != null && databaseURL != null) { - database = FirebaseDatabase.getInstance(FirebaseApp.getInstance(appName), databaseURL); - } else if (appName != null) { - database = FirebaseDatabase.getInstance(FirebaseApp.getInstance(appName)); - } else if (databaseURL != null) { - database = FirebaseDatabase.getInstance(databaseURL); - } else { - database = FirebaseDatabase.getInstance(); - } - switch (call.method) { - case "FirebaseDatabase#goOnline": - { - database.goOnline(); - result.success(null); - break; - } - - case "FirebaseDatabase#goOffline": - { - database.goOffline(); - result.success(null); - break; - } - - case "FirebaseDatabase#purgeOutstandingWrites": - { - database.purgeOutstandingWrites(); - result.success(null); - break; - } - - case "FirebaseDatabase#setPersistenceEnabled": - { - Boolean isEnabled = call.argument("enabled"); - try { - database.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 = call.argument("cacheSize"); - try { - database.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": - { - Object value = call.argument("value"); - Object priority = call.argument("priority"); - DatabaseReference reference = getReference(database, arguments); - if (priority != null) { - reference.setValue(value, priority, new DefaultCompletionListener(result)); - } else { - reference.setValue(value, new DefaultCompletionListener(result)); - } - break; - } - - case "DatabaseReference#update": - { - Map value = call.argument("value"); - DatabaseReference reference = getReference(database, arguments); - reference.updateChildren(value, new DefaultCompletionListener(result)); - break; - } - - case "DatabaseReference#setPriority": - { - Object priority = call.argument("priority"); - DatabaseReference reference = getReference(database, arguments); - reference.setPriority(priority, new DefaultCompletionListener(result)); - break; - } - - case "DatabaseReference#runTransaction": - { - final DatabaseReference reference = getReference(database, arguments); - - // Initiate native transaction. - reference.runTransaction( - new Transaction.Handler() { - @NonNull - @Override - public Transaction.Result doTransaction(@NonNull MutableData mutableData) { - // Tasks are used to allow native execution of doTransaction to wait while Snapshot is - // processed by logic on the Dart side. - final TaskCompletionSource> updateMutableDataTCS = - new TaskCompletionSource<>(); - final Task> updateMutableDataTCSTask = - updateMutableDataTCS.getTask(); - - final Map doTransactionMap = new HashMap<>(); - doTransactionMap.put("transactionKey", call.argument("transactionKey")); - - final Map snapshotMap = new HashMap<>(); - snapshotMap.put("key", mutableData.getKey()); - snapshotMap.put("value", mutableData.getValue()); - doTransactionMap.put("snapshot", snapshotMap); - - // Return snapshot to Dart side for update. - handler.post( - new Runnable() { - @Override - public void run() { - channel.invokeMethod( - "DoTransaction", - doTransactionMap, - new MethodChannel.Result() { - @Override - @SuppressWarnings("unchecked") - public void success(Object result) { - updateMutableDataTCS.setResult((Map) result); - } - - @Override - public void error( - String errorCode, String errorMessage, Object errorDetails) { - String exceptionMessage = - "Error code: " - + errorCode - + "\nError message: " - + errorMessage - + "\nError details: " - + errorDetails; - updateMutableDataTCS.setException( - new Exception(exceptionMessage)); - } - - @Override - public void notImplemented() { - updateMutableDataTCS.setException( - new Exception("DoTransaction not implemented on Dart side.")); - } - }); - } - }); - - try { - // Wait for updated snapshot from the Dart side. - final Map updatedSnapshotMap = - Tasks.await( - updateMutableDataTCSTask, - (int) arguments.get("transactionTimeout"), - TimeUnit.MILLISECONDS); - // Set value of MutableData to value returned from the Dart side. - mutableData.setValue(updatedSnapshotMap.get("value")); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - Log.e(TAG, "Unable to commit Snapshot update. Transaction failed.", e); - if (e instanceof TimeoutException) { - Log.e(TAG, "Transaction at " + reference.toString() + " timed out."); - } - return Transaction.abort(); - } - return Transaction.success(mutableData); - } - - @Override - public void onComplete( - DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) { - final Map completionMap = new HashMap<>(); - completionMap.put("transactionKey", call.argument("transactionKey")); - if (databaseError != null) { - completionMap.put("error", asMap(databaseError)); - } - completionMap.put("committed", committed); - if (dataSnapshot != null) { - Map snapshotMap = new HashMap<>(); - snapshotMap.put("key", dataSnapshot.getKey()); - snapshotMap.put("value", dataSnapshot.getValue()); - completionMap.put("snapshot", snapshotMap); - } - - // Invoke transaction completion on the Dart side. - handler.post( - new Runnable() { - public void run() { - result.success(completionMap); - } - }); - } - }); - break; - } - - case "OnDisconnect#set": - { - Object value = call.argument("value"); - Object priority = call.argument("priority"); - DatabaseReference reference = getReference(database, arguments); - if (priority != null) { - if (priority instanceof String) { - reference - .onDisconnect() - .setValue(value, (String) priority, new DefaultCompletionListener(result)); - } else if (priority instanceof Double) { - reference - .onDisconnect() - .setValue(value, (double) priority, new DefaultCompletionListener(result)); - } else if (priority instanceof Map) { - reference - .onDisconnect() - .setValue(value, (Map) priority, new DefaultCompletionListener(result)); - } - } else { - reference.onDisconnect().setValue(value, new DefaultCompletionListener(result)); - } - break; - } - - case "OnDisconnect#update": - { - Map value = call.argument("value"); - DatabaseReference reference = getReference(database, arguments); - reference.onDisconnect().updateChildren(value, new DefaultCompletionListener(result)); - break; - } - - case "OnDisconnect#cancel": - { - DatabaseReference reference = getReference(database, arguments); - reference.onDisconnect().cancel(new DefaultCompletionListener(result)); - break; - } - - case "Query#keepSynced": - { - Boolean value = call.argument("value"); - getQuery(database, arguments).keepSynced(value); - result.success(null); - break; - } - - case "Query#observe": - { - String eventType = call.argument("eventType"); - int handle = nextHandle++; - EventObserver observer = new EventObserver(eventType, handle); - observers.put(handle, observer); - if (EVENT_TYPE_VALUE.equals(eventType)) { - getQuery(database, arguments).addValueEventListener(observer); - } else { - getQuery(database, arguments).addChildEventListener(observer); - } - result.success(handle); - break; - } - - case "Query#removeObserver": - { - Query query = getQuery(database, arguments); - Integer handle = call.argument("handle"); - EventObserver observer = observers.get(handle); - if (observer != null) { - if (observer.requestedEventType.equals(EVENT_TYPE_VALUE)) { - query.removeEventListener((ValueEventListener) observer); - } else { - query.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; - } - } + public void onAttachedToEngine(FlutterPluginBinding binding) { + setupMethodChannel(binding.getFlutterEngine().getDartExecutor()); } - private static Map asMap(DatabaseError error) { - Map map = new HashMap<>(); - map.put("code", error.getCode()); - map.put("message", error.getMessage()); - map.put("details", error.getDetails()); - return map; + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); } } diff --git a/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/MethodCallHandlerImpl.java b/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..03dbf8f77117 --- /dev/null +++ b/packages/firebase_database/android/src/main/java/io/flutter/plugins/firebase/database/MethodCallHandlerImpl.java @@ -0,0 +1,542 @@ +// Copyright 2019 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. + +package io.flutter.plugins.firebase.database; + +import android.os.Handler; +import android.util.Log; +import android.util.SparseArray; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.FirebaseApp; +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.MutableData; +import com.google.firebase.database.Query; +import com.google.firebase.database.Transaction; +import com.google.firebase.database.ValueEventListener; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + + private static final String TAG = "MethodCallHandlerImpl"; + + private MethodChannel channel; + + private final Handler handler = new Handler(); + private static final String EVENT_TYPE_CHILD_ADDED = "_EventType.childAdded"; + private static final String EVENT_TYPE_CHILD_REMOVED = "_EventType.childRemoved"; + private static final String EVENT_TYPE_CHILD_CHANGED = "_EventType.childChanged"; + private static final String EVENT_TYPE_CHILD_MOVED = "_EventType.childMoved"; + private static final String EVENT_TYPE_VALUE = "_EventType.value"; + + // Handles are ints used as indexes into the sparse array of active observers + private int nextHandle = 0; + private final SparseArray observers = new SparseArray<>(); + + MethodCallHandlerImpl(MethodChannel channel) { + this.channel = channel; + } + + private DatabaseReference getReference(FirebaseDatabase database, Map arguments) { + String path = (String) arguments.get("path"); + DatabaseReference reference = database.getReference(); + if (path != null) reference = reference.child(path); + return reference; + } + + private Query getQuery(FirebaseDatabase database, Map arguments) { + Query query = getReference(database, arguments); + @SuppressWarnings("unchecked") + Map parameters = (Map) 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"); + if (parameters.containsKey("startAtKey")) { + String startAtKey = (String) parameters.get("startAtKey"); + if (startAt instanceof Boolean) { + query = query.startAt((Boolean) startAt, startAtKey); + } else if (startAt instanceof Number) { + query = query.startAt(((Number) startAt).doubleValue(), startAtKey); + } else { + query = query.startAt((String) startAt, startAtKey); + } + } else { + if (startAt instanceof Boolean) { + query = query.startAt((Boolean) startAt); + } else if (startAt instanceof Number) { + query = query.startAt(((Number) startAt).doubleValue()); + } else { + query = query.startAt((String) startAt); + } + } + } + if (parameters.containsKey("endAt")) { + Object endAt = parameters.get("endAt"); + if (parameters.containsKey("endAtKey")) { + String endAtKey = (String) parameters.get("endAtKey"); + if (endAt instanceof Boolean) { + query = query.endAt((Boolean) endAt, endAtKey); + } else if (endAt instanceof Number) { + query = query.endAt(((Number) endAt).doubleValue(), endAtKey); + } else { + query = query.endAt((String) endAt, endAtKey); + } + } else { + if (endAt instanceof Boolean) { + query = query.endAt((Boolean) endAt); + } else if (endAt instanceof Number) { + query = query.endAt(((Number) endAt).doubleValue()); + } else { + query = query.endAt((String) endAt); + } + } + } + if (parameters.containsKey("equalTo")) { + Object equalTo = parameters.get("equalTo"); + if (parameters.containsKey("equalToKey")) { + String equalToKey = (String) parameters.get("equalToKey"); + if (equalTo instanceof Boolean) { + query = query.equalTo((Boolean) equalTo, equalToKey); + } else if (equalTo instanceof Number) { + query = query.equalTo(((Number) equalTo).doubleValue(), equalToKey); + } else { + query = query.equalTo((String) equalTo, equalToKey); + } + } else { + if (equalTo instanceof Boolean) { + query = query.equalTo((Boolean) equalTo); + } else if (equalTo instanceof Number) { + query = query.equalTo(((Number) equalTo).doubleValue()); + } else { + query = query.equalTo((String) 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 MethodChannel.Result result; + + DefaultCompletionListener(MethodChannel.Result result) { + this.result = result; + } + + @Override + public void onComplete(@Nullable DatabaseError error, @NonNull DatabaseReference ref) { + if (error != null) { + result.error(String.valueOf(error.getCode()), error.getMessage(), error.getDetails()); + } else { + result.success(null); + } + } + } + + private class EventObserver implements ChildEventListener, ValueEventListener { + private String requestedEventType; + private int handle; + + EventObserver(String requestedEventType, int handle) { + this.requestedEventType = requestedEventType; + this.handle = handle; + } + + private void sendEvent( + String eventType, @NonNull DataSnapshot snapshot, String previousChildName) { + if (eventType.equals(requestedEventType)) { + Map arguments = new HashMap<>(); + Map snapshotMap = new HashMap<>(); + snapshotMap.put("key", snapshot.getKey()); + snapshotMap.put("value", snapshot.getValue()); + arguments.put("handle", handle); + arguments.put("snapshot", snapshotMap); + arguments.put("previousSiblingKey", previousChildName); + channel.invokeMethod("Event", arguments); + } + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + Map arguments = new HashMap<>(); + arguments.put("handle", handle); + arguments.put("error", asMap(error)); + channel.invokeMethod("Error", arguments); + } + + @Override + public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChildName) { + sendEvent(EVENT_TYPE_CHILD_ADDED, snapshot, previousChildName); + } + + @Override + public void onChildRemoved(@NonNull DataSnapshot snapshot) { + sendEvent(EVENT_TYPE_CHILD_REMOVED, snapshot, null); + } + + @Override + public void onChildChanged(@NonNull DataSnapshot snapshot, String previousChildName) { + sendEvent(EVENT_TYPE_CHILD_CHANGED, snapshot, previousChildName); + } + + @Override + public void onChildMoved(@NonNull DataSnapshot snapshot, String previousChildName) { + sendEvent(EVENT_TYPE_CHILD_MOVED, snapshot, previousChildName); + } + + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + sendEvent(EVENT_TYPE_VALUE, snapshot, null); + } + } + + @Override + public void onMethodCall(final MethodCall call, @NonNull final MethodChannel.Result result) { + final Map arguments = call.arguments(); + FirebaseDatabase database; + String appName = call.argument("app"); + String databaseURL = call.argument("databaseURL"); + if (appName != null && databaseURL != null) { + database = FirebaseDatabase.getInstance(FirebaseApp.getInstance(appName), databaseURL); + } else if (appName != null) { + database = FirebaseDatabase.getInstance(FirebaseApp.getInstance(appName)); + } else if (databaseURL != null) { + database = FirebaseDatabase.getInstance(databaseURL); + } else { + database = FirebaseDatabase.getInstance(); + } + switch (call.method) { + case "FirebaseDatabase#goOnline": + { + database.goOnline(); + result.success(null); + break; + } + + case "FirebaseDatabase#goOffline": + { + database.goOffline(); + result.success(null); + break; + } + + case "FirebaseDatabase#purgeOutstandingWrites": + { + database.purgeOutstandingWrites(); + result.success(null); + break; + } + + case "FirebaseDatabase#setPersistenceEnabled": + { + Boolean isEnabled = call.argument("enabled"); + try { + database.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 = call.argument("cacheSize"); + try { + database.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": + { + Object value = call.argument("value"); + Object priority = call.argument("priority"); + DatabaseReference reference = getReference(database, arguments); + if (priority != null) { + reference.setValue( + value, priority, new MethodCallHandlerImpl.DefaultCompletionListener(result)); + } else { + reference.setValue(value, new MethodCallHandlerImpl.DefaultCompletionListener(result)); + } + break; + } + + case "DatabaseReference#update": + { + Map value = call.argument("value"); + DatabaseReference reference = getReference(database, arguments); + reference.updateChildren( + value, new MethodCallHandlerImpl.DefaultCompletionListener(result)); + break; + } + + case "DatabaseReference#setPriority": + { + Object priority = call.argument("priority"); + DatabaseReference reference = getReference(database, arguments); + reference.setPriority( + priority, new MethodCallHandlerImpl.DefaultCompletionListener(result)); + break; + } + + case "DatabaseReference#runTransaction": + { + final DatabaseReference reference = getReference(database, arguments); + + // Initiate native transaction. + reference.runTransaction( + new Transaction.Handler() { + @NonNull + @Override + public Transaction.Result doTransaction(@NonNull MutableData mutableData) { + // Tasks are used to allow native execution of doTransaction to wait while Snapshot is + // processed by logic on the Dart side. + final TaskCompletionSource> updateMutableDataTCS = + new TaskCompletionSource<>(); + final Task> updateMutableDataTCSTask = + updateMutableDataTCS.getTask(); + + final Map doTransactionMap = new HashMap<>(); + doTransactionMap.put("transactionKey", call.argument("transactionKey")); + + final Map snapshotMap = new HashMap<>(); + snapshotMap.put("key", mutableData.getKey()); + snapshotMap.put("value", mutableData.getValue()); + doTransactionMap.put("snapshot", snapshotMap); + + // Return snapshot to Dart side for update. + handler.post( + new Runnable() { + @Override + public void run() { + channel.invokeMethod( + "DoTransaction", + doTransactionMap, + new MethodChannel.Result() { + @Override + @SuppressWarnings("unchecked") + public void success(Object result) { + updateMutableDataTCS.setResult((Map) result); + } + + @Override + public void error( + String errorCode, String errorMessage, Object errorDetails) { + String exceptionMessage = + "Error code: " + + errorCode + + "\nError message: " + + errorMessage + + "\nError details: " + + errorDetails; + updateMutableDataTCS.setException( + new Exception(exceptionMessage)); + } + + @Override + public void notImplemented() { + updateMutableDataTCS.setException( + new Exception("DoTransaction not implemented on Dart side.")); + } + }); + } + }); + + try { + // Wait for updated snapshot from the Dart side. + final Map updatedSnapshotMap = + Tasks.await( + updateMutableDataTCSTask, + (int) arguments.get("transactionTimeout"), + TimeUnit.MILLISECONDS); + // Set value of MutableData to value returned from the Dart side. + mutableData.setValue(updatedSnapshotMap.get("value")); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + Log.e(TAG, "Unable to commit Snapshot update. Transaction failed.", e); + if (e instanceof TimeoutException) { + Log.e(TAG, "Transaction at " + reference.toString() + " timed out."); + } + return Transaction.abort(); + } + return Transaction.success(mutableData); + } + + @Override + public void onComplete( + DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) { + final Map completionMap = new HashMap<>(); + completionMap.put("transactionKey", call.argument("transactionKey")); + if (databaseError != null) { + completionMap.put("error", asMap(databaseError)); + } + completionMap.put("committed", committed); + if (dataSnapshot != null) { + Map snapshotMap = new HashMap<>(); + snapshotMap.put("key", dataSnapshot.getKey()); + snapshotMap.put("value", dataSnapshot.getValue()); + completionMap.put("snapshot", snapshotMap); + } + + // Invoke transaction completion on the Dart side. + handler.post( + new Runnable() { + public void run() { + result.success(completionMap); + } + }); + } + }); + break; + } + + case "OnDisconnect#set": + { + Object value = call.argument("value"); + Object priority = call.argument("priority"); + DatabaseReference reference = getReference(database, arguments); + if (priority != null) { + if (priority instanceof String) { + reference + .onDisconnect() + .setValue( + value, + (String) priority, + new MethodCallHandlerImpl.DefaultCompletionListener(result)); + } else if (priority instanceof Double) { + reference + .onDisconnect() + .setValue( + value, + (double) priority, + new MethodCallHandlerImpl.DefaultCompletionListener(result)); + } else if (priority instanceof Map) { + reference + .onDisconnect() + .setValue( + value, + (Map) priority, + new MethodCallHandlerImpl.DefaultCompletionListener(result)); + } + } else { + reference + .onDisconnect() + .setValue(value, new MethodCallHandlerImpl.DefaultCompletionListener(result)); + } + break; + } + + case "OnDisconnect#update": + { + Map value = call.argument("value"); + DatabaseReference reference = getReference(database, arguments); + reference + .onDisconnect() + .updateChildren(value, new MethodCallHandlerImpl.DefaultCompletionListener(result)); + break; + } + + case "OnDisconnect#cancel": + { + DatabaseReference reference = getReference(database, arguments); + reference + .onDisconnect() + .cancel(new MethodCallHandlerImpl.DefaultCompletionListener(result)); + break; + } + + case "Query#keepSynced": + { + Boolean value = call.argument("value"); + getQuery(database, arguments).keepSynced(value); + result.success(null); + break; + } + + case "Query#observe": + { + String eventType = call.argument("eventType"); + int handle = nextHandle++; + MethodCallHandlerImpl.EventObserver observer = + new MethodCallHandlerImpl.EventObserver(eventType, handle); + observers.put(handle, observer); + if (EVENT_TYPE_VALUE.equals(eventType)) { + getQuery(database, arguments).addValueEventListener(observer); + } else { + getQuery(database, arguments).addChildEventListener(observer); + } + result.success(handle); + break; + } + + case "Query#removeObserver": + { + Query query = getQuery(database, arguments); + Integer handle = call.argument("handle"); + MethodCallHandlerImpl.EventObserver observer = observers.get(handle); + if (observer != null) { + if (observer.requestedEventType.equals(EVENT_TYPE_VALUE)) { + query.removeEventListener((ValueEventListener) observer); + } else { + query.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; + } + } + } + + private static Map asMap(DatabaseError error) { + Map map = new HashMap<>(); + map.put("code", error.getCode()); + map.put("message", error.getMessage()); + map.put("details", error.getDetails()); + return map; + } +} diff --git a/packages/firebase_database/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_database/example/android/app/src/main/AndroidManifest.xml index 794bfa20d51d..f7805c70e182 100755 --- a/packages/firebase_database/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_database/example/android/app/src/main/AndroidManifest.xml @@ -4,12 +4,20 @@ - + + diff --git a/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1Activity.java b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..b241c4c08872 --- /dev/null +++ b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1Activity.java @@ -0,0 +1,17 @@ +// 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. + +package io.flutter.plugins.firebasedatabaseexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1ActivityTest.java b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..efcff0ff10d1 --- /dev/null +++ b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,17 @@ +// Copyright 2019 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. + +package io.flutter.plugins.firebasedatabaseexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.java b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.java index 0c58e26a5c1b..b52b4395eefc 100644 --- a/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.java +++ b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.java @@ -4,14 +4,19 @@ package io.flutter.plugins.firebasedatabaseexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.firebase.core.FirebaseCorePlugin; +import io.flutter.plugins.firebase.database.FirebaseDatabasePlugin; public class MainActivity extends FlutterActivity { + + // TODO(cyanglaz): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. + // https://github.com/flutter/flutter/issues/42694 @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + flutterEngine.getPlugins().add(new FirebaseDatabasePlugin()); + flutterEngine.getPlugins().add(new FirebaseCorePlugin()); } } diff --git a/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivityTest.java b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivityTest.java new file mode 100644 index 000000000000..30c3e3e57031 --- /dev/null +++ b/packages/firebase_database/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivityTest.java @@ -0,0 +1,15 @@ +// Copyright 2019 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. + +package io.flutter.plugins.firebasedatabaseexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/firebase_database/example/android/gradle.properties b/packages/firebase_database/example/android/gradle.properties index 08f2b5f91bff..38c8d4544ff1 100755 --- a/packages/firebase_database/example/android/gradle.properties +++ b/packages/firebase_database/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M -android.enableJetifier=true +android.enableR8=true android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/firebase_database/example/lib/main.dart b/packages/firebase_database/example/lib/main.dart index c22374aa3e94..d2753c9e8b45 100755 --- a/packages/firebase_database/example/lib/main.dart +++ b/packages/firebase_database/example/lib/main.dart @@ -11,6 +11,7 @@ import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/ui/firebase_animated_list.dart'; Future main() async { + WidgetsFlutterBinding.ensureInitialized(); final FirebaseApp app = await FirebaseApp.configure( name: 'db2', options: Platform.isIOS diff --git a/packages/firebase_database/example/pubspec.yaml b/packages/firebase_database/example/pubspec.yaml index 3c1735eab718..de0c16a7dc74 100755 --- a/packages/firebase_database/example/pubspec.yaml +++ b/packages/firebase_database/example/pubspec.yaml @@ -6,12 +6,17 @@ dependencies: sdk: flutter firebase_database: path: ../ - firebase_core: ^0.4.0 + firebase_core: ^0.4.1 dev_dependencies: flutter_driver: sdk: flutter test: any + e2e: ^0.2.0 flutter: uses-material-design: true + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.2 <2.0.0" diff --git a/packages/firebase_database/example/test_driver/firebase_database.dart b/packages/firebase_database/example/test_driver/firebase_database_e2e.dart similarity index 72% rename from packages/firebase_database/example/test_driver/firebase_database.dart rename to packages/firebase_database/example/test_driver/firebase_database_e2e.dart index c40cc0823d90..3b11659bdd2d 100644 --- a/packages/firebase_database/example/test_driver/firebase_database.dart +++ b/packages/firebase_database/example/test_driver/firebase_database_e2e.dart @@ -1,17 +1,14 @@ -import 'dart:async'; -import 'package:flutter_driver/driver_extension.dart'; +import 'package:e2e/e2e.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:firebase_database/firebase_database.dart'; void main() { - final Completer completer = Completer(); - enableFlutterDriverExtension(handler: (_) => completer.future); - tearDownAll(() => completer.complete(null)); + E2EWidgetsFlutterBinding.ensureInitialized(); group('$FirebaseDatabase', () { final FirebaseDatabase database = FirebaseDatabase.instance; - test('runTransaction', () async { + testWidgets('runTransaction', (WidgetTester tester) async { final DatabaseReference ref = database.reference().child('counter'); final DataSnapshot snapshot = await ref.once(); final int value = snapshot.value ?? 0; diff --git a/packages/firebase_database/example/test_driver/firebase_database_e2e_test.dart b/packages/firebase_database/example/test_driver/firebase_database_e2e_test.dart new file mode 100644 index 000000000000..bcfa86da4455 --- /dev/null +++ b/packages/firebase_database/example/test_driver/firebase_database_e2e_test.dart @@ -0,0 +1,11 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/firebase_database/example/test_driver/firebase_database_test.dart b/packages/firebase_database/example/test_driver/firebase_database_test.dart deleted file mode 100644 index 08cdadbd0809..000000000000 --- a/packages/firebase_database/example/test_driver/firebase_database_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:test/test.dart'; - -void main() { - test('FirebaseDatabase driver test', () async { - final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); - }); -} diff --git a/packages/firebase_database/pubspec.yaml b/packages/firebase_database/pubspec.yaml index bbfcb78b560b..cd74aa395587 100755 --- a/packages/firebase_database/pubspec.yaml +++ b/packages/firebase_database/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Database, a cloud-hosted NoSQL database with realtime data syncing across Android and iOS clients, and offline access. author: Flutter Team homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_database -version: 3.0.9 +version: 3.1.0 flutter: plugin: @@ -23,7 +23,8 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter + e2e: ^0.2.1 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.9.1+hotfix.2 <2.0.0"