commit da21a013db761374af8662cdf10dad625d1972d5 Author: JohnE Date: Tue Jun 11 13:19:04 2019 -0700 NEW: initial commit diff --git a/packages/cloud_firestore/CHANGELOG.md b/packages/cloud_firestore/CHANGELOG.md new file mode 100644 index 0000000..b4f3c2a --- /dev/null +++ b/packages/cloud_firestore/CHANGELOG.md @@ -0,0 +1,381 @@ +## 0.12.5 + +* Makes `startAtDocument`, `startAfterDocument`, `endAtDocument` and `endBeforeDocument` work + with `Query.collectionGroup` queries. +* Fixes `startAtDocument`, `startAfterDocument`, `endAtDocument` and `endBeforeDocument` to + also work with a descending order as the last explicit sort order. +* Fixed an integration test by increasing the value of `cacheSizeBytes` to a valid value. + +## 0.12.4 + +* Added support for `Query.collectionGroup`. + +## 0.12.3 + +* Added support for `cacheSizeBytes` to `Firestore.settings`. + +## 0.12.2 + +* Ensure that all channel calls to the Dart side from the Java side are done + on the UI thread. This change allows Transactions to work with upcoming + Engine restrictions, which require channel calls be made on the UI thread. + **Note** this is an Android only change, the iOS implementation was not impacted. +* Updated the Firebase reporting string to `flutter-fire-fst` to be consistent + with other reporting libraries. + +## 0.12.1 + +* Added support for `Source` to `Query.getDocuments()` and `DocumentReference.get()`. + +## 0.12.0+2 + +* Bump the minimum Flutter version to 1.5. +* Replace invokeMethod with invokeMapMethod wherever necessary. + +## 0.12.0+1 + +* Send user agent to Firebase. + +## 0.12.0 + +* **Breaking change**. Fixed `CollectionReference.parent` to correctly return a `DocumentReference`. + If you were using the method previously to obtain the parent + document's id via `collectionReference.parent().id`, + you will have to use `collectionReference.parent().documentID` now. +* Added `DocumentReference.parent`. + +## 0.11.0+2 + +* Remove iOS dependency on Firebase/Database and Firebase/Auth CocoaPods. + +## 0.11.0+1 + +* Update iOS CocoaPod dependencies to '~> 6.0' to ensure support for `FieldValue.increment`. + +## 0.11.0 + +* Update Android dependencies to latest. + +## 0.10.1 + +* Support for `startAtDocument`, `startAfterDocument`, `endAtDocument`, `endBeforeDocument`. +* Added additional unit and integration tests. + +## 0.10.0 + +* Support for `FieldValue.increment`. +* Remove `FieldValue.type` and `FieldValue.value` from public API. +* Additional integration testing. + +## 0.9.13+1 + +* Added an integration test for transactions. + +## 0.9.13 + +* Remove Gradle BoM to avoid Gradle version issues. + +## 0.9.12 + +* Move Android dependency to Gradle BoM to help maintain compatibility + with other FlutterFire plugins. + +## 0.9.11 + +* Bump Android dependencies to latest. + +# 0.9.10 + +* Support for cloud_firestore running in the background on Android. +* Fixed a bug in cleanup for DocumentReference.snapshots(). +* Additional integration testing. + +## 0.9.9 + +* Remove `invokeMapMethod` calls to prevent crash. + +## 0.9.8 + +* Add metadata field to DocumentSnapshot. + +## 0.9.7+2 + +* Bump the minimum Flutter version to 1.2.0. +* Add template type parameter to `invokeMethod` calls. + +## 0.9.7+1 + +* Update README with example of getting a document. + +## 0.9.7 + +* Fixes a NoSuchMethodError when using getDocuments on iOS (introduced in 0.9.6). +* Adds a driver test for getDocuments. + +## 0.9.6 + +* On iOS, update null checking to match the recommended pattern usage in the Firebase documentation. +* Fixes a case where snapshot errors might result in plugin crash. + +## 0.9.5+2 + +* Fixing PlatformException(Error 0, null, null) which happened when a successful operation was performed. + +## 0.9.5+1 + +* Log messages about automatic configuration of the default app are now less confusing. + +## 0.9.5 + +* Fix an issue on some iOS devices that results in reading incorrect dates. + +## 0.9.4 + +* No longer sends empty snapshot events on iOS when encountering errors. + +## 0.9.3 + +* Fix transactions on iOS when getting snapshot that doesn't exist. + +## 0.9.2 + +* Fix IllegalStateException errors when using transactions on Android. + +## 0.9.1 + +* Fixed Firebase multiple app support in transactions and document snapshots. + +## 0.9.0+2 + +* Remove categories. + +## 0.9.0+1 + +* Log a more detailed warning at build time about the previous AndroidX + migration. + +## 0.9.0 + +* **Breaking change**. Migrate from the deprecated original Android Support + Library to AndroidX. This shouldn't result in any functional changes, but it + requires any Android apps using this plugin to [also + migrate](https://developer.android.com/jetpack/androidx/migrate) if they're + using the original support library. + +## 0.8.2+3 + +* Resolved "explicit self reference" and "loses accuracy" compiler warnings. + +## 0.8.2+2 + +* Clean up Android build logs. @SuppressWarnings("unchecked") + +## 0.8.2+1 + +* Avoid crash in document snapshot callback. + +## 0.8.2 + +* Added `Firestore.settings` +* Added `Timestamp` class + +## 0.8.1+1 + +* Bump Android dependencies to latest. + +## 0.8.1 + +* Fixed bug where updating arrays in with `FieldValue` always throws an Exception on Android. + +## 0.8.0 + +Note: this version depends on features available in iOS SDK versions 5.5.0 or later. +To update iOS SDK in existing projects run `pod update Firebase/Firestore`. + +* Added `Firestore.enablePersistence` +* Added `FieldValue` with all currently supported values: `arrayUnion`, `arrayRemove`, `delete` and + `serverTimestamp`. +* Added `arrayContains` argument in `Query.where` method. + +## 0.7.4 + +* Bump Android and Firebase dependency versions. + +## 0.7.3 + +* Updated Gradle tooling to match Android Studio 3.1.2. + +## 0.7.2 + +* Fixes crash on Android if a FirebaseFirestoreException happened. + +## 0.7.1 + +* Updated iOS implementation to reflect Firebase API changes. +* Fixed bug in Transaction.get that would fail on no data. +* Fixed error in README.md code sample. + +## 0.7.0+2 + +* Update transactions example in README to add `await`. + +## 0.7.0+1 + +* Add transactions example to README. + +## 0.7.0 + +* **Breaking change**. `snapshots` is now a method instead of a getter. +* **Breaking change**. `setData` uses named arguments instead of `SetOptions`. + +## 0.6.3 + +* Updated Google Play Services dependencies to version 15.0.0. + +## 0.6.2 + +* Support for BLOB data type. + +## 0.6.1 + +* Simplified podspec for Cocoapods 1.5.0, avoiding link issues in app archives. + +## 0.6.0 + +* **Breaking change**. Renamed 'getCollection()' to 'collection().' + +## 0.5.1 + +* Expose the Firebase app corresponding to a Firestore +* Expose a constructor for a Firestore with a non-default Firebase app + +## 0.5.0 + +* **Breaking change**. Move path getter to CollectionReference +* Add id getter to CollectionReference + +## 0.4.0 + +* **Breaking change**. Hide Firestore codec class from public API. +* Adjusted Flutter SDK constraint to match Flutter release with extensible + platform message codec, required already by version 0.3.1. +* Move each class into separate files + +## 0.3.2 + +* Support for batched writes. + +## 0.3.1 + +* Add GeoPoint class +* Allow for reading and writing DocumentReference, DateTime, and GeoPoint + values from and to Documents. + +## 0.3.0 + +* **Breaking change**. Set SDK constraints to match the Flutter beta release. + +## 0.2.12 + +* Fix handling of `null` document snapshots (document not exists). +* Add `DocumentSnapshot.exists`. + +## 0.2.11 +* Fix Dart 2 type errors. + +## 0.2.10 +* Fix Dart 2 type errors. + +## 0.2.9 +* Relax sdk upper bound constraint to '<2.0.0' to allow 'edge' dart sdk use. + +## 0.2.8 +* Support for Query.getDocuments + +## 0.2.7 + +* Add transaction support. + +## 0.2.6 + +* Build fixes for iOS +* Null checking in newly added Query methods + +## 0.2.5 + +* Query can now have more than one orderBy field. +* startAt, startAfter, endAt, and endBefore support +* limit support + +## 0.2.4 + +* Support for DocumentReference.documentID +* Support for CollectionReference.add + +## 0.2.3 + +* Simplified and upgraded Android project template to Android SDK 27. +* Updated package description. + +## 0.2.2 + +* Add `get` to DocumentReference. + +## 0.2.1 + +* Fix bug on Android where removeListener is sometimes called without a handle + +## 0.2.0 + +* **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin + 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in + order to use this version of the plugin. Instructions can be found + [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). +* Relaxed GMS dependency to [11.4.0,12.0[ + +## 0.1.2 + +* Support for `DocumentReference` update and merge writes +* Suppress unchecked warnings and package name warnings on Android + +## 0.1.1 + +* Added FLT prefix to iOS types. + +## 0.1.0 + +* Added reference to DocumentSnapshot +* Breaking: removed path from DocumentSnapshot +* Additional test coverage for reading collections and documents +* Fixed typo in DocumentChange documentation + +## 0.0.6 + +* Support for getCollection + +## 0.0.5 + +* Support `isNull` filtering in `Query.where` +* Fixed `DocumentChange.oldIndex` and `DocumentChange.newIndex` to be signed + integers (iOS) + +## 0.0.4 + +* Support for where clauses +* Support for deletion + +## 0.0.3 + +* Renamed package to cloud_firestore + +## 0.0.2 + +* Add path property to DocumentSnapshot + +## 0.0.1+1 + +* Update project homepage + +## 0.0.1 + +* Initial Release diff --git a/packages/cloud_firestore/LICENSE b/packages/cloud_firestore/LICENSE new file mode 100755 index 0000000..5b8ff62 --- /dev/null +++ b/packages/cloud_firestore/LICENSE @@ -0,0 +1,26 @@ +Copyright 2017, the Chromium project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/cloud_firestore/README.md b/packages/cloud_firestore/README.md new file mode 100755 index 0000000..c34e2fa --- /dev/null +++ b/packages/cloud_firestore/README.md @@ -0,0 +1,104 @@ +# Cloud Firestore Plugin for Flutter + +A Flutter plugin to use the [Cloud Firestore API](https://firebase.google.com/docs/firestore/). + +[![pub package](https://img.shields.io/pub/v/cloud_firestore.svg)](https://pub.dartlang.org/packages/cloud_firestore) + +For Flutter plugins for other Firebase products, see [FlutterFire.md](https://github.com/flutter/plugins/blob/master/FlutterFire.md). + +*Note*: This plugin is still under development, and some APIs might not be available yet. [Feedback](https://github.com/flutter/flutter/issues) and [Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome! + +## Setup + +To use this plugin: + +1. Using the [Firebase Console](http://console.firebase.google.com/), add an Android app to your project: +Follow the assistant, download the generated google-services.json file and place it inside android/app. Next, +modify the android/build.gradle file and the android/app/build.gradle file to add the Google services plugin +as described by the Firebase assistant. Ensure that your `android/build.gradle` file contains the +`maven.google.com` as [described here](https://firebase.google.com/docs/android/setup#add_the_sdk). +1. Using the [Firebase Console](http://console.firebase.google.com/), add an iOS app to your project: +Follow the assistant, download the generated GoogleService-Info.plist file, open ios/Runner.xcworkspace +with Xcode, and within Xcode place the file inside ios/Runner. Don't follow the steps named +"Add Firebase SDK" and "Add initialization code" in the Firebase assistant. +1. Add `cloud_firestore` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). + +## Usage + +```dart +import 'package:cloud_firestore/cloud_firestore.dart'; +``` + +Adding a new `DocumentReference`: + +```dart +Firestore.instance.collection('books').document() + .setData({ 'title': 'title', 'author': 'author' }); +``` + +Binding a `CollectionReference` to a `ListView`: + +```dart +class BookList extends StatelessWidget { + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: Firestore.instance.collection('books').snapshots(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) + return new Text('Error: ${snapshot.error}'); + switch (snapshot.connectionState) { + case ConnectionState.waiting: return new Text('Loading...'); + default: + return new ListView( + children: snapshot.data.documents.map((DocumentSnapshot document) { + return new ListTile( + title: new Text(document['title']), + subtitle: new Text(document['author']), + ); + }).toList(), + ); + } + }, + ); + } +} +``` + +Performing a query: +```dart +Firestore.instance + .collection('talks') + .where("topic", isEqualTo: "flutter") + .snapshots() + .listen((data) => + data.documents.forEach((doc) => print(doc["title"]))); +``` + +Get a specific document: + +```dart +Firestore.instance + .collection('talks') + .document('document-name') + .get() + .then((DocumentSnapshot ds) { + // use ds as a snapshot + }); +``` + +Running a transaction: + +```dart +final DocumentReference postRef = Firestore.instance.document('posts/123'); +Firestore.instance.runTransaction((Transaction tx) async { + DocumentSnapshot postSnapshot = await tx.get(postRef); + if (postSnapshot.exists) { + await tx.update(postRef, {'likesCount': postSnapshot.data['likesCount'] + 1}); + } +}); +``` + +## Getting Started + +See the `example` directory for a complete sample app using Cloud Firestore. diff --git a/packages/cloud_firestore/android/build.gradle b/packages/cloud_firestore/android/build.gradle new file mode 100755 index 0000000..df8db91 --- /dev/null +++ b/packages/cloud_firestore/android/build.gradle @@ -0,0 +1,54 @@ +def PLUGIN = "cloud_firestore"; +def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; +gradle.buildFinished { buildResult -> + if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { + println ' *********************************************************' + println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' + println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' + println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' + println ' *********************************************************' + rootProject.ext.set(ANDROIDX_WARNING, true); + } +} + +group 'io.flutter.plugins.firebase.firestore' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + jcenter() + mavenLocal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + } +} + +allprojects { + repositories { + google() + jcenter() + mavenLocal() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + dependencies { + api 'com.google.firebase:firebase-firestore:19.0.0' + implementation 'com.google.firebase:firebase-common:16.1.0' + implementation 'androidx.annotation:annotation:1.0.0' + } +} diff --git a/packages/cloud_firestore/android/settings.gradle b/packages/cloud_firestore/android/settings.gradle new file mode 100755 index 0000000..caf1065 --- /dev/null +++ b/packages/cloud_firestore/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cloud_firestore' diff --git a/packages/cloud_firestore/android/src/main/AndroidManifest.xml b/packages/cloud_firestore/android/src/main/AndroidManifest.xml new file mode 100755 index 0000000..b86fd33 --- /dev/null +++ b/packages/cloud_firestore/android/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java new file mode 100644 index 0000000..ec23d61 --- /dev/null +++ b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java @@ -0,0 +1,881 @@ +// 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.firebase.cloudfirestore; + +import android.app.Activity; +import android.os.AsyncTask; +import android.util.Log; +import android.util.SparseArray; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +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.Timestamp; +import com.google.firebase.firestore.Blob; +import com.google.firebase.firestore.CollectionReference; +import com.google.firebase.firestore.DocumentChange; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.EventListener; +import com.google.firebase.firestore.FieldPath; +import com.google.firebase.firestore.FieldValue; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.FirebaseFirestoreSettings; +import com.google.firebase.firestore.GeoPoint; +import com.google.firebase.firestore.ListenerRegistration; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QuerySnapshot; +import com.google.firebase.firestore.SetOptions; +import com.google.firebase.firestore.Source; +import com.google.firebase.firestore.Transaction; +import com.google.firebase.firestore.WriteBatch; +import io.flutter.plugin.common.MethodCall; +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 io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugin.common.StandardMethodCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class CloudFirestorePlugin implements MethodCallHandler { + + private static final String TAG = "CloudFirestorePlugin"; + private final MethodChannel channel; + private final Activity activity; + + // Handles are ints used as indexes into the sparse array of active observers + private int nextListenerHandle = 0; + private int nextBatchHandle = 0; + private final SparseArray observers = new SparseArray<>(); + private final SparseArray documentObservers = new SparseArray<>(); + private final SparseArray listenerRegistrations = new SparseArray<>(); + private final SparseArray batches = new SparseArray<>(); + private final SparseArray transactions = new SparseArray<>(); + private final SparseArray completionTasks = new SparseArray<>(); + + public static void registerWith(PluginRegistry.Registrar registrar) { + final MethodChannel channel = + new MethodChannel( + registrar.messenger(), + "plugins.flutter.io/cloud_firestore", + new StandardMethodCodec(FirestoreMessageCodec.INSTANCE)); + channel.setMethodCallHandler(new CloudFirestorePlugin(channel, registrar.activity())); + } + + private CloudFirestorePlugin(MethodChannel channel, Activity activity) { + this.channel = channel; + this.activity = activity; + } + + private FirebaseFirestore getFirestore(Map arguments) { + String appName = (String) arguments.get("app"); + return FirebaseFirestore.getInstance(FirebaseApp.getInstance(appName)); + } + + private Query getReference(Map arguments) { + if ((boolean) arguments.get("isCollectionGroup")) return getCollectionGroupReference(arguments); + else return getCollectionReference(arguments); + } + + private Query getCollectionGroupReference(Map arguments) { + String path = (String) arguments.get("path"); + return getFirestore(arguments).collectionGroup(path); + } + + private CollectionReference getCollectionReference(Map arguments) { + String path = (String) arguments.get("path"); + return getFirestore(arguments).collection(path); + } + + private DocumentReference getDocumentReference(Map arguments) { + String path = (String) arguments.get("path"); + return getFirestore(arguments).document(path); + } + + private Source getSource(Map arguments) { + String source = (String) arguments.get("source"); + switch (source) { + case "server": + return Source.SERVER; + case "cache": + return Source.CACHE; + default: + return Source.DEFAULT; + } + } + + private Query implicitOrderBy(Query query, List> orderBy) { + boolean descending = (boolean) orderBy.get(orderBy.size() - 1).get(1); + Query.Direction direction = descending ? Query.Direction.DESCENDING : Query.Direction.ASCENDING; + return query.orderBy(FieldPath.documentId(), direction); + } + + private Object[] getDocumentValues( + Map document, List> orderBy, Map arguments) { + String documentId = (String) document.get("id"); + Map documentData = (Map) document.get("data"); + List data = new ArrayList<>(); + if (orderBy != null) { + for (List order : orderBy) { + String orderByFieldName = (String) order.get(0); + data.add(documentData.get(orderByFieldName)); + } + } + data.add((boolean) arguments.get("isCollectionGroup") ? document.get("path") : documentId); + return data.toArray(); + } + + private Map parseQuerySnapshot(QuerySnapshot querySnapshot) { + if (querySnapshot == null) return new HashMap<>(); + Map data = new HashMap<>(); + List paths = new ArrayList<>(); + List> documents = new ArrayList<>(); + List> metadatas = new ArrayList<>(); + for (DocumentSnapshot document : querySnapshot.getDocuments()) { + paths.add(document.getReference().getPath()); + documents.add(document.getData()); + Map metadata = new HashMap(); + metadata.put("hasPendingWrites", document.getMetadata().hasPendingWrites()); + metadata.put("isFromCache", document.getMetadata().isFromCache()); + metadatas.add(metadata); + } + data.put("paths", paths); + data.put("documents", documents); + data.put("metadatas", metadatas); + + List> documentChanges = new ArrayList<>(); + for (DocumentChange documentChange : querySnapshot.getDocumentChanges()) { + Map change = new HashMap<>(); + String type = null; + switch (documentChange.getType()) { + case ADDED: + type = "DocumentChangeType.added"; + break; + case MODIFIED: + type = "DocumentChangeType.modified"; + break; + case REMOVED: + type = "DocumentChangeType.removed"; + break; + } + change.put("type", type); + change.put("oldIndex", documentChange.getOldIndex()); + change.put("newIndex", documentChange.getNewIndex()); + change.put("document", documentChange.getDocument().getData()); + change.put("path", documentChange.getDocument().getReference().getPath()); + Map metadata = new HashMap(); + metadata.put( + "hasPendingWrites", documentChange.getDocument().getMetadata().hasPendingWrites()); + metadata.put("isFromCache", documentChange.getDocument().getMetadata().isFromCache()); + change.put("metadata", metadata); + documentChanges.add(change); + } + data.put("documentChanges", documentChanges); + + return data; + } + + private Transaction getTransaction(Map arguments) { + return transactions.get((Integer) arguments.get("transactionId")); + } + + private Query getQuery(Map arguments) { + Query query = getReference(arguments); + @SuppressWarnings("unchecked") + Map parameters = (Map) arguments.get("parameters"); + if (parameters == null) return query; + @SuppressWarnings("unchecked") + List> whereConditions = (List>) parameters.get("where"); + for (List condition : whereConditions) { + String fieldName = (String) condition.get(0); + String operator = (String) condition.get(1); + Object value = condition.get(2); + if ("==".equals(operator)) { + query = query.whereEqualTo(fieldName, value); + } else if ("<".equals(operator)) { + query = query.whereLessThan(fieldName, value); + } else if ("<=".equals(operator)) { + query = query.whereLessThanOrEqualTo(fieldName, value); + } else if (">".equals(operator)) { + query = query.whereGreaterThan(fieldName, value); + } else if (">=".equals(operator)) { + query = query.whereGreaterThanOrEqualTo(fieldName, value); + } else if ("array-contains".equals(operator)) { + query = query.whereArrayContains(fieldName, value); + } else { + // Invalid operator. + } + } + @SuppressWarnings("unchecked") + Number limit = (Number) parameters.get("limit"); + if (limit != null) query = query.limit(limit.longValue()); + @SuppressWarnings("unchecked") + List> orderBy = (List>) parameters.get("orderBy"); + if (orderBy == null) return query; + for (List order : orderBy) { + String orderByFieldName = (String) order.get(0); + boolean descending = (boolean) order.get(1); + Query.Direction direction = + descending ? Query.Direction.DESCENDING : Query.Direction.ASCENDING; + query = query.orderBy(orderByFieldName, direction); + } + @SuppressWarnings("unchecked") + Map startAtDocument = (Map) parameters.get("startAtDocument"); + if (startAtDocument != null) { + query = + implicitOrderBy(query, orderBy) + .startAt(getDocumentValues(startAtDocument, orderBy, arguments)); + } + @SuppressWarnings("unchecked") + Map startAfterDocument = + (Map) parameters.get("startAfterDocument"); + if (startAfterDocument != null) { + query = + implicitOrderBy(query, orderBy) + .startAfter(getDocumentValues(startAfterDocument, orderBy, arguments)); + } + @SuppressWarnings("unchecked") + List startAt = (List) parameters.get("startAt"); + if (startAt != null) query = query.startAt(startAt.toArray()); + @SuppressWarnings("unchecked") + List startAfter = (List) parameters.get("startAfter"); + if (startAfter != null) query = query.startAfter(startAfter.toArray()); + @SuppressWarnings("unchecked") + Map endAtDocument = (Map) parameters.get("endAtDocument"); + if (endAtDocument != null) { + query = + implicitOrderBy(query, orderBy) + .endAt(getDocumentValues(endAtDocument, orderBy, arguments)); + } + @SuppressWarnings("unchecked") + Map endBeforeDocument = + (Map) parameters.get("endBeforeDocument"); + if (endBeforeDocument != null) { + query = + implicitOrderBy(query, orderBy) + .endBefore(getDocumentValues(endBeforeDocument, orderBy, arguments)); + } + @SuppressWarnings("unchecked") + List endAt = (List) parameters.get("endAt"); + if (endAt != null) query = query.endAt(endAt.toArray()); + @SuppressWarnings("unchecked") + List endBefore = (List) parameters.get("endBefore"); + if (endBefore != null) query = query.endBefore(endBefore.toArray()); + return query; + } + + private class DocumentObserver implements EventListener { + private int handle; + + DocumentObserver(int handle) { + this.handle = handle; + } + + @Override + public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException e) { + if (e != null) { + // TODO: send error + System.out.println(e); + return; + } + Map arguments = new HashMap<>(); + Map metadata = new HashMap<>(); + arguments.put("handle", handle); + metadata.put("hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites()); + metadata.put("isFromCache", documentSnapshot.getMetadata().isFromCache()); + arguments.put("metadata", metadata); + if (documentSnapshot.exists()) { + arguments.put("data", documentSnapshot.getData()); + arguments.put("path", documentSnapshot.getReference().getPath()); + } else { + arguments.put("data", null); + arguments.put("path", documentSnapshot.getReference().getPath()); + } + channel.invokeMethod("DocumentSnapshot", arguments); + } + } + + private class EventObserver implements EventListener { + private int handle; + + EventObserver(int handle) { + this.handle = handle; + } + + @Override + public void onEvent(QuerySnapshot querySnapshot, FirebaseFirestoreException e) { + if (e != null) { + // TODO: send error + System.out.println(e); + return; + } + + Map arguments = parseQuerySnapshot(querySnapshot); + arguments.put("handle", handle); + + channel.invokeMethod("QuerySnapshot", arguments); + } + } + + private void addDefaultListeners(final String description, Task task, final Result result) { + task.addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(Void ignored) { + result.success(null); + } + }); + task.addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + result.error("Error performing " + description, e.getMessage(), null); + } + }); + } + + @Override + public void onMethodCall(MethodCall call, final Result result) { + switch (call.method) { + case "Firestore#runTransaction": + { + final TaskCompletionSource> transactionTCS = + new TaskCompletionSource<>(); + final Task> transactionTCSTask = transactionTCS.getTask(); + + final Map arguments = call.arguments(); + getFirestore(arguments) + .runTransaction( + new Transaction.Function>() { + @Nullable + @Override + public Map apply(@NonNull Transaction transaction) { + // Store transaction. + int transactionId = (Integer) arguments.get("transactionId"); + transactions.append(transactionId, transaction); + completionTasks.append(transactionId, transactionTCS); + + // Start operations on Dart side. + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + channel.invokeMethod( + "DoTransaction", + arguments, + new Result() { + @SuppressWarnings("unchecked") + @Override + public void success(Object doTransactionResult) { + transactionTCS.trySetResult( + (Map) doTransactionResult); + } + + @Override + public void error( + String errorCode, + String errorMessage, + Object errorDetails) { + transactionTCS.trySetException( + new Exception("Do transaction failed.")); + } + + @Override + public void notImplemented() { + transactionTCS.trySetException( + new Exception("DoTransaction not implemented")); + } + }); + } + }); + + // Wait till transaction is complete. + try { + String timeoutKey = "transactionTimeout"; + long timeout = ((Number) arguments.get(timeoutKey)).longValue(); + final Map transactionResult = + Tasks.await(transactionTCSTask, timeout, TimeUnit.MILLISECONDS); + + // Once transaction completes return the result to the Dart side. + return transactionResult; + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + result.error("Error performing transaction", e.getMessage(), null); + } + return null; + } + }) + .addOnCompleteListener( + new OnCompleteListener>() { + @Override + public void onComplete(Task> task) { + if (task.isSuccessful()) { + result.success(task.getResult()); + } else { + result.error( + "Error performing transaction", task.getException().getMessage(), null); + } + } + }); + break; + } + case "Transaction#get": + { + final Map arguments = call.arguments(); + final Transaction transaction = getTransaction(arguments); + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + try { + DocumentSnapshot documentSnapshot = + transaction.get(getDocumentReference(arguments)); + final Map snapshotMap = new HashMap<>(); + snapshotMap.put("path", documentSnapshot.getReference().getPath()); + if (documentSnapshot.exists()) { + snapshotMap.put("data", documentSnapshot.getData()); + } else { + snapshotMap.put("data", null); + } + Map metadata = new HashMap(); + metadata.put("hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites()); + metadata.put("isFromCache", documentSnapshot.getMetadata().isFromCache()); + snapshotMap.put("metadata", metadata); + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + result.success(snapshotMap); + } + }); + } catch (final FirebaseFirestoreException e) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + result.error("Error performing Transaction#get", e.getMessage(), null); + } + }); + } + return null; + } + }.execute(); + break; + } + case "Transaction#update": + { + final Map arguments = call.arguments(); + final Transaction transaction = getTransaction(arguments); + new AsyncTask() { + @SuppressWarnings("unchecked") + @Override + protected Void doInBackground(Void... voids) { + Map data = (Map) arguments.get("data"); + try { + transaction.update(getDocumentReference(arguments), data); + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + result.success(null); + } + }); + } catch (final IllegalStateException e) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + result.error("Error performing Transaction#update", e.getMessage(), null); + } + }); + } + return null; + } + }.execute(); + break; + } + case "Transaction#set": + { + final Map arguments = call.arguments(); + final Transaction transaction = getTransaction(arguments); + new AsyncTask() { + @SuppressWarnings("unchecked") + @Override + protected Void doInBackground(Void... voids) { + Map data = (Map) arguments.get("data"); + transaction.set(getDocumentReference(arguments), data); + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + Log.d(TAG, "sending set success"); + result.success(null); + } + }); + return null; + } + }.execute(); + break; + } + case "Transaction#delete": + { + final Map arguments = call.arguments(); + final Transaction transaction = getTransaction(arguments); + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + transaction.delete(getDocumentReference(arguments)); + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + result.success(null); + } + }); + return null; + } + }.execute(); + break; + } + case "WriteBatch#create": + { + int handle = nextBatchHandle++; + final Map arguments = call.arguments(); + WriteBatch batch = getFirestore(arguments).batch(); + batches.put(handle, batch); + result.success(handle); + break; + } + case "WriteBatch#setData": + { + Map arguments = call.arguments(); + int handle = (Integer) arguments.get("handle"); + DocumentReference reference = getDocumentReference(arguments); + @SuppressWarnings("unchecked") + Map options = (Map) arguments.get("options"); + WriteBatch batch = batches.get(handle); + if (options != null && (boolean) options.get("merge")) { + batch.set(reference, arguments.get("data"), SetOptions.merge()); + } else { + batch.set(reference, arguments.get("data")); + } + result.success(null); + break; + } + case "WriteBatch#updateData": + { + Map arguments = call.arguments(); + int handle = (Integer) arguments.get("handle"); + DocumentReference reference = getDocumentReference(arguments); + @SuppressWarnings("unchecked") + Map data = (Map) arguments.get("data"); + WriteBatch batch = batches.get(handle); + batch.update(reference, data); + result.success(null); + break; + } + case "WriteBatch#delete": + { + Map arguments = call.arguments(); + int handle = (Integer) arguments.get("handle"); + DocumentReference reference = getDocumentReference(arguments); + WriteBatch batch = batches.get(handle); + batch.delete(reference); + result.success(null); + break; + } + case "WriteBatch#commit": + { + Map arguments = call.arguments(); + int handle = (Integer) arguments.get("handle"); + WriteBatch batch = batches.get(handle); + Task task = batch.commit(); + batches.delete(handle); + addDefaultListeners("commit", task, result); + break; + } + case "Query#addSnapshotListener": + { + Map arguments = call.arguments(); + int handle = nextListenerHandle++; + EventObserver observer = new EventObserver(handle); + observers.put(handle, observer); + listenerRegistrations.put(handle, getQuery(arguments).addSnapshotListener(observer)); + result.success(handle); + break; + } + case "Query#addDocumentListener": + { + Map arguments = call.arguments(); + int handle = nextListenerHandle++; + DocumentObserver observer = new DocumentObserver(handle); + documentObservers.put(handle, observer); + listenerRegistrations.put( + handle, getDocumentReference(arguments).addSnapshotListener(observer)); + result.success(handle); + break; + } + case "Query#removeListener": + { + Map arguments = call.arguments(); + int handle = (Integer) arguments.get("handle"); + listenerRegistrations.get(handle).remove(); + listenerRegistrations.remove(handle); + observers.remove(handle); + result.success(null); + break; + } + case "Query#getDocuments": + { + Map arguments = call.arguments(); + Query query = getQuery(arguments); + Source source = getSource(arguments); + Task task = query.get(source); + task.addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(QuerySnapshot querySnapshot) { + result.success(parseQuerySnapshot(querySnapshot)); + } + }) + .addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + result.error("Error performing getDocuments", e.getMessage(), null); + } + }); + break; + } + case "DocumentReference#setData": + { + Map arguments = call.arguments(); + DocumentReference documentReference = getDocumentReference(arguments); + @SuppressWarnings("unchecked") + Map options = (Map) arguments.get("options"); + @SuppressWarnings("unchecked") + Map data = (Map) arguments.get("data"); + Task task; + if (options != null && (boolean) options.get("merge")) { + task = documentReference.set(data, SetOptions.merge()); + } else { + task = documentReference.set(data); + } + addDefaultListeners("setData", task, result); + break; + } + case "DocumentReference#updateData": + { + Map arguments = call.arguments(); + DocumentReference documentReference = getDocumentReference(arguments); + @SuppressWarnings("unchecked") + Map data = (Map) arguments.get("data"); + Task task = documentReference.update(data); + addDefaultListeners("updateData", task, result); + break; + } + case "DocumentReference#get": + { + Map arguments = call.arguments(); + DocumentReference documentReference = getDocumentReference(arguments); + Source source = getSource(arguments); + Task task = documentReference.get(source); + task.addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(DocumentSnapshot documentSnapshot) { + Map snapshotMap = new HashMap<>(); + Map metadata = new HashMap<>(); + metadata.put( + "hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites()); + metadata.put("isFromCache", documentSnapshot.getMetadata().isFromCache()); + snapshotMap.put("metadata", metadata); + snapshotMap.put("path", documentSnapshot.getReference().getPath()); + if (documentSnapshot.exists()) { + snapshotMap.put("data", documentSnapshot.getData()); + } else { + snapshotMap.put("data", null); + } + result.success(snapshotMap); + } + }) + .addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + result.error("Error performing get", e.getMessage(), null); + } + }); + break; + } + case "DocumentReference#delete": + { + Map arguments = call.arguments(); + DocumentReference documentReference = getDocumentReference(arguments); + Task task = documentReference.delete(); + addDefaultListeners("delete", task, result); + break; + } + case "Firestore#enablePersistence": + { + Map arguments = call.arguments(); + boolean enable = (boolean) arguments.get("enable"); + FirebaseFirestoreSettings.Builder builder = new FirebaseFirestoreSettings.Builder(); + builder.setPersistenceEnabled(enable); + FirebaseFirestoreSettings settings = builder.build(); + getFirestore(arguments).setFirestoreSettings(settings); + result.success(null); + break; + } + case "Firestore#settings": + { + final Map arguments = call.arguments(); + final FirebaseFirestoreSettings.Builder builder = new FirebaseFirestoreSettings.Builder(); + + if (arguments.get("persistenceEnabled") != null) { + builder.setPersistenceEnabled((boolean) arguments.get("persistenceEnabled")); + } + + if (arguments.get("host") != null) { + builder.setHost((String) arguments.get("host")); + } + + if (arguments.get("sslEnabled") != null) { + builder.setSslEnabled((boolean) arguments.get("sslEnabled")); + } + + if (arguments.get("timestampsInSnapshotsEnabled") != null) { + builder.setTimestampsInSnapshotsEnabled( + (boolean) arguments.get("timestampsInSnapshotsEnabled")); + } + + if (arguments.get("cacheSizeBytes") != null) { + builder.setCacheSizeBytes(((Integer) arguments.get("cacheSizeBytes")).longValue()); + } + + FirebaseFirestoreSettings settings = builder.build(); + getFirestore(arguments).setFirestoreSettings(settings); + result.success(null); + break; + } + default: + { + result.notImplemented(); + break; + } + } + } +} + +final class FirestoreMessageCodec extends StandardMessageCodec { + public static final FirestoreMessageCodec INSTANCE = new FirestoreMessageCodec(); + private static final Charset UTF8 = Charset.forName("UTF8"); + private static final byte DATE_TIME = (byte) 128; + private static final byte GEO_POINT = (byte) 129; + private static final byte DOCUMENT_REFERENCE = (byte) 130; + private static final byte BLOB = (byte) 131; + private static final byte ARRAY_UNION = (byte) 132; + private static final byte ARRAY_REMOVE = (byte) 133; + private static final byte DELETE = (byte) 134; + private static final byte SERVER_TIMESTAMP = (byte) 135; + private static final byte TIMESTAMP = (byte) 136; + private static final byte INCREMENT_DOUBLE = (byte) 137; + private static final byte INCREMENT_INTEGER = (byte) 138; + + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + if (value instanceof Date) { + stream.write(DATE_TIME); + writeLong(stream, ((Date) value).getTime()); + } else if (value instanceof Timestamp) { + stream.write(TIMESTAMP); + writeLong(stream, ((Timestamp) value).getSeconds()); + writeInt(stream, ((Timestamp) value).getNanoseconds()); + } else if (value instanceof GeoPoint) { + stream.write(GEO_POINT); + writeAlignment(stream, 8); + writeDouble(stream, ((GeoPoint) value).getLatitude()); + writeDouble(stream, ((GeoPoint) value).getLongitude()); + } else if (value instanceof DocumentReference) { + stream.write(DOCUMENT_REFERENCE); + writeBytes( + stream, ((DocumentReference) value).getFirestore().getApp().getName().getBytes(UTF8)); + writeBytes(stream, ((DocumentReference) value).getPath().getBytes(UTF8)); + } else if (value instanceof Blob) { + stream.write(BLOB); + writeBytes(stream, ((Blob) value).toBytes()); + } else { + super.writeValue(stream, value); + } + } + + @Override + protected Object readValueOfType(byte type, ByteBuffer buffer) { + switch (type) { + case DATE_TIME: + return new Date(buffer.getLong()); + case TIMESTAMP: + return new Timestamp(buffer.getLong(), buffer.getInt()); + case GEO_POINT: + readAlignment(buffer, 8); + return new GeoPoint(buffer.getDouble(), buffer.getDouble()); + case DOCUMENT_REFERENCE: + final byte[] appNameBytes = readBytes(buffer); + String appName = new String(appNameBytes, UTF8); + final FirebaseFirestore firestore = + FirebaseFirestore.getInstance(FirebaseApp.getInstance(appName)); + final byte[] pathBytes = readBytes(buffer); + final String path = new String(pathBytes, UTF8); + return firestore.document(path); + case BLOB: + final byte[] bytes = readBytes(buffer); + return Blob.fromBytes(bytes); + case ARRAY_UNION: + return FieldValue.arrayUnion(toArray(readValue(buffer))); + case ARRAY_REMOVE: + return FieldValue.arrayRemove(toArray(readValue(buffer))); + case DELETE: + return FieldValue.delete(); + case SERVER_TIMESTAMP: + return FieldValue.serverTimestamp(); + case INCREMENT_INTEGER: + final Number integerIncrementValue = (Number) readValue(buffer); + return FieldValue.increment(integerIncrementValue.intValue()); + case INCREMENT_DOUBLE: + final Number doubleIncrementValue = (Number) readValue(buffer); + return FieldValue.increment(doubleIncrementValue.doubleValue()); + default: + return super.readValueOfType(type, buffer); + } + } + + private Object[] toArray(Object source) { + if (source instanceof List) { + return ((List) source).toArray(); + } + + if (source == null) { + return new Object[0]; + } + + String sourceType = source.getClass().getCanonicalName(); + String message = "java.util.List was expected, unable to convert '%s' to an object array"; + throw new IllegalArgumentException(String.format(message, sourceType)); + } +} diff --git a/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java new file mode 100644 index 0000000..362ea0c --- /dev/null +++ b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java @@ -0,0 +1,18 @@ +package io.flutter.plugins.firebase.cloudfirestore; + +import com.google.firebase.components.Component; +import com.google.firebase.components.ComponentRegistrar; +import com.google.firebase.platforminfo.LibraryVersionComponent; +import java.util.Collections; +import java.util.List; + +public class FlutterFirebaseAppRegistrar implements ComponentRegistrar { + private static final String LIBRARY_NAME = "flutter-fire-fst"; + private static final String LIBRARY_VERSION = "0.12.5"; + + @Override + public List> getComponents() { + return Collections.>singletonList( + LibraryVersionComponent.create(LIBRARY_NAME, LIBRARY_VERSION)); + } +} diff --git a/packages/cloud_firestore/example/.flutter-plugins b/packages/cloud_firestore/example/.flutter-plugins new file mode 100644 index 0000000..dddad15 --- /dev/null +++ b/packages/cloud_firestore/example/.flutter-plugins @@ -0,0 +1,2 @@ +cloud_firestore=/Volumes/D1/dev/flutter/google-oem-plugins/packages/cloud_firestore/ +firebase_core=/Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.4.0+3/ diff --git a/packages/cloud_firestore/example/.packages b/packages/cloud_firestore/example/.packages new file mode 100644 index 0000000..d7b9312 --- /dev/null +++ b/packages/cloud_firestore/example/.packages @@ -0,0 +1,64 @@ +# Generated by pub on 2019-06-10 21:12:02.134413. +analyzer:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/analyzer-0.36.3/lib/ +args:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.5.2/lib/ +async:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.2.0/lib/ +boolean_selector:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.4/lib/ +charcode:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.2/lib/ +cloud_firestore:../lib/ +collection:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.14.11/lib/ +convert:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/convert-2.1.1/lib/ +crypto:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.0.6/lib/ +csslib:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/csslib-0.16.0/lib/ +file:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/file-5.0.8/lib/ +firebase_core:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.4.0+3/lib/ +flutter:file:///Users/j3g/apps/flutter/packages/flutter/lib/ +flutter_driver:file:///Users/j3g/apps/flutter/packages/flutter_driver/lib/ +flutter_test:file:///Users/j3g/apps/flutter/packages/flutter_test/lib/ +front_end:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/front_end-0.1.18/lib/ +fuchsia_remote_debug_protocol:file:///Users/j3g/apps/flutter/packages/fuchsia_remote_debug_protocol/lib/ +glob:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/glob-1.1.7/lib/ +html:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/html-0.14.0+2/lib/ +http:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/http-0.12.0+2/lib/ +http_multi_server:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.1.0/lib/ +http_parser:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.3/lib/ +intl:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/intl-0.15.8/lib/ +io:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/io-0.3.3/lib/ +js:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.1+1/lib/ +json_rpc_2:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/json_rpc_2-2.1.0/lib/ +kernel:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/kernel-0.3.18/lib/ +matcher:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.5/lib/ +meta:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.1.6/lib/ +mime:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/mime-0.9.6+3/lib/ +multi_server_socket:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/multi_server_socket-1.0.2/lib/ +node_preamble:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.4/lib/ +package_config:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/package_config-1.0.5/lib/ +package_resolver:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.10/lib/ +path:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.6.2/lib/ +pedantic:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/pedantic-1.7.0/lib/ +platform:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/platform-2.2.0/lib/ +pool:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/pool-1.4.0/lib/ +process:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/process-3.0.9/lib/ +pub_semver:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.2/lib/ +quiver:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/quiver-2.0.3/lib/ +shelf:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.5/lib/ +shelf_packages_handler:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-1.0.4/lib/ +shelf_static:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.8/lib/ +shelf_web_socket:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.3/lib/ +sky_engine:file:///Users/j3g/apps/flutter/bin/cache/pkg/sky_engine/lib/ +source_map_stack_trace:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-1.1.5/lib/ +source_maps:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib/ +source_span:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/ +stack_trace:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/ +stream_channel:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/ +string_scanner:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.4/lib/ +term_glyph:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.1.0/lib/ +test:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/test-1.6.3/lib/ +test_api:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.5/lib/ +test_core:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/test_core-0.2.5/lib/ +typed_data:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.1.6/lib/ +vector_math:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.0.8/lib/ +vm_service_client:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/vm_service_client-0.2.6+2/lib/ +watcher:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+10/lib/ +web_socket_channel:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.0.13/lib/ +yaml:file:///Users/j3g/apps/flutter/.pub-cache/hosted/pub.dartlang.org/yaml-2.1.15/lib/ +firestore_example:lib/ diff --git a/packages/cloud_firestore/example/README.md b/packages/cloud_firestore/example/README.md new file mode 100755 index 0000000..a85ddb7 --- /dev/null +++ b/packages/cloud_firestore/example/README.md @@ -0,0 +1,8 @@ +# firestore_example + +Demonstrates how to use the firestore plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/cloud_firestore/example/android.iml b/packages/cloud_firestore/example/android.iml new file mode 100755 index 0000000..462b903 --- /dev/null +++ b/packages/cloud_firestore/example/android.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/cloud_firestore/example/android/app/build.gradle b/packages/cloud_firestore/example/android/app/build.gradle new file mode 100755 index 0000000..335003e --- /dev/null +++ b/packages/cloud_firestore/example/android/app/build.gradle @@ -0,0 +1,63 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId 'io.flutter.plugins.firebase.firestoreexample' + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} + +apply plugin: 'com.google.gms.google-services' diff --git a/packages/cloud_firestore/example/android/app/google-services.json b/packages/cloud_firestore/example/android/app/google-services.json new file mode 100644 index 0000000..5616df5 --- /dev/null +++ b/packages/cloud_firestore/example/android/app/google-services.json @@ -0,0 +1,42 @@ +{ + "project_info": { + "project_number": "159623150305", + "firebase_url": "https://flutter-firebase-plugins.firebaseio.com", + "project_id": "flutter-firebase-plugins", + "storage_bucket": "flutter-firebase-plugins.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:159623150305:android:236f9daea101f77e", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.firestoreexample" + } + }, + "oauth_client": [ + { + "client_id": "159623150305-q05bbbtsutr02abhips3suj7hujfk4bg.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyChk3KEG7QYrs4kQPLP1tjJNxBTbfCAdgg" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} diff --git a/packages/cloud_firestore/example/android/app/gradle.properties b/packages/cloud_firestore/example/android/app/gradle.properties new file mode 100644 index 0000000..5465fec --- /dev/null +++ b/packages/cloud_firestore/example/android/app/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/packages/cloud_firestore/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/cloud_firestore/example/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9a4163a --- /dev/null +++ b/packages/cloud_firestore/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/cloud_firestore/example/android/app/src/main/AndroidManifest.xml b/packages/cloud_firestore/example/android/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..d116d58 --- /dev/null +++ b/packages/cloud_firestore/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..dd15ba7 --- /dev/null +++ b/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,27 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin; +import io.flutter.plugins.firebase.core.FirebaseCorePlugin; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + CloudFirestorePlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin")); + FirebaseCorePlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebase.core.FirebaseCorePlugin")); + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/firebase/firestoreexample/MainActivity.java b/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/firebase/firestoreexample/MainActivity.java new file mode 100644 index 0000000..3e63aa8 --- /dev/null +++ b/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/firebase/firestoreexample/MainActivity.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.firebase.firestoreexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.java b/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.java new file mode 100644 index 0000000..0c58e26 --- /dev/null +++ b/packages/cloud_firestore/example/android/app/src/main/java/io/flutter/plugins/firebasedatabaseexample/MainActivity.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 MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/cloud_firestore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000..db77bb4 Binary files /dev/null and b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/cloud_firestore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100755 index 0000000..17987b7 Binary files /dev/null and b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 0000000..09d4391 Binary files /dev/null and b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000..d5f1c8d Binary files /dev/null and b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000..4d6372e Binary files /dev/null and b/packages/cloud_firestore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/cloud_firestore/example/android/build.gradle b/packages/cloud_firestore/example/android/build.gradle new file mode 100755 index 0000000..a144a19 --- /dev/null +++ b/packages/cloud_firestore/example/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + repositories { + google() + jcenter() + mavenLocal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.google.gms:google-services:4.2.0' + } +} + +allprojects { + repositories { + google() + jcenter() + mavenLocal() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/cloud_firestore/example/android/gradle.properties b/packages/cloud_firestore/example/android/gradle.properties new file mode 100755 index 0000000..8bd86f6 --- /dev/null +++ b/packages/cloud_firestore/example/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/packages/cloud_firestore/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/cloud_firestore/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..019065d --- /dev/null +++ b/packages/cloud_firestore/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/packages/cloud_firestore/example/android/local.properties b/packages/cloud_firestore/example/android/local.properties new file mode 100644 index 0000000..b3fbec5 --- /dev/null +++ b/packages/cloud_firestore/example/android/local.properties @@ -0,0 +1,2 @@ +sdk.dir=/usr/local/opt/android-sdk +flutter.sdk=/Users/j3g/apps/flutter \ No newline at end of file diff --git a/packages/cloud_firestore/example/android/settings.gradle b/packages/cloud_firestore/example/android/settings.gradle new file mode 100755 index 0000000..6cb349e --- /dev/null +++ b/packages/cloud_firestore/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} \ No newline at end of file diff --git a/packages/cloud_firestore/example/build/ios/Runner.build/Release-iphoneos/Runner.build/dgph b/packages/cloud_firestore/example/build/ios/Runner.build/Release-iphoneos/Runner.build/dgph new file mode 100644 index 0000000..18497c8 Binary files /dev/null and b/packages/cloud_firestore/example/build/ios/Runner.build/Release-iphoneos/Runner.build/dgph differ diff --git a/packages/cloud_firestore/example/firestore_example.iml b/packages/cloud_firestore/example/firestore_example.iml new file mode 100755 index 0000000..1ae40a0 --- /dev/null +++ b/packages/cloud_firestore/example/firestore_example.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/cloud_firestore/example/ios/Flutter/AppFrameworkInfo.plist b/packages/cloud_firestore/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100755 index 0000000..6c2de80 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + UIRequiredDeviceCapabilities + + arm64 + + MinimumOSVersion + 8.0 + + diff --git a/packages/cloud_firestore/example/ios/Flutter/Debug.xcconfig b/packages/cloud_firestore/example/ios/Flutter/Debug.xcconfig new file mode 100755 index 0000000..9803018 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" diff --git a/packages/cloud_firestore/example/ios/Flutter/Generated.xcconfig b/packages/cloud_firestore/example/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000..57f3add --- /dev/null +++ b/packages/cloud_firestore/example/ios/Flutter/Generated.xcconfig @@ -0,0 +1,7 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/j3g/apps/flutter +FLUTTER_APPLICATION_PATH=/Volumes/D1/dev/flutter/google-oem-plugins/packages/cloud_firestore/example +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build/ios +FLUTTER_FRAMEWORK_DIR=/Users/j3g/apps/flutter/bin/cache/artifacts/engine/ios diff --git a/packages/cloud_firestore/example/ios/Flutter/Release.xcconfig b/packages/cloud_firestore/example/ios/Flutter/Release.xcconfig new file mode 100755 index 0000000..a4a8c60 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" diff --git a/packages/cloud_firestore/example/ios/Podfile b/packages/cloud_firestore/example/ios/Podfile new file mode 100644 index 0000000..64ba749 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Podfile @@ -0,0 +1,72 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +target 'Runner' do + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') + } +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/cloud_firestore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/cloud_firestore/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..80980c3 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,500 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5C6F5A711EC3CCCC008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A701EC3CCCC008D64B5 /* GeneratedPluginRegistrant.m */; }; + 7A1ECC911E8EDB6900309407 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A1ECC901E8EDB6900309407 /* GoogleService-Info.plist */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + CE57DC9C9240FBD15E358E24 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E13AAF33B0B411D7B2D38642 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 5C6F5A6F1EC3CCCC008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 5C6F5A701EC3CCCC008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 7A1ECC901E8EDB6900309407 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E13AAF33B0B411D7B2D38642 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + CE57DC9C9240FBD15E358E24 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 840012C8B5EDBCF56B0E4AC1 /* Pods */, + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 5C6F5A6F1EC3CCCC008D64B5 /* GeneratedPluginRegistrant.h */, + 5C6F5A701EC3CCCC008D64B5 /* GeneratedPluginRegistrant.m */, + 7A1ECC901E8EDB6900309407 /* GoogleService-Info.plist */, + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E13AAF33B0B411D7B2D38642 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */, + 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A1ECC911E8EDB6900309407 /* GoogleService-Info.plist in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 5C6F5A711EC3CCCC008D64B5 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firestoreExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firestoreExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/cloud_firestore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/cloud_firestore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100755 index 0000000..21a3cc1 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/cloud_firestore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/cloud_firestore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100755 index 0000000..1c95807 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cloud_firestore/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/cloud_firestore/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100755 index 0000000..21a3cc1 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/cloud_firestore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cloud_firestore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cloud_firestore/example/ios/Runner/AppDelegate.h b/packages/cloud_firestore/example/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..d9e18e9 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/AppDelegate.h @@ -0,0 +1,10 @@ +// 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 +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/cloud_firestore/example/ios/Runner/AppDelegate.m b/packages/cloud_firestore/example/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..a4b51c8 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/AppDelegate.m @@ -0,0 +1,16 @@ +// 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. + +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 0000000..d22f10b --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100755 index 0000000..28c6bf0 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100755 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100755 index 0000000..f091b6b Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100755 index 0000000..4cde121 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100755 index 0000000..d0ef06e Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100755 index 0000000..dcdc230 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100755 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100755 index 0000000..c8f9ed8 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100755 index 0000000..a6d6b86 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100755 index 0000000..a6d6b86 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100755 index 0000000..75b2d16 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100755 index 0000000..c4df70d Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100755 index 0000000..6a84f41 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100755 index 0000000..d0e1f58 Binary files /dev/null and b/packages/cloud_firestore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/cloud_firestore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/cloud_firestore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100755 index 0000000..ebf48f6 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cloud_firestore/example/ios/Runner/Base.lproj/Main.storyboard b/packages/cloud_firestore/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100755 index 0000000..f3c2851 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cloud_firestore/example/ios/Runner/GeneratedPluginRegistrant.h b/packages/cloud_firestore/example/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 0000000..3b700eb --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +#endif /* GeneratedPluginRegistrant_h */ diff --git a/packages/cloud_firestore/example/ios/Runner/GeneratedPluginRegistrant.m b/packages/cloud_firestore/example/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 0000000..5a4260b --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +#import "GeneratedPluginRegistrant.h" +#import +#import + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { + [FLTCloudFirestorePlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTCloudFirestorePlugin"]]; + [FLTFirebaseCorePlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseCorePlugin"]]; +} + +@end diff --git a/packages/cloud_firestore/example/ios/Runner/GoogleService-Info.plist b/packages/cloud_firestore/example/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..9edb534 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,40 @@ + + + + + AD_UNIT_ID_FOR_BANNER_TEST + ca-app-pub-3940256099942544/2934735716 + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + ca-app-pub-3940256099942544/4411468910 + CLIENT_ID + 159623150305-1iiqqggbff817a8bpnalo64nuc3qobid.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.159623150305-1iiqqggbff817a8bpnalo64nuc3qobid + API_KEY + AIzaSyDyzecVw1zXTpBKwfFHxpl7QyYBhimNhUk + GCM_SENDER_ID + 159623150305 + PLIST_VERSION + 1 + BUNDLE_ID + io.flutter.plugins.firestoreExample + PROJECT_ID + flutter-firebase-plugins + STORAGE_BUCKET + flutter-firebase-plugins.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:159623150305:ios:7e8aafdf0bd8d289 + DATABASE_URL + https://flutter-firebase-plugins.firebaseio.com + + diff --git a/packages/cloud_firestore/example/ios/Runner/Info.plist b/packages/cloud_firestore/example/ios/Runner/Info.plist new file mode 100755 index 0000000..4715632 --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + firestore_example + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/cloud_firestore/example/ios/Runner/main.m b/packages/cloud_firestore/example/ios/Runner/main.m new file mode 100644 index 0000000..bec320c --- /dev/null +++ b/packages/cloud_firestore/example/ios/Runner/main.m @@ -0,0 +1,13 @@ +// 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 +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/cloud_firestore/example/lib/main.dart b/packages/cloud_firestore/example/lib/main.dart new file mode 100755 index 0000000..27138f2 --- /dev/null +++ b/packages/cloud_firestore/example/lib/main.dart @@ -0,0 +1,81 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. 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:flutter/material.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +Future main() async { + final FirebaseApp app = await FirebaseApp.configure( + name: 'test', + options: const FirebaseOptions( + googleAppID: '1:79601577497:ios:5f2bcc6ba8cecddd', + gcmSenderID: '79601577497', + apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI-G72PVU', + projectID: 'flutter-firestore', + ), + ); + final Firestore firestore = Firestore(app: app); + await firestore.settings(timestampsInSnapshotsEnabled: true); + + runApp(MaterialApp( + title: 'Firestore Example', home: MyHomePage(firestore: firestore))); +} + +class MessageList extends StatelessWidget { + MessageList({this.firestore}); + + final Firestore firestore; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: firestore.collection('messages').snapshots(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (!snapshot.hasData) return const Text('Loading...'); + final int messageCount = snapshot.data.documents.length; + return ListView.builder( + itemCount: messageCount, + itemBuilder: (_, int index) { + final DocumentSnapshot document = snapshot.data.documents[index]; + return ListTile( + title: Text(document['message'] ?? ''), + subtitle: Text('Message ${index + 1} of $messageCount'), + ); + }, + ); + }, + ); + } +} + +class MyHomePage extends StatelessWidget { + MyHomePage({this.firestore}); + final Firestore firestore; + CollectionReference get messages => firestore.collection('messages'); + + Future _addMessage() async { + await messages.add({ + 'message': 'Hello world!', + 'created_at': FieldValue.serverTimestamp(), + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Firestore Example'), + ), + body: MessageList(firestore: firestore), + floatingActionButton: FloatingActionButton( + onPressed: _addMessage, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/packages/cloud_firestore/example/pubspec.lock b/packages/cloud_firestore/example/pubspec.lock new file mode 100644 index 0000000..5813881 --- /dev/null +++ b/packages/cloud_firestore/example/pubspec.lock @@ -0,0 +1,430 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.36.3" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.12.5" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.8" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0+3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + front_end: + dependency: transitive + description: + name: front_end + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.18" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+2" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0+2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.8" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+1" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + kernel: + dependency: transitive + description: + name: kernel + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.18" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.6+3" + multi_server_socket: + dependency: transitive + description: + name: multi_server_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + package_resolver: + dependency: transitive + description: + name: package_resolver + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.10" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.9" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.2" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.5" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.5" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.8" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test: + dependency: "direct dev" + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.3" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + vm_service_client: + dependency: transitive + description: + name: vm_service_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.6+2" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+10" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.13" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.15" +sdks: + dart: ">=2.2.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/packages/cloud_firestore/example/pubspec.yaml b/packages/cloud_firestore/example/pubspec.yaml new file mode 100755 index 0000000..004c491 --- /dev/null +++ b/packages/cloud_firestore/example/pubspec.yaml @@ -0,0 +1,17 @@ +name: firestore_example +description: Demonstrates how to use the firestore plugin. + +dependencies: + flutter: + sdk: flutter + cloud_firestore: + path: ../ + firebase_core: "^0.4.0" + +dev_dependencies: + flutter_driver: + sdk: flutter + test: any + +flutter: + uses-material-design: true diff --git a/packages/cloud_firestore/example/test_driver/cloud_firestore.dart b/packages/cloud_firestore/example/test_driver/cloud_firestore.dart new file mode 100644 index 0000000..01d2d73 --- /dev/null +++ b/packages/cloud_firestore/example/test_driver/cloud_firestore.dart @@ -0,0 +1,192 @@ +import 'dart:async'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; + +void main() { + final Completer completer = Completer(); + enableFlutterDriverExtension(handler: (_) => completer.future); + tearDownAll(() => completer.complete(null)); + + group('$Firestore', () { + Firestore firestore; + Firestore firestoreWithSettings; + + setUp(() async { + final FirebaseOptions firebaseOptions = const FirebaseOptions( + googleAppID: '1:79601577497:ios:5f2bcc6ba8cecddd', + gcmSenderID: '79601577497', + apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI-G72PVU', + projectID: 'flutter-firestore', + ); + final FirebaseApp app = await FirebaseApp.configure( + name: 'test', + options: firebaseOptions, + ); + final FirebaseApp app2 = await FirebaseApp.configure( + name: 'test2', + options: firebaseOptions, + ); + firestore = Firestore(app: app); + firestoreWithSettings = Firestore(app: app2); + await firestoreWithSettings.settings( + persistenceEnabled: true, + host: null, + sslEnabled: true, + timestampsInSnapshotsEnabled: true, + cacheSizeBytes: 1048576, + ); + }); + + test('getDocumentsWithFirestoreSettings', () async { + final Query query = firestoreWithSettings.collection('messages').limit(1); + final QuerySnapshot querySnapshot = await query.getDocuments(); + expect(querySnapshot.documents.length, 1); + }); + + test('getDocumentsFromCollection', () async { + final Query query = firestore + .collection('messages') + .where('message', isEqualTo: 'Hello world!') + .limit(1); + final QuerySnapshot querySnapshot = await query.getDocuments(); + expect(querySnapshot.documents.first['message'], 'Hello world!'); + final DocumentReference firstDoc = + querySnapshot.documents.first.reference; + final DocumentSnapshot documentSnapshot = await firstDoc.get(); + expect(documentSnapshot.data['message'], 'Hello world!'); + final DocumentSnapshot cachedSnapshot = + await firstDoc.get(source: Source.cache); + expect(cachedSnapshot.data['message'], 'Hello world!'); + final DocumentSnapshot snapshot = await firstDoc.snapshots().first; + expect(snapshot.data['message'], 'Hello world!'); + }); + + test('getDocumentsFromCollectionGroup', () async { + final Query query = firestore + .collectionGroup('reviews') + .where('stars', isEqualTo: 5) + .limit(1); + final QuerySnapshot querySnapshot = await query.getDocuments(); + expect(querySnapshot.documents.first['stars'], 5); + }); + + test('increment', () async { + final DocumentReference ref = firestore.collection('messages').document(); + await ref.setData({ + 'message': 1, + 'created_at': FieldValue.serverTimestamp(), + }); + DocumentSnapshot snapshot = await ref.get(); + expect(snapshot.data['message'], 1); + await ref.updateData({ + 'message': FieldValue.increment(1), + }); + snapshot = await ref.get(); + expect(snapshot.data['message'], 2); + await ref.updateData({ + 'message': FieldValue.increment(40.1), + }); + snapshot = await ref.get(); + expect(snapshot.data['message'], 42.1); + await ref.delete(); + }); + + test('runTransaction', () async { + final DocumentReference ref = firestore.collection('messages').document(); + await ref.setData({ + 'message': 'testing', + 'created_at': FieldValue.serverTimestamp(), + }); + final DocumentSnapshot initialSnapshot = await ref.get(); + expect(initialSnapshot.data['message'], 'testing'); + final dynamic result = await firestore.runTransaction( + (Transaction tx) async { + final DocumentSnapshot snapshot = await tx.get(ref); + final Map updatedData = + Map.from(snapshot.data); + updatedData['message'] = 'testing2'; + await tx.update(ref, updatedData); + return updatedData; + }, + ); + expect(result['message'], 'testing2'); + await ref.delete(); + final DocumentSnapshot nonexistentSnapshot = await ref.get(); + expect(nonexistentSnapshot.data, null); + expect(nonexistentSnapshot.exists, false); + }); + + test('pagination', () async { + // Populate the database with two test documents + final CollectionReference messages = firestore.collection('messages'); + final DocumentReference doc1 = messages.document(); + // Use document ID as a unique identifier to ensure that we don't + // collide with other tests running against this database. + final String testRun = doc1.documentID; + await doc1.setData({ + 'message': 'pagination testing1', + 'test_run': testRun, + 'created_at': FieldValue.serverTimestamp(), + }); + final DocumentSnapshot snapshot1 = await doc1.get(); + final DocumentReference doc2 = messages.document(); + await doc2.setData({ + 'message': 'pagination testing2', + 'test_run': testRun, + 'created_at': FieldValue.serverTimestamp(), + }); + final DocumentSnapshot snapshot2 = await doc2.get(); + + QuerySnapshot snapshot; + List results; + + // startAtDocument + snapshot = await messages + .orderBy('created_at') + .where('test_run', isEqualTo: testRun) + .startAtDocument(snapshot1) + .getDocuments(); + results = snapshot.documents; + expect(results.length, 2); + expect(results[0].data['message'], 'pagination testing1'); + expect(results[1].data['message'], 'pagination testing2'); + + // startAfterDocument + snapshot = await messages + .orderBy('created_at') + .where('test_run', isEqualTo: testRun) + .startAfterDocument(snapshot1) + .getDocuments(); + results = snapshot.documents; + expect(results.length, 1); + expect(results[0].data['message'], 'pagination testing2'); + + // endAtDocument + snapshot = await messages + .orderBy('created_at') + .where('test_run', isEqualTo: testRun) + .endAtDocument(snapshot2) + .getDocuments(); + results = snapshot.documents; + expect(results.length, 2); + expect(results[0].data['message'], 'pagination testing1'); + expect(results[1].data['message'], 'pagination testing2'); + + // endBeforeDocument + snapshot = await messages + .orderBy('created_at') + .where('test_run', isEqualTo: testRun) + .endBeforeDocument(snapshot2) + .getDocuments(); + results = snapshot.documents; + expect(results.length, 1); + expect(results[0].data['message'], 'pagination testing1'); + + // Clean up + await doc1.delete(); + await doc2.delete(); + }); + }); +} diff --git a/packages/cloud_firestore/example/test_driver/cloud_firestore_test.dart b/packages/cloud_firestore/example/test_driver/cloud_firestore_test.dart new file mode 100644 index 0000000..cc11eae --- /dev/null +++ b/packages/cloud_firestore/example/test_driver/cloud_firestore_test.dart @@ -0,0 +1,7 @@ +import 'package:flutter_driver/flutter_driver.dart'; + +void main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); +} diff --git a/packages/cloud_firestore/ios/Assets/.gitkeep b/packages/cloud_firestore/ios/Assets/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.h b/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.h new file mode 100644 index 0000000..3a9035a --- /dev/null +++ b/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.h @@ -0,0 +1,8 @@ +// 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 + +@interface FLTCloudFirestorePlugin : NSObject +@end diff --git a/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m b/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m new file mode 100644 index 0000000..8950b4a --- /dev/null +++ b/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m @@ -0,0 +1,653 @@ +// 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 "CloudFirestorePlugin.h" + +#import + +#define LIBRARY_NAME @"flutter-firebase_cloud_firestore" +#define LIBRARY_VERSION @"0.12.5" + +static FlutterError *getFlutterError(NSError *error) { + if (error == nil) return nil; + + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code] + message:error.domain + details:error.localizedDescription]; +} + +static FIRFirestore *getFirestore(NSDictionary *arguments) { + FIRApp *app = [FIRApp appNamed:arguments[@"app"]]; + return [FIRFirestore firestoreForApp:app]; +} + +static FIRDocumentReference *getDocumentReference(NSDictionary *arguments) { + return [getFirestore(arguments) documentWithPath:arguments[@"path"]]; +} + +static NSArray *getDocumentValues(NSDictionary *document, NSArray *orderBy, + BOOL *isCollectionGroup) { + NSMutableArray *values = [[NSMutableArray alloc] init]; + NSDictionary *documentData = document[@"data"]; + if (orderBy) { + for (id item in orderBy) { + NSArray *orderByParameters = item; + NSString *fieldName = orderByParameters[0]; + [values addObject:[documentData objectForKey:fieldName]]; + } + } + if (isCollectionGroup) { + NSString *path = document[@"path"]; + [values addObject:path]; + } else { + NSString *documentId = document[@"id"]; + [values addObject:documentId]; + } + return values; +} + +static FIRQuery *getQuery(NSDictionary *arguments) { + NSNumber *data = arguments[@"isCollectionGroup"]; + BOOL isCollectionGroup = data.boolValue; + FIRQuery *query; + if (isCollectionGroup) { + query = [getFirestore(arguments) collectionGroupWithID:arguments[@"path"]]; + } else { + query = [getFirestore(arguments) collectionWithPath:arguments[@"path"]]; + } + NSDictionary *parameters = arguments[@"parameters"]; + NSArray *whereConditions = parameters[@"where"]; + for (id item in whereConditions) { + NSArray *condition = item; + NSString *fieldName = condition[0]; + NSString *op = condition[1]; + id value = condition[2]; + if ([op isEqualToString:@"=="]) { + query = [query queryWhereField:fieldName isEqualTo:value]; + } else if ([op isEqualToString:@"<"]) { + query = [query queryWhereField:fieldName isLessThan:value]; + } else if ([op isEqualToString:@"<="]) { + query = [query queryWhereField:fieldName isLessThanOrEqualTo:value]; + } else if ([op isEqualToString:@">"]) { + query = [query queryWhereField:fieldName isGreaterThan:value]; + } else if ([op isEqualToString:@">="]) { + query = [query queryWhereField:fieldName isGreaterThanOrEqualTo:value]; + } else if ([op isEqualToString:@"array-contains"]) { + query = [query queryWhereField:fieldName arrayContains:value]; + } else { + // Unsupported operator + } + } + id limit = parameters[@"limit"]; + if (limit) { + NSNumber *length = limit; + query = [query queryLimitedTo:[length intValue]]; + } + NSArray *orderBy = parameters[@"orderBy"]; + if (orderBy) { + for (NSArray *orderByParameters in orderBy) { + NSString *fieldName = orderByParameters[0]; + NSNumber *descending = orderByParameters[1]; + query = [query queryOrderedByField:fieldName descending:[descending boolValue]]; + } + } + id startAt = parameters[@"startAt"]; + if (startAt) { + NSArray *startAtValues = startAt; + query = [query queryStartingAtValues:startAtValues]; + } + id startAtDocument = parameters[@"startAtDocument"]; + if (startAtDocument) { + NSArray *orderByParameters = [orderBy lastObject]; + NSNumber *descending = orderByParameters[1]; + query = [query queryOrderedByFieldPath:FIRFieldPath.documentID + descending:[descending boolValue]]; + query = [query + queryStartingAtValues:getDocumentValues(startAtDocument, orderBy, isCollectionGroup)]; + } + id startAfter = parameters[@"startAfter"]; + if (startAfter) { + NSArray *startAfterValues = startAfter; + query = [query queryStartingAfterValues:startAfterValues]; + } + id startAfterDocument = parameters[@"startAfterDocument"]; + if (startAfterDocument) { + NSArray *orderByParameters = [orderBy lastObject]; + NSNumber *descending = orderByParameters[1]; + query = [query queryOrderedByFieldPath:FIRFieldPath.documentID + descending:[descending boolValue]]; + query = [query + queryStartingAfterValues:getDocumentValues(startAfterDocument, orderBy, isCollectionGroup)]; + } + id endAt = parameters[@"endAt"]; + if (endAt) { + NSArray *endAtValues = endAt; + query = [query queryEndingAtValues:endAtValues]; + } + id endAtDocument = parameters[@"endAtDocument"]; + if (endAtDocument) { + NSArray *orderByParameters = [orderBy lastObject]; + NSNumber *descending = orderByParameters[1]; + query = [query queryOrderedByFieldPath:FIRFieldPath.documentID + descending:[descending boolValue]]; + query = + [query queryEndingAtValues:getDocumentValues(endAtDocument, orderBy, isCollectionGroup)]; + } + id endBefore = parameters[@"endBefore"]; + if (endBefore) { + NSArray *endBeforeValues = endBefore; + query = [query queryEndingBeforeValues:endBeforeValues]; + } + id endBeforeDocument = parameters[@"endBeforeDocument"]; + if (endBeforeDocument) { + NSArray *orderByParameters = [orderBy lastObject]; + NSNumber *descending = orderByParameters[1]; + query = [query queryOrderedByFieldPath:FIRFieldPath.documentID + descending:[descending boolValue]]; + query = [query + queryEndingBeforeValues:getDocumentValues(endBeforeDocument, orderBy, isCollectionGroup)]; + } + return query; +} + +static FIRFirestoreSource getSource(NSDictionary *arguments) { + NSString *source = arguments[@"source"]; + if ([@"server" isEqualToString:source]) { + return FIRFirestoreSourceServer; + } + if ([@"cache" isEqualToString:source]) { + return FIRFirestoreSourceCache; + } + return FIRFirestoreSourceDefault; +} + +static NSDictionary *parseQuerySnapshot(FIRQuerySnapshot *snapshot) { + NSMutableArray *paths = [NSMutableArray array]; + NSMutableArray *documents = [NSMutableArray array]; + NSMutableArray *metadatas = [NSMutableArray array]; + for (FIRDocumentSnapshot *document in snapshot.documents) { + [paths addObject:document.reference.path]; + [documents addObject:document.data]; + [metadatas addObject:@{ + @"hasPendingWrites" : @(document.metadata.hasPendingWrites), + @"isFromCache" : @(document.metadata.isFromCache), + }]; + } + NSMutableArray *documentChanges = [NSMutableArray array]; + for (FIRDocumentChange *documentChange in snapshot.documentChanges) { + NSString *type; + switch (documentChange.type) { + case FIRDocumentChangeTypeAdded: + type = @"DocumentChangeType.added"; + break; + case FIRDocumentChangeTypeModified: + type = @"DocumentChangeType.modified"; + break; + case FIRDocumentChangeTypeRemoved: + type = @"DocumentChangeType.removed"; + break; + } + [documentChanges addObject:@{ + @"type" : type, + @"document" : documentChange.document.data, + @"path" : documentChange.document.reference.path, + @"oldIndex" : [NSNumber numberWithUnsignedInteger:documentChange.oldIndex], + @"newIndex" : [NSNumber numberWithUnsignedInteger:documentChange.newIndex], + @"metadata" : @{ + @"hasPendingWrites" : @(documentChange.document.metadata.hasPendingWrites), + @"isFromCache" : @(documentChange.document.metadata.isFromCache), + }, + }]; + } + return @{ + @"paths" : paths, + @"documentChanges" : documentChanges, + @"documents" : documents, + @"metadatas" : metadatas, + }; +} + +const UInt8 DATE_TIME = 128; +const UInt8 GEO_POINT = 129; +const UInt8 DOCUMENT_REFERENCE = 130; +const UInt8 BLOB = 131; +const UInt8 ARRAY_UNION = 132; +const UInt8 ARRAY_REMOVE = 133; +const UInt8 DELETE = 134; +const UInt8 SERVER_TIMESTAMP = 135; +const UInt8 TIMESTAMP = 136; +const UInt8 INCREMENT_DOUBLE = 137; +const UInt8 INCREMENT_INTEGER = 138; + +@interface FirestoreWriter : FlutterStandardWriter +- (void)writeValue:(id)value; +@end + +@implementation FirestoreWriter : FlutterStandardWriter +- (void)writeValue:(id)value { + if ([value isKindOfClass:[NSDate class]]) { + [self writeByte:DATE_TIME]; + NSDate *date = value; + NSTimeInterval time = date.timeIntervalSince1970; + SInt64 ms = (SInt64)(time * 1000.0); + [self writeBytes:&ms length:8]; + } else if ([value isKindOfClass:[FIRTimestamp class]]) { + FIRTimestamp *timestamp = value; + SInt64 seconds = timestamp.seconds; + int nanoseconds = timestamp.nanoseconds; + [self writeByte:TIMESTAMP]; + [self writeBytes:(UInt8 *)&seconds length:8]; + [self writeBytes:(UInt8 *)&nanoseconds length:4]; + } else if ([value isKindOfClass:[FIRGeoPoint class]]) { + FIRGeoPoint *geoPoint = value; + Float64 latitude = geoPoint.latitude; + Float64 longitude = geoPoint.longitude; + [self writeByte:GEO_POINT]; + [self writeAlignment:8]; + [self writeBytes:(UInt8 *)&latitude length:8]; + [self writeBytes:(UInt8 *)&longitude length:8]; + } else if ([value isKindOfClass:[FIRDocumentReference class]]) { + FIRDocumentReference *document = value; + NSString *documentPath = [document path]; + [self writeByte:DOCUMENT_REFERENCE]; + [self writeUTF8:document.firestore.app.name]; + [self writeUTF8:documentPath]; + } else if ([value isKindOfClass:[NSData class]]) { + NSData *blob = value; + [self writeByte:BLOB]; + [self writeSize:(UInt32)blob.length]; + [self writeData:blob]; + } else { + [super writeValue:value]; + } +} +@end + +@interface FirestoreReader : FlutterStandardReader +- (id)readValueOfType:(UInt8)type; +@end + +@implementation FirestoreReader +- (id)readValueOfType:(UInt8)type { + switch (type) { + case DATE_TIME: { + SInt64 value; + [self readBytes:&value length:8]; + return [NSDate dateWithTimeIntervalSince1970:(value / 1000.0)]; + } + case TIMESTAMP: { + SInt64 seconds; + int nanoseconds; + [self readBytes:&seconds length:8]; + [self readBytes:&nanoseconds length:4]; + return [[FIRTimestamp alloc] initWithSeconds:seconds nanoseconds:nanoseconds]; + } + case GEO_POINT: { + Float64 latitude; + Float64 longitude; + [self readAlignment:8]; + [self readBytes:&latitude length:8]; + [self readBytes:&longitude length:8]; + return [[FIRGeoPoint alloc] initWithLatitude:latitude longitude:longitude]; + } + case DOCUMENT_REFERENCE: { + NSString *appName = [self readUTF8]; + FIRFirestore *firestore = [FIRFirestore firestoreForApp:[FIRApp appNamed:appName]]; + NSString *documentPath = [self readUTF8]; + return [firestore documentWithPath:documentPath]; + } + case BLOB: { + UInt32 elementCount = [self readSize]; + return [self readData:elementCount]; + } + case ARRAY_UNION: { + return [FIRFieldValue fieldValueForArrayUnion:[self readValue]]; + } + case ARRAY_REMOVE: { + return [FIRFieldValue fieldValueForArrayRemove:[self readValue]]; + } + case DELETE: { + return [FIRFieldValue fieldValueForDelete]; + } + case SERVER_TIMESTAMP: { + return [FIRFieldValue fieldValueForServerTimestamp]; + } + case INCREMENT_DOUBLE: { + NSNumber *value = [self readValue]; + return [FIRFieldValue fieldValueForDoubleIncrement:value.doubleValue]; + } + case INCREMENT_INTEGER: { + NSNumber *value = [self readValue]; + return [FIRFieldValue fieldValueForIntegerIncrement:value.intValue]; + } + default: + return [super readValueOfType:type]; + } +} +@end + +@interface FirestoreReaderWriter : FlutterStandardReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data; +- (FlutterStandardReader *)readerWithData:(NSData *)data; +@end + +@implementation FirestoreReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[FirestoreWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[FirestoreReader alloc] initWithData:data]; +} +@end + +@interface FLTCloudFirestorePlugin () +@property(nonatomic, retain) FlutterMethodChannel *channel; +@end + +@implementation FLTCloudFirestorePlugin { + NSMutableDictionary> *_listeners; + int _nextListenerHandle; + NSMutableDictionary *transactions; + NSMutableDictionary *transactionResults; + NSMutableDictionary *_batches; + int _nextBatchHandle; +} + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FirestoreReaderWriter *firestoreReaderWriter = [FirestoreReaderWriter new]; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/cloud_firestore" + binaryMessenger:[registrar messenger] + codec:[FlutterStandardMethodCodec + codecWithReaderWriter:firestoreReaderWriter]]; + FLTCloudFirestorePlugin *instance = [[FLTCloudFirestorePlugin alloc] init]; + instance.channel = channel; + [registrar addMethodCallDelegate:instance channel:channel]; + + SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:"); + if ([FIRApp respondsToSelector:sel]) { + [FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION]; + } +} + +- (instancetype)init { + self = [super init]; + if (self) { + if (![FIRApp appNamed:@"__FIRAPP_DEFAULT"]) { + NSLog(@"Configuring the default Firebase app..."); + [FIRApp configure]; + NSLog(@"Configured the default Firebase app %@.", [FIRApp defaultApp].name); + } + _listeners = [NSMutableDictionary> dictionary]; + _batches = [NSMutableDictionary dictionary]; + _nextListenerHandle = 0; + _nextBatchHandle = 0; + transactions = [NSMutableDictionary dictionary]; + transactionResults = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + __weak __typeof__(self) weakSelf = self; + void (^defaultCompletionBlock)(NSError *) = ^(NSError *error) { + result(getFlutterError(error)); + }; + if ([@"Firestore#runTransaction" isEqualToString:call.method]) { + [getFirestore(call.arguments) + runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) { + NSNumber *transactionId = call.arguments[@"transactionId"]; + NSNumber *transactionTimeout = call.arguments[@"transactionTimeout"]; + + self->transactions[transactionId] = transaction; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + [weakSelf.channel invokeMethod:@"DoTransaction" + arguments:call.arguments + result:^(id doTransactionResult) { + FLTCloudFirestorePlugin *currentSelf = weakSelf; + currentSelf->transactionResults[transactionId] = + doTransactionResult; + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait( + semaphore, + dispatch_time(DISPATCH_TIME_NOW, [transactionTimeout integerValue] * 1000000)); + + return self->transactionResults[transactionId]; + } + completion:^(id transactionResult, NSError *error) { + if (error != nil) { + result([FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code] + message:error.localizedDescription + details:nil]); + } + result(transactionResult); + }]; + } else if ([@"Transaction#get" isEqualToString:call.method]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSNumber *transactionId = call.arguments[@"transactionId"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRTransaction *transaction = self->transactions[transactionId]; + NSError *error = [[NSError alloc] init]; + + FIRDocumentSnapshot *snapshot = [transaction getDocument:document error:&error]; + + if (error != nil) { + result([FlutterError errorWithCode:[NSString stringWithFormat:@"%tu", [error code]] + message:[error localizedDescription] + details:nil]); + } else if (snapshot != nil) { + result(@{ + @"path" : snapshot.reference.path, + @"data" : snapshot.exists ? snapshot.data : [NSNull null], + @"metadata" : @{ + @"hasPendingWrites" : @(snapshot.metadata.hasPendingWrites), + @"isFromCache" : @(snapshot.metadata.isFromCache), + }, + }); + } else { + result(nil); + } + }); + } else if ([@"Transaction#update" isEqualToString:call.method]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSNumber *transactionId = call.arguments[@"transactionId"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRTransaction *transaction = self->transactions[transactionId]; + + [transaction updateData:call.arguments[@"data"] forDocument:document]; + result(nil); + }); + } else if ([@"Transaction#set" isEqualToString:call.method]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSNumber *transactionId = call.arguments[@"transactionId"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRTransaction *transaction = self->transactions[transactionId]; + + [transaction setData:call.arguments[@"data"] forDocument:document]; + result(nil); + }); + } else if ([@"Transaction#delete" isEqualToString:call.method]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSNumber *transactionId = call.arguments[@"transactionId"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRTransaction *transaction = self->transactions[transactionId]; + + [transaction deleteDocument:document]; + result(nil); + }); + } else if ([@"DocumentReference#setData" isEqualToString:call.method]) { + NSDictionary *options = call.arguments[@"options"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + if (![options isEqual:[NSNull null]] && + [options[@"merge"] isEqual:[NSNumber numberWithBool:YES]]) { + [document setData:call.arguments[@"data"] merge:YES completion:defaultCompletionBlock]; + } else { + [document setData:call.arguments[@"data"] completion:defaultCompletionBlock]; + } + } else if ([@"DocumentReference#updateData" isEqualToString:call.method]) { + FIRDocumentReference *document = getDocumentReference(call.arguments); + [document updateData:call.arguments[@"data"] completion:defaultCompletionBlock]; + } else if ([@"DocumentReference#delete" isEqualToString:call.method]) { + FIRDocumentReference *document = getDocumentReference(call.arguments); + [document deleteDocumentWithCompletion:defaultCompletionBlock]; + } else if ([@"DocumentReference#get" isEqualToString:call.method]) { + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRFirestoreSource source = getSource(call.arguments); + [document + getDocumentWithSource:source + completion:^(FIRDocumentSnapshot *_Nullable snapshot, NSError *_Nullable error) { + if (snapshot == nil) { + result(getFlutterError(error)); + } else { + result(@{ + @"path" : snapshot.reference.path, + @"data" : snapshot.exists ? snapshot.data : [NSNull null], + @"metadata" : @{ + @"hasPendingWrites" : @(snapshot.metadata.hasPendingWrites), + @"isFromCache" : @(snapshot.metadata.isFromCache), + }, + }); + } + }]; + } else if ([@"Query#addSnapshotListener" isEqualToString:call.method]) { + __block NSNumber *handle = [NSNumber numberWithInt:_nextListenerHandle++]; + FIRQuery *query; + @try { + query = getQuery(call.arguments); + } @catch (NSException *exception) { + result([FlutterError errorWithCode:@"invalid_query" + message:[exception name] + details:[exception reason]]); + } + id listener = [query + addSnapshotListener:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) { + if (snapshot == nil) { + result(getFlutterError(error)); + return; + } + NSMutableDictionary *arguments = [parseQuerySnapshot(snapshot) mutableCopy]; + [arguments setObject:handle forKey:@"handle"]; + [weakSelf.channel invokeMethod:@"QuerySnapshot" arguments:arguments]; + }]; + _listeners[handle] = listener; + result(handle); + } else if ([@"Query#addDocumentListener" isEqualToString:call.method]) { + __block NSNumber *handle = [NSNumber numberWithInt:_nextListenerHandle++]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + id listener = + [document addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *_Nullable error) { + if (snapshot == nil) { + result(getFlutterError(error)); + return; + } + [weakSelf.channel invokeMethod:@"DocumentSnapshot" + arguments:@{ + @"handle" : handle, + @"path" : snapshot ? snapshot.reference.path : [NSNull null], + @"data" : snapshot.exists ? snapshot.data : [NSNull null], + @"metadata" : snapshot ? @{ + @"hasPendingWrites" : @(snapshot.metadata.hasPendingWrites), + @"isFromCache" : @(snapshot.metadata.isFromCache), + } + : [NSNull null], + }]; + }]; + _listeners[handle] = listener; + result(handle); + } else if ([@"Query#getDocuments" isEqualToString:call.method]) { + FIRQuery *query; + FIRFirestoreSource source = getSource(call.arguments); + @try { + query = getQuery(call.arguments); + } @catch (NSException *exception) { + result([FlutterError errorWithCode:@"invalid_query" + message:[exception name] + details:[exception reason]]); + } + + [query + getDocumentsWithSource:source + completion:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) { + if (snapshot == nil) { + result(getFlutterError(error)); + return; + } + result(parseQuerySnapshot(snapshot)); + }]; + } else if ([@"Query#removeListener" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + [[_listeners objectForKey:handle] remove]; + [_listeners removeObjectForKey:handle]; + result(nil); + } else if ([@"WriteBatch#create" isEqualToString:call.method]) { + __block NSNumber *handle = [NSNumber numberWithInt:_nextBatchHandle++]; + FIRWriteBatch *batch = [getFirestore(call.arguments) batch]; + _batches[handle] = batch; + result(handle); + } else if ([@"WriteBatch#setData" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + NSDictionary *options = call.arguments[@"options"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRWriteBatch *batch = [_batches objectForKey:handle]; + if (![options isEqual:[NSNull null]] && + [options[@"merge"] isEqual:[NSNumber numberWithBool:YES]]) { + [batch setData:call.arguments[@"data"] forDocument:document merge:YES]; + } else { + [batch setData:call.arguments[@"data"] forDocument:document]; + } + result(nil); + } else if ([@"WriteBatch#updateData" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRWriteBatch *batch = [_batches objectForKey:handle]; + [batch updateData:call.arguments[@"data"] forDocument:document]; + result(nil); + } else if ([@"WriteBatch#delete" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + FIRDocumentReference *document = getDocumentReference(call.arguments); + FIRWriteBatch *batch = [_batches objectForKey:handle]; + [batch deleteDocument:document]; + result(nil); + } else if ([@"WriteBatch#commit" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + FIRWriteBatch *batch = [_batches objectForKey:handle]; + [batch commitWithCompletion:defaultCompletionBlock]; + [_batches removeObjectForKey:handle]; + } else if ([@"Firestore#enablePersistence" isEqualToString:call.method]) { + bool enable = (bool)call.arguments[@"enable"]; + FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init]; + settings.persistenceEnabled = enable; + FIRFirestore *db = getFirestore(call.arguments); + db.settings = settings; + result(nil); + } else if ([@"Firestore#settings" isEqualToString:call.method]) { + FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init]; + if (![call.arguments[@"persistenceEnabled"] isEqual:[NSNull null]]) { + settings.persistenceEnabled = (bool)call.arguments[@"persistenceEnabled"]; + } + if (![call.arguments[@"host"] isEqual:[NSNull null]]) { + settings.host = (NSString *)call.arguments[@"host"]; + } + if (![call.arguments[@"sslEnabled"] isEqual:[NSNull null]]) { + settings.sslEnabled = (bool)call.arguments[@"sslEnabled"]; + } + if (![call.arguments[@"timestampsInSnapshotsEnabled"] isEqual:[NSNull null]]) { + settings.timestampsInSnapshotsEnabled = (bool)call.arguments[@"timestampsInSnapshotsEnabled"]; + } + if (![call.arguments[@"cacheSizeBytes"] isEqual:[NSNull null]]) { + settings.cacheSizeBytes = ((NSNumber *)call.arguments[@"cacheSizeBytes"]).intValue; + } + FIRFirestore *db = getFirestore(call.arguments); + db.settings = settings; + result(nil); + } else { + result(FlutterMethodNotImplemented); + } +} + +@end diff --git a/packages/cloud_firestore/ios/cloud_firestore.podspec b/packages/cloud_firestore/ios/cloud_firestore.podspec new file mode 100755 index 0000000..e88645e --- /dev/null +++ b/packages/cloud_firestore/ios/cloud_firestore.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'cloud_firestore' + s.version = '0.0.1' + s.summary = 'Firestore plugin for Flutter.' + s.description = <<-DESC +Firestore plugin for Flutter. + DESC + s.homepage = 'https://github.com/flutter/firestore' + s.license = { :file => '../LICENSE' } + s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.ios.deployment_target = '8.0' + s.dependency 'Flutter' + s.dependency 'Firebase/Core' + s.dependency 'Firebase/Firestore', '~> 6.0' + s.static_framework = true +end diff --git a/packages/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/lib/cloud_firestore.dart new file mode 100755 index 0000000..ec4a21a --- /dev/null +++ b/packages/cloud_firestore/lib/cloud_firestore.dart @@ -0,0 +1,35 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library cloud_firestore; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'dart:ui' show hashValues, hashList; + +import 'package:collection/collection.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; + +import 'src/utils/push_id_generator.dart'; + +part 'src/blob.dart'; +part 'src/collection_reference.dart'; +part 'src/document_change.dart'; +part 'src/document_reference.dart'; +part 'src/document_snapshot.dart'; +part 'src/field_value.dart'; +part 'src/firestore.dart'; +part 'src/firestore_message_codec.dart'; +part 'src/geo_point.dart'; +part 'src/query.dart'; +part 'src/query_snapshot.dart'; +part 'src/snapshot_metadata.dart'; +part 'src/timestamp.dart'; +part 'src/transaction.dart'; +part 'src/write_batch.dart'; +part 'src/source.dart'; diff --git a/packages/cloud_firestore/lib/src/blob.dart b/packages/cloud_firestore/lib/src/blob.dart new file mode 100644 index 0000000..665efea --- /dev/null +++ b/packages/cloud_firestore/lib/src/blob.dart @@ -0,0 +1,19 @@ +// Copyright 2018, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +class Blob { + const Blob(this.bytes); + + final Uint8List bytes; + + @override + bool operator ==(dynamic other) => + other is Blob && + const DeepCollectionEquality().equals(other.bytes, bytes); + + @override + int get hashCode => hashList(bytes); +} diff --git a/packages/cloud_firestore/lib/src/collection_reference.dart b/packages/cloud_firestore/lib/src/collection_reference.dart new file mode 100644 index 0000000..ffa5e01 --- /dev/null +++ b/packages/cloud_firestore/lib/src/collection_reference.dart @@ -0,0 +1,61 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// A CollectionReference object can be used for adding documents, getting +/// document references, and querying for documents (using the methods +/// inherited from [Query]). +class CollectionReference extends Query { + CollectionReference._(Firestore firestore, List pathComponents) + : super._(firestore: firestore, pathComponents: pathComponents); + + /// ID of the referenced collection. + String get id => _pathComponents.isEmpty ? null : _pathComponents.last; + + /// For subcollections, parent returns the containing [DocumentReference]. + /// + /// For root collections, null is returned. + DocumentReference parent() { + if (_pathComponents.length < 2) { + return null; + } + return DocumentReference._( + firestore, + (List.from(_pathComponents)..removeLast()), + ); + } + + /// A string containing the slash-separated path to this CollectionReference + /// (relative to the root of the database). + String get path => _path; + + /// Returns a `DocumentReference` with the provided path. + /// + /// If no [path] is provided, an auto-generated ID is used. + /// + /// The unique key generated is prefixed with a client-generated timestamp + /// so that the resulting list will be chronologically-sorted. + DocumentReference document([String path]) { + List childPath; + if (path == null) { + final String key = PushIdGenerator.generatePushChildName(); + childPath = List.from(_pathComponents)..add(key); + } else { + childPath = List.from(_pathComponents)..addAll(path.split(('/'))); + } + return DocumentReference._(firestore, childPath); + } + + /// Returns a `DocumentReference` with an auto-generated ID, after + /// populating it with provided [data]. + /// + /// The unique key generated is prefixed with a client-generated timestamp + /// so that the resulting list will be chronologically-sorted. + Future add(Map data) async { + final DocumentReference newDocument = document(); + await newDocument.setData(data); + return newDocument; + } +} diff --git a/packages/cloud_firestore/lib/src/document_change.dart b/packages/cloud_firestore/lib/src/document_change.dart new file mode 100644 index 0000000..8409d51 --- /dev/null +++ b/packages/cloud_firestore/lib/src/document_change.dart @@ -0,0 +1,61 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// An enumeration of document change types. +enum DocumentChangeType { + /// Indicates a new document was added to the set of documents matching the + /// query. + added, + + /// Indicates a document within the query was modified. + modified, + + /// Indicates a document within the query was removed (either deleted or no + /// longer matches the query. + removed, +} + +/// A DocumentChange represents a change to the documents matching a query. +/// +/// It contains the document affected and the type of change that occurred +/// (added, modified, or removed). +class DocumentChange { + DocumentChange._(Map data, this._firestore) + : oldIndex = data['oldIndex'], + newIndex = data['newIndex'], + document = DocumentSnapshot._( + data['path'], + _asStringKeyedMap(data['document']), + SnapshotMetadata._(data["metadata"]["hasPendingWrites"], + data["metadata"]["isFromCache"]), + _firestore, + ), + type = DocumentChangeType.values.firstWhere((DocumentChangeType type) { + return type.toString() == data['type']; + }); + + final Firestore _firestore; + + /// The type of change that occurred (added, modified, or removed). + final DocumentChangeType type; + + /// The index of the changed document in the result set immediately prior to + /// this [DocumentChange] (i.e. supposing that all prior DocumentChange objects + /// have been applied). + /// + /// -1 for [DocumentChangeType.added] events. + final int oldIndex; + + /// The index of the changed document in the result set immediately after this + /// DocumentChange (i.e. supposing that all prior [DocumentChange] objects + /// and the current [DocumentChange] object have been applied). + /// + /// -1 for [DocumentChangeType.removed] events. + final int newIndex; + + /// The document affected by this change. + final DocumentSnapshot document; +} diff --git a/packages/cloud_firestore/lib/src/document_reference.dart b/packages/cloud_firestore/lib/src/document_reference.dart new file mode 100644 index 0000000..2edf8ac --- /dev/null +++ b/packages/cloud_firestore/lib/src/document_reference.dart @@ -0,0 +1,149 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// A [DocumentReference] refers to a document location in a Firestore database +/// and can be used to write, read, or listen to the location. +/// +/// The document at the referenced location may or may not exist. +/// A [DocumentReference] can also be used to create a [CollectionReference] +/// to a subcollection. +class DocumentReference { + DocumentReference._(this.firestore, List pathComponents) + : _pathComponents = pathComponents, + assert(firestore != null); + + /// The Firestore instance associated with this document reference + final Firestore firestore; + + final List _pathComponents; + + @override + bool operator ==(dynamic o) => + o is DocumentReference && o.firestore == firestore && o.path == path; + + @override + int get hashCode => hashList(_pathComponents); + + /// Parent returns the containing [CollectionReference]. + CollectionReference parent() { + return CollectionReference._( + firestore, + (List.from(_pathComponents)..removeLast()), + ); + } + + /// Slash-delimited path representing the database location of this query. + String get path => _pathComponents.join('/'); + + /// This document's given or generated ID in the collection. + String get documentID => _pathComponents.last; + + /// Writes to the document referred to by this [DocumentReference]. + /// + /// If the document does not yet exist, it will be created. + /// + /// If [merge] is true, the provided data will be merged into an + /// existing document instead of overwriting. + Future setData(Map data, {bool merge = false}) { + return Firestore.channel.invokeMethod( + 'DocumentReference#setData', + { + 'app': firestore.app.name, + 'path': path, + 'data': data, + 'options': {'merge': merge}, + }, + ); + } + + /// Updates fields in the document referred to by this [DocumentReference]. + /// + /// Values in [data] may be of any supported Firestore type as well as + /// special sentinel [FieldValue] type. + /// + /// If no document exists yet, the update will fail. + Future updateData(Map data) { + return Firestore.channel.invokeMethod( + 'DocumentReference#updateData', + { + 'app': firestore.app.name, + 'path': path, + 'data': data, + }, + ); + } + + /// Reads the document referenced by this [DocumentReference]. + /// + /// If no document exists, the read will return null. + Future get({Source source = Source.serverAndCache}) async { + final Map data = + await Firestore.channel.invokeMapMethod( + 'DocumentReference#get', + { + 'app': firestore.app.name, + 'path': path, + 'source': _getSourceString(source), + }, + ); + return DocumentSnapshot._( + data['path'], + _asStringKeyedMap(data['data']), + SnapshotMetadata._(data['metadata']['hasPendingWrites'], + data['metadata']['isFromCache']), + firestore, + ); + } + + /// Deletes the document referred to by this [DocumentReference]. + Future delete() { + return Firestore.channel.invokeMethod( + 'DocumentReference#delete', + {'app': firestore.app.name, 'path': path}, + ); + } + + /// Returns the reference of a collection contained inside of this + /// document. + CollectionReference collection(String collectionPath) { + return firestore.collection( + [path, collectionPath].join('/'), + ); + } + + /// Notifies of documents at this location + // TODO(jackson): Reduce code duplication with [Query] + Stream snapshots() { + Future _handle; + // 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 = StreamController.broadcast( + onListen: () { + _handle = Firestore.channel.invokeMethod( + 'Query#addDocumentListener', + { + 'app': firestore.app.name, + 'path': path, + }, + ).then((dynamic result) => result); + _handle.then((int handle) { + Firestore._documentObservers[handle] = controller; + }); + }, + onCancel: () { + _handle.then((int handle) async { + await Firestore.channel.invokeMethod( + 'Query#removeListener', + {'handle': handle}, + ); + Firestore._documentObservers.remove(handle); + }); + }, + ); + return controller.stream; + } +} diff --git a/packages/cloud_firestore/lib/src/document_snapshot.dart b/packages/cloud_firestore/lib/src/document_snapshot.dart new file mode 100644 index 0000000..4550856 --- /dev/null +++ b/packages/cloud_firestore/lib/src/document_snapshot.dart @@ -0,0 +1,45 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// A DocumentSnapshot contains data read from a document in your Firestore +/// database. +/// +/// The data can be extracted with the data property or by using subscript +/// syntax to access a specific field. +class DocumentSnapshot { + DocumentSnapshot._(this._path, this.data, this.metadata, this._firestore); + + final String _path; + final Firestore _firestore; + + /// The reference that produced this snapshot + DocumentReference get reference => _firestore.document(_path); + + /// Contains all the data of this snapshot + final Map data; + + /// Metadata about this snapshot concerning its source and if it has local + /// modifications. + final SnapshotMetadata metadata; + + /// Reads individual values from the snapshot + dynamic operator [](String key) => data[key]; + + /// Returns the ID of the snapshot's document + String get documentID => _path.split('/').last; + + /// Returns `true` if the document exists. + bool get exists => data != null; +} + +Map _asStringKeyedMap(Map map) { + if (map == null) return null; + if (map is Map) { + return map; + } else { + return Map.from(map); + } +} diff --git a/packages/cloud_firestore/lib/src/field_value.dart b/packages/cloud_firestore/lib/src/field_value.dart new file mode 100644 index 0000000..4ab12b0 --- /dev/null +++ b/packages/cloud_firestore/lib/src/field_value.dart @@ -0,0 +1,68 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +@visibleForTesting +enum FieldValueType { + arrayUnion, + arrayRemove, + delete, + serverTimestamp, + incrementDouble, + incrementInteger, +} + +/// Sentinel values that can be used when writing document fields with set() or +/// update(). +class FieldValue { + FieldValue._(this.type, this.value); + + @visibleForTesting + final FieldValueType type; + + @visibleForTesting + final dynamic value; + + /// Returns a special value that tells the server to union the given elements + /// with any array value that already exists on the server. + /// + /// Each specified element that doesn't already exist in the array will be + /// added to the end. If the field being modified is not already an array it + /// will be overwritten with an array containing exactly the specified + /// elements. + static FieldValue arrayUnion(List elements) => + FieldValue._(FieldValueType.arrayUnion, elements); + + /// Returns a special value that tells the server to remove the given + /// elements from any array value that already exists on the server. + /// + /// All instances of each element specified will be removed from the array. + /// If the field being modified is not already an array it will be overwritten + /// with an empty array. + static FieldValue arrayRemove(List elements) => + FieldValue._(FieldValueType.arrayRemove, elements); + + /// Returns a sentinel for use with update() to mark a field for deletion. + static FieldValue delete() => FieldValue._(FieldValueType.delete, null); + + /// Returns a sentinel for use with set() or update() to include a + /// server-generated timestamp in the written data. + static FieldValue serverTimestamp() => + FieldValue._(FieldValueType.serverTimestamp, null); + + /// Returns a special value for use with set() or update() that tells the + /// server to increment the field’s current value by the given value. + static FieldValue increment(num value) { + // It is a compile-time error for any type other than int or double to + // attempt to extend or implement num. + assert(value is int || value is double); + if (value is double) { + return FieldValue._(FieldValueType.incrementDouble, value); + } else if (value is int) { + return FieldValue._(FieldValueType.incrementInteger, value); + } + return null; + } +} diff --git a/packages/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/lib/src/firestore.dart new file mode 100644 index 0000000..e91bc47 --- /dev/null +++ b/packages/cloud_firestore/lib/src/firestore.dart @@ -0,0 +1,161 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// The entry point for accessing a Firestore. +/// +/// You can get an instance by calling [Firestore.instance]. +class Firestore { + Firestore({FirebaseApp app}) : app = app ?? FirebaseApp.instance { + if (_initialized) return; + channel.setMethodCallHandler((MethodCall call) async { + if (call.method == 'QuerySnapshot') { + final QuerySnapshot snapshot = QuerySnapshot._(call.arguments, this); + _queryObservers[call.arguments['handle']].add(snapshot); + } else if (call.method == 'DocumentSnapshot') { + final DocumentSnapshot snapshot = DocumentSnapshot._( + call.arguments['path'], + _asStringKeyedMap(call.arguments['data']), + SnapshotMetadata._(call.arguments['metadata']['hasPendingWrites'], + call.arguments['metadata']['isFromCache']), + this, + ); + _documentObservers[call.arguments['handle']].add(snapshot); + } else if (call.method == 'DoTransaction') { + final int transactionId = call.arguments['transactionId']; + return _transactionHandlers[transactionId]( + Transaction(transactionId, this), + ); + } + }); + _initialized = true; + } + + /// Gets the instance of Firestore for the default Firebase app. + static final Firestore instance = Firestore(); + + /// The [FirebaseApp] instance to which this [FirebaseDatabase] belongs. + /// + /// If null, the default [FirebaseApp] is used. + final FirebaseApp app; + + static bool _initialized = false; + + @visibleForTesting + static const MethodChannel channel = MethodChannel( + 'plugins.flutter.io/cloud_firestore', + StandardMethodCodec(FirestoreMessageCodec()), + ); + + static final Map> _queryObservers = + >{}; + + static final Map> _documentObservers = + >{}; + + static final Map _transactionHandlers = + {}; + static int _transactionHandlerId = 0; + + @override + bool operator ==(dynamic o) => o is Firestore && o.app == app; + + @override + int get hashCode => app.hashCode; + + /// Gets a [CollectionReference] for the specified Firestore path. + CollectionReference collection(String path) { + assert(path != null); + return CollectionReference._(this, path.split('/')); + } + + /// Gets a [Query] for the specified collection group. + Query collectionGroup(String path) { + assert(path != null); + assert(!path.contains("/"), "Collection IDs must not contain '/'."); + return Query._( + firestore: this, + isCollectionGroup: true, + pathComponents: path.split('/'), + ); + } + + /// Gets a [DocumentReference] for the specified Firestore path. + DocumentReference document(String path) { + assert(path != null); + return DocumentReference._(this, path.split('/')); + } + + /// Creates a write batch, used for performing multiple writes as a single + /// atomic operation. + /// + /// Unlike transactions, write batches are persisted offline and therefore are + /// preferable when you don’t need to condition your writes on read data. + WriteBatch batch() => WriteBatch._(this); + + /// Executes the given TransactionHandler and then attempts to commit the + /// changes applied within an atomic transaction. + /// + /// In the TransactionHandler, a set of reads and writes can be performed + /// atomically using the Transaction object passed to the TransactionHandler. + /// After the TransactionHandler is run, Firestore will attempt to apply the + /// changes to the server. If any of the data read has been modified outside + /// of this transaction since being read, then the transaction will be + /// retried by executing the updateBlock again. If the transaction still + /// fails after 5 retries, then the transaction will fail. + /// + /// The TransactionHandler may be executed multiple times, it should be able + /// to handle multiple executions. + /// + /// Data accessed with the transaction will not reflect local changes that + /// have not been committed. For this reason, it is required that all + /// reads are performed before any writes. Transactions must be performed + /// while online. Otherwise, reads will fail, and the final commit will fail. + /// + /// By default transactions are limited to 5 seconds of execution time. This + /// timeout can be adjusted by setting the timeout parameter. + Future> runTransaction( + TransactionHandler transactionHandler, + {Duration timeout = const Duration(seconds: 5)}) async { + assert(timeout.inMilliseconds > 0, + 'Transaction timeout must be more than 0 milliseconds'); + final int transactionId = _transactionHandlerId++; + _transactionHandlers[transactionId] = transactionHandler; + final Map result = await channel + .invokeMapMethod( + 'Firestore#runTransaction', { + 'app': app.name, + 'transactionId': transactionId, + 'transactionTimeout': timeout.inMilliseconds + }); + return result ?? {}; + } + + @deprecated + Future enablePersistence(bool enable) async { + assert(enable != null); + await channel + .invokeMethod('Firestore#enablePersistence', { + 'app': app.name, + 'enable': enable, + }); + } + + Future settings( + {bool persistenceEnabled, + String host, + bool sslEnabled, + bool timestampsInSnapshotsEnabled, + int cacheSizeBytes}) async { + await channel.invokeMethod('Firestore#settings', { + 'app': app.name, + 'persistenceEnabled': persistenceEnabled, + 'host': host, + 'sslEnabled': sslEnabled, + 'timestampsInSnapshotsEnabled': timestampsInSnapshotsEnabled, + 'cacheSizeBytes': cacheSizeBytes, + }); + } +} diff --git a/packages/cloud_firestore/lib/src/firestore_message_codec.dart b/packages/cloud_firestore/lib/src/firestore_message_codec.dart new file mode 100644 index 0000000..14f6e02 --- /dev/null +++ b/packages/cloud_firestore/lib/src/firestore_message_codec.dart @@ -0,0 +1,111 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +@visibleForTesting +class FirestoreMessageCodec extends StandardMessageCodec { + const FirestoreMessageCodec(); + + static const int _kDateTime = 128; + static const int _kGeoPoint = 129; + static const int _kDocumentReference = 130; + static const int _kBlob = 131; + static const int _kArrayUnion = 132; + static const int _kArrayRemove = 133; + static const int _kDelete = 134; + static const int _kServerTimestamp = 135; + static const int _kTimestamp = 136; + static const int _kIncrementDouble = 137; + static const int _kIncrementInteger = 138; + + static const Map _kFieldValueCodes = + { + FieldValueType.arrayUnion: _kArrayUnion, + FieldValueType.arrayRemove: _kArrayRemove, + FieldValueType.delete: _kDelete, + FieldValueType.serverTimestamp: _kServerTimestamp, + FieldValueType.incrementDouble: _kIncrementDouble, + FieldValueType.incrementInteger: _kIncrementInteger, + }; + + @override + void writeValue(WriteBuffer buffer, dynamic value) { + if (value is DateTime) { + buffer.putUint8(_kDateTime); + buffer.putInt64(value.millisecondsSinceEpoch); + } else if (value is Timestamp) { + buffer.putUint8(_kTimestamp); + buffer.putInt64(value.seconds); + buffer.putInt32(value.nanoseconds); + } else if (value is GeoPoint) { + buffer.putUint8(_kGeoPoint); + buffer.putFloat64(value.latitude); + buffer.putFloat64(value.longitude); + } else if (value is DocumentReference) { + buffer.putUint8(_kDocumentReference); + final List appName = utf8.encoder.convert(value.firestore.app.name); + writeSize(buffer, appName.length); + buffer.putUint8List(appName); + final List bytes = utf8.encoder.convert(value.path); + writeSize(buffer, bytes.length); + buffer.putUint8List(bytes); + } else if (value is Blob) { + buffer.putUint8(_kBlob); + writeSize(buffer, value.bytes.length); + buffer.putUint8List(value.bytes); + } else if (value is FieldValue) { + final int code = _kFieldValueCodes[value.type]; + assert(code != null); + buffer.putUint8(code); + if (value.value != null) writeValue(buffer, value.value); + } else { + super.writeValue(buffer, value); + } + } + + @override + dynamic readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case _kDateTime: + return DateTime.fromMillisecondsSinceEpoch(buffer.getInt64()); + case _kTimestamp: + return Timestamp(buffer.getInt64(), buffer.getInt32()); + case _kGeoPoint: + return GeoPoint(buffer.getFloat64(), buffer.getFloat64()); + case _kDocumentReference: + final int appNameLength = readSize(buffer); + final String appName = + utf8.decoder.convert(buffer.getUint8List(appNameLength)); + final FirebaseApp app = FirebaseApp(name: appName); + final Firestore firestore = Firestore(app: app); + final int pathLength = readSize(buffer); + final String path = + utf8.decoder.convert(buffer.getUint8List(pathLength)); + return firestore.document(path); + case _kBlob: + final int length = readSize(buffer); + final List bytes = buffer.getUint8List(length); + return Blob(bytes); + case _kArrayUnion: + final List value = readValue(buffer); + return FieldValue.arrayUnion(value); + case _kArrayRemove: + final List value = readValue(buffer); + return FieldValue.arrayRemove(value); + case _kDelete: + return FieldValue.delete(); + case _kServerTimestamp: + return FieldValue.serverTimestamp(); + case _kIncrementDouble: + final double value = readValue(buffer); + return FieldValue.increment(value); + case _kIncrementInteger: + final int value = readValue(buffer); + return FieldValue.increment(value); + default: + return super.readValueOfType(type, buffer); + } + } +} diff --git a/packages/cloud_firestore/lib/src/geo_point.dart b/packages/cloud_firestore/lib/src/geo_point.dart new file mode 100644 index 0000000..bc91d4d --- /dev/null +++ b/packages/cloud_firestore/lib/src/geo_point.dart @@ -0,0 +1,19 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +class GeoPoint { + const GeoPoint(this.latitude, this.longitude); + + final double latitude; + final double longitude; + + @override + bool operator ==(dynamic o) => + o is GeoPoint && o.latitude == latitude && o.longitude == longitude; + + @override + int get hashCode => hashValues(latitude, longitude); +} diff --git a/packages/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/lib/src/query.dart new file mode 100644 index 0000000..957d429 --- /dev/null +++ b/packages/cloud_firestore/lib/src/query.dart @@ -0,0 +1,352 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// Represents a query over the data at a particular location. +class Query { + Query._( + {@required this.firestore, + @required List pathComponents, + bool isCollectionGroup = false, + Map parameters}) + : _pathComponents = pathComponents, + _isCollectionGroup = isCollectionGroup, + _parameters = parameters ?? + Map.unmodifiable({ + 'where': List>.unmodifiable(>[]), + 'orderBy': List>.unmodifiable(>[]), + }), + assert(firestore != null), + assert(pathComponents != null); + + /// The Firestore instance associated with this query + final Firestore firestore; + + final List _pathComponents; + final Map _parameters; + final bool _isCollectionGroup; + + String get _path => _pathComponents.join('/'); + + Query _copyWithParameters(Map parameters) { + return Query._( + firestore: firestore, + isCollectionGroup: _isCollectionGroup, + pathComponents: _pathComponents, + parameters: Map.unmodifiable( + Map.from(_parameters)..addAll(parameters), + ), + ); + } + + Map buildArguments() { + return Map.from(_parameters) + ..addAll({ + 'path': _path, + }); + } + + /// Notifies of query results at this location + // TODO(jackson): Reduce code duplication with [DocumentReference] + Stream snapshots() { + Future _handle; + // 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 = StreamController.broadcast( + onListen: () { + _handle = Firestore.channel.invokeMethod( + 'Query#addSnapshotListener', + { + 'app': firestore.app.name, + 'path': _path, + 'isCollectionGroup': _isCollectionGroup, + 'parameters': _parameters, + }, + ).then((dynamic result) => result); + _handle.then((int handle) { + Firestore._queryObservers[handle] = controller; + }); + }, + onCancel: () { + _handle.then((int handle) async { + await Firestore.channel.invokeMethod( + 'Query#removeListener', + {'handle': handle}, + ); + Firestore._queryObservers.remove(handle); + }); + }, + ); + return controller.stream; + } + + /// Fetch the documents for this query + Future getDocuments( + {Source source = Source.serverAndCache}) async { + assert(source != null); + final Map data = + await Firestore.channel.invokeMapMethod( + 'Query#getDocuments', + { + 'app': firestore.app.name, + 'path': _path, + 'isCollectionGroup': _isCollectionGroup, + 'parameters': _parameters, + 'source': _getSourceString(source), + }, + ); + return QuerySnapshot._(data, firestore); + } + + /// Obtains a CollectionReference corresponding to this query's location. + CollectionReference reference() => + CollectionReference._(firestore, _pathComponents); + + /// Creates and returns a new [Query] with additional filter on specified + /// [field]. [field] refers to a field in a document. + /// + /// The [field] may consist of a single field name (referring to a top level + /// field in the document), or a series of field names seperated by dots '.' + /// (referring to a nested field in the document). + /// + /// Only documents satisfying provided condition are included in the result + /// set. + Query where( + String field, { + dynamic isEqualTo, + dynamic isLessThan, + dynamic isLessThanOrEqualTo, + dynamic isGreaterThan, + dynamic isGreaterThanOrEqualTo, + dynamic arrayContains, + bool isNull, + }) { + final ListEquality equality = const ListEquality(); + final List> conditions = + List>.from(_parameters['where']); + + void addCondition(String field, String operator, dynamic value) { + final List condition = [field, operator, value]; + assert( + conditions + .where((List item) => equality.equals(condition, item)) + .isEmpty, + 'Condition $condition already exists in this query.'); + conditions.add(condition); + } + + if (isEqualTo != null) addCondition(field, '==', isEqualTo); + if (isLessThan != null) addCondition(field, '<', isLessThan); + if (isLessThanOrEqualTo != null) + addCondition(field, '<=', isLessThanOrEqualTo); + if (isGreaterThan != null) addCondition(field, '>', isGreaterThan); + if (isGreaterThanOrEqualTo != null) + addCondition(field, '>=', isGreaterThanOrEqualTo); + if (arrayContains != null) + addCondition(field, 'array-contains', arrayContains); + if (isNull != null) { + assert( + isNull, + 'isNull can only be set to true. ' + 'Use isEqualTo to filter on non-null values.'); + addCondition(field, '==', null); + } + + return _copyWithParameters({'where': conditions}); + } + + /// Creates and returns a new [Query] that's additionally sorted by the specified + /// [field]. + Query orderBy(String field, {bool descending = false}) { + final List> orders = + List>.from(_parameters['orderBy']); + + final List order = [field, descending]; + assert(orders.where((List item) => field == item[0]).isEmpty, + 'OrderBy $field already exists in this query'); + orders.add(order); + return _copyWithParameters({'orderBy': orders}); + } + + /// Creates and returns a new [Query] that starts after the provided document + /// (exclusive). The starting position is relative to the order of the query. + /// The document must contain all of the fields provided in the orderBy of + /// this query. + /// + /// Cannot be used in combination with [startAtDocument], [startAt], or + /// [startAfter]. + /// + /// See also: + /// * [endAfterDocument] for a query that ends after a document. + /// * [startAtDocument] for a query that starts at a document. + /// * [endAtDocument] for a query that ends at a document. + Query startAfterDocument(DocumentSnapshot documentSnapshot) { + assert(documentSnapshot != null); + assert(!_parameters.containsKey('startAfter')); + assert(!_parameters.containsKey('startAt')); + assert(!_parameters.containsKey('startAfterDocument')); + assert(!_parameters.containsKey('startAtDocument')); + return _copyWithParameters({ + 'startAfterDocument': { + 'id': documentSnapshot.documentID, + 'path': documentSnapshot.reference.path, + 'data': documentSnapshot.data + } + }); + } + + /// Creates and returns a new [Query] that starts at the provided document + /// (inclusive). The starting position is relative to the order of the query. + /// The document must contain all of the fields provided in the orderBy of + /// this query. + /// + /// Cannot be used in combination with [startAfterDocument], [startAfter], or + /// [startAt]. + /// + /// See also: + /// * [startAfterDocument] for a query that starts after a document. + /// * [endAtDocument] for a query that ends at a document. + /// * [endBeforeDocument] for a query that ends before a document. + Query startAtDocument(DocumentSnapshot documentSnapshot) { + assert(documentSnapshot != null); + assert(!_parameters.containsKey('startAfter')); + assert(!_parameters.containsKey('startAt')); + assert(!_parameters.containsKey('startAfterDocument')); + assert(!_parameters.containsKey('startAtDocument')); + return _copyWithParameters({ + 'startAtDocument': { + 'id': documentSnapshot.documentID, + 'path': documentSnapshot.reference.path, + 'data': documentSnapshot.data + }, + }); + } + + /// Takes a list of [values], creates and returns a new [Query] that starts + /// after the provided fields relative to the order of the query. + /// + /// The [values] must be in order of [orderBy] filters. + /// + /// Cannot be used in combination with [startAt], [startAfterDocument], or + /// [startAtDocument]. + Query startAfter(List values) { + assert(values != null); + assert(!_parameters.containsKey('startAfter')); + assert(!_parameters.containsKey('startAt')); + assert(!_parameters.containsKey('startAfterDocument')); + assert(!_parameters.containsKey('startAtDocument')); + return _copyWithParameters({'startAfter': values}); + } + + /// Takes a list of [values], creates and returns a new [Query] that starts at + /// the provided fields relative to the order of the query. + /// + /// The [values] must be in order of [orderBy] filters. + /// + /// Cannot be used in combination with [startAfter], [startAfterDocument], + /// or [startAtDocument]. + Query startAt(List values) { + assert(values != null); + assert(!_parameters.containsKey('startAfter')); + assert(!_parameters.containsKey('startAt')); + assert(!_parameters.containsKey('startAfterDocument')); + assert(!_parameters.containsKey('startAtDocument')); + return _copyWithParameters({'startAt': values}); + } + + /// Creates and returns a new [Query] that ends at the provided document + /// (inclusive). The end position is relative to the order of the query. + /// The document must contain all of the fields provided in the orderBy of + /// this query. + /// + /// Cannot be used in combination with [endBefore], [endBeforeDocument], or + /// [endAt]. + /// + /// See also: + /// * [startAfterDocument] for a query that starts after a document. + /// * [startAtDocument] for a query that starts at a document. + /// * [endBeforeDocument] for a query that ends before a document. + Query endAtDocument(DocumentSnapshot documentSnapshot) { + assert(documentSnapshot != null); + assert(!_parameters.containsKey('endBefore')); + assert(!_parameters.containsKey('endAt')); + assert(!_parameters.containsKey('endBeforeDocument')); + assert(!_parameters.containsKey('endAtDocument')); + return _copyWithParameters({ + 'endAtDocument': { + 'id': documentSnapshot.documentID, + 'path': documentSnapshot.reference.path, + 'data': documentSnapshot.data + }, + }); + } + + /// Takes a list of [values], creates and returns a new [Query] that ends at the + /// provided fields relative to the order of the query. + /// + /// The [values] must be in order of [orderBy] filters. + /// + /// Cannot be used in combination with [endBefore], [endBeforeDocument], or + /// [endAtDocument]. + Query endAt(List values) { + assert(values != null); + assert(!_parameters.containsKey('endBefore')); + assert(!_parameters.containsKey('endAt')); + assert(!_parameters.containsKey('endBeforeDocument')); + assert(!_parameters.containsKey('endAtDocument')); + return _copyWithParameters({'endAt': values}); + } + + /// Creates and returns a new [Query] that ends before the provided document + /// (exclusive). The end position is relative to the order of the query. + /// The document must contain all of the fields provided in the orderBy of + /// this query. + /// + /// Cannot be used in combination with [endAt], [endBefore], or + /// [endAtDocument]. + /// + /// See also: + /// * [startAfterDocument] for a query that starts after document. + /// * [startAtDocument] for a query that starts at a document. + /// * [endAtDocument] for a query that ends at a document. + Query endBeforeDocument(DocumentSnapshot documentSnapshot) { + assert(documentSnapshot != null); + assert(!_parameters.containsKey('endBefore')); + assert(!_parameters.containsKey('endAt')); + assert(!_parameters.containsKey('endBeforeDocument')); + assert(!_parameters.containsKey('endAtDocument')); + return _copyWithParameters({ + 'endBeforeDocument': { + 'id': documentSnapshot.documentID, + 'path': documentSnapshot.reference.path, + 'data': documentSnapshot.data, + }, + }); + } + + /// Takes a list of [values], creates and returns a new [Query] that ends before + /// the provided fields relative to the order of the query. + /// + /// The [values] must be in order of [orderBy] filters. + /// + /// Cannot be used in combination with [endAt], [endBeforeDocument], or + /// [endBeforeDocument] + Query endBefore(List values) { + assert(values != null); + assert(!_parameters.containsKey('endBefore')); + assert(!_parameters.containsKey('endAt')); + assert(!_parameters.containsKey('endBeforeDocument')); + assert(!_parameters.containsKey('endAtDocument')); + return _copyWithParameters({'endBefore': values}); + } + + /// Creates and returns a new Query that's additionally limited to only return up + /// to the specified number of documents. + Query limit(int length) { + assert(!_parameters.containsKey('limit')); + return _copyWithParameters({'limit': length}); + } +} diff --git a/packages/cloud_firestore/lib/src/query_snapshot.dart b/packages/cloud_firestore/lib/src/query_snapshot.dart new file mode 100644 index 0000000..4cc8540 --- /dev/null +++ b/packages/cloud_firestore/lib/src/query_snapshot.dart @@ -0,0 +1,38 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// A QuerySnapshot contains zero or more DocumentSnapshot objects. +class QuerySnapshot { + QuerySnapshot._(Map data, this._firestore) + : documents = List.generate(data['documents'].length, + (int index) { + return DocumentSnapshot._( + data['paths'][index], + _asStringKeyedMap(data['documents'][index]), + SnapshotMetadata._( + data['metadatas'][index]['hasPendingWrites'], + data['metadatas'][index]['isFromCache'], + ), + _firestore, + ); + }), + documentChanges = List.generate( + data['documentChanges'].length, (int index) { + return DocumentChange._( + data['documentChanges'][index], + _firestore, + ); + }); + + /// Gets a list of all the documents included in this snapshot + final List documents; + + /// An array of the documents that changed since the last snapshot. If this + /// is the first snapshot, all documents will be in the list as Added changes. + final List documentChanges; + + final Firestore _firestore; +} diff --git a/packages/cloud_firestore/lib/src/snapshot_metadata.dart b/packages/cloud_firestore/lib/src/snapshot_metadata.dart new file mode 100644 index 0000000..39f51f5 --- /dev/null +++ b/packages/cloud_firestore/lib/src/snapshot_metadata.dart @@ -0,0 +1,28 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// Metadata about a snapshot, describing the state of the snapshot. +class SnapshotMetadata { + SnapshotMetadata._(this.hasPendingWrites, this.isFromCache); + + /// Whether the snapshot contains the result of local writes that have not yet + /// been committed to the backend. + /// + /// If your listener has opted into metadata updates (via + /// [DocumentListenOptions] or [QueryListenOptions]) you will receive another + /// snapshot with `hasPendingWrites` equal to `false` once the writes have been + /// committed to the backend. + final bool hasPendingWrites; + + /// Whether the snapshot was created from cached data rather than guaranteed + /// up-to-date server data. + /// + /// If your listener has opted into metadata updates (via + /// [DocumentListenOptions] or [QueryListenOptions]) you will receive another + /// snapshot with `isFomCache` equal to `false` once the client has received + /// up-to-date data from the backend. + final bool isFromCache; +} diff --git a/packages/cloud_firestore/lib/src/source.dart b/packages/cloud_firestore/lib/src/source.dart new file mode 100644 index 0000000..0a0a43e --- /dev/null +++ b/packages/cloud_firestore/lib/src/source.dart @@ -0,0 +1,37 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// An enumeration of firestore source types. +enum Source { + /// Causes Firestore to try to retrieve an up-to-date (server-retrieved) snapshot, but fall back to + /// returning cached data if the server can't be reached. + serverAndCache, + + /// Causes Firestore to avoid the cache, generating an error if the server cannot be reached. Note + /// that the cache will still be updated if the server request succeeds. Also note that + /// latency-compensation still takes effect, so any pending write operations will be visible in the + /// returned data (merged into the server-provided data). + server, + + /// Causes Firestore to immediately return a value from the cache, ignoring the server completely + /// (implying that the returned value may be stale with respect to the value on the server). If + /// there is no data in the cache to satisfy the [get()] or [getDocuments()] call, + /// [DocumentReference.get()] will return an error and [Query.getDocuments()] will return an empty + /// [QuerySnapshot] with no documents. + cache, +} + +/// Converts [Source] to [String] +String _getSourceString(Source source) { + assert(source != null); + if (source == Source.server) { + return 'server'; + } + if (source == Source.cache) { + return 'cache'; + } + return 'default'; +} diff --git a/packages/cloud_firestore/lib/src/timestamp.dart b/packages/cloud_firestore/lib/src/timestamp.dart new file mode 100644 index 0000000..027f5a5 --- /dev/null +++ b/packages/cloud_firestore/lib/src/timestamp.dart @@ -0,0 +1,98 @@ +// Copyright 2018, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +const int _kThousand = 1000; +const int _kMillion = 1000000; +const int _kBillion = 1000000000; + +void _check(bool expr, String name, int value) { + if (!expr) { + throw ArgumentError("Timestamp $name out of range: $value"); + } +} + +/// A Timestamp represents a point in time independent of any time zone or calendar, +/// represented as seconds and fractions of seconds at nanosecond resolution in UTC +/// Epoch time. It is encoded using the Proleptic Gregorian Calendar which extends +/// the Gregorian calendar backwards to year one. It is encoded assuming all minutes +/// are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table +/// is needed for interpretation. Range is from 0001-01-01T00:00:00Z to +/// 9999-12-31T23:59:59.999999999Z. By restricting to that range, we ensure that we +/// can convert to and from RFC 3339 date strings. +/// +/// For more information, see [the reference timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto) +class Timestamp implements Comparable { + Timestamp(this._seconds, this._nanoseconds) { + _validateRange(_seconds, _nanoseconds); + } + + factory Timestamp.fromMillisecondsSinceEpoch(int milliseconds) { + final int seconds = (milliseconds / _kThousand).floor(); + final int nanoseconds = (milliseconds - seconds * _kThousand) * _kMillion; + return Timestamp(seconds, nanoseconds); + } + + factory Timestamp.fromMicrosecondsSinceEpoch(int microseconds) { + final int seconds = (microseconds / _kMillion).floor(); + final int nanoseconds = (microseconds - seconds * _kMillion) * _kThousand; + return Timestamp(seconds, nanoseconds); + } + + factory Timestamp.fromDate(DateTime date) { + return Timestamp.fromMicrosecondsSinceEpoch(date.microsecondsSinceEpoch); + } + + factory Timestamp.now() { + return Timestamp.fromMicrosecondsSinceEpoch( + DateTime.now().microsecondsSinceEpoch); + } + + final int _seconds; + final int _nanoseconds; + + static const int _kStartOfTime = -62135596800; + static const int _kEndOfTime = 253402300800; + + int get seconds => _seconds; + + int get nanoseconds => _nanoseconds; + + int get millisecondsSinceEpoch => + (seconds * _kThousand + nanoseconds / _kMillion).floor(); + + int get microsecondsSinceEpoch => + (seconds * _kMillion + nanoseconds / _kThousand).floor(); + + DateTime toDate() { + return DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch); + } + + @override + int get hashCode => hashValues(seconds, nanoseconds); + @override + bool operator ==(dynamic o) => + o is Timestamp && o.seconds == seconds && o.nanoseconds == nanoseconds; + @override + int compareTo(Timestamp other) { + if (seconds == other.seconds) { + return nanoseconds.compareTo(other.nanoseconds); + } + + return seconds.compareTo(other.seconds); + } + + @override + String toString() { + return "Timestamp(seconds=$seconds, nanoseconds=$nanoseconds)"; + } + + static void _validateRange(int seconds, int nanoseconds) { + _check(nanoseconds >= 0, 'nanoseconds', nanoseconds); + _check(nanoseconds < _kBillion, 'nanoseconds', nanoseconds); + _check(seconds >= _kStartOfTime, 'seconds', seconds); + _check(seconds < _kEndOfTime, 'seconds', seconds); + } +} diff --git a/packages/cloud_firestore/lib/src/transaction.dart b/packages/cloud_firestore/lib/src/transaction.dart new file mode 100644 index 0000000..d42c89d --- /dev/null +++ b/packages/cloud_firestore/lib/src/transaction.dart @@ -0,0 +1,65 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +typedef Future TransactionHandler(Transaction transaction); + +class Transaction { + @visibleForTesting + Transaction(this._transactionId, this._firestore); + + int _transactionId; + Firestore _firestore; + + Future get(DocumentReference documentReference) async { + final Map result = await Firestore.channel + .invokeMapMethod('Transaction#get', { + 'app': _firestore.app.name, + 'transactionId': _transactionId, + 'path': documentReference.path, + }); + if (result != null) { + return DocumentSnapshot._( + documentReference.path, + result['data']?.cast(), + SnapshotMetadata._(result['metadata']['hasPendingWrites'], + result['metadata']['isFromCache']), + _firestore); + } else { + return null; + } + } + + Future delete(DocumentReference documentReference) async { + return Firestore.channel + .invokeMethod('Transaction#delete', { + 'app': _firestore.app.name, + 'transactionId': _transactionId, + 'path': documentReference.path, + }); + } + + Future update( + DocumentReference documentReference, Map data) async { + return Firestore.channel + .invokeMethod('Transaction#update', { + 'app': _firestore.app.name, + 'transactionId': _transactionId, + 'path': documentReference.path, + 'data': data, + }); + } + + Future set( + DocumentReference documentReference, Map data) async { + return Firestore.channel + .invokeMethod('Transaction#set', { + 'app': _firestore.app.name, + 'transactionId': _transactionId, + 'path': documentReference.path, + 'data': data, + }); + } +} diff --git a/packages/cloud_firestore/lib/src/utils/push_id_generator.dart b/packages/cloud_firestore/lib/src/utils/push_id_generator.dart new file mode 100644 index 0000000..f822f6c --- /dev/null +++ b/packages/cloud_firestore/lib/src/utils/push_id_generator.dart @@ -0,0 +1,63 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. 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:math'; + +/// Utility class for generating Firebase child node keys. +/// +/// Since the Flutter plugin API is asynchronous, there's no way for us +/// to use the native SDK to generate the node key synchronously and we +/// have to do it ourselves if we want to be able to reference the +/// newly-created node synchronously. +/// +/// This code is based on a Firebase blog post and ported to Dart. +/// https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html +class PushIdGenerator { + static const String PUSH_CHARS = + '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; + + static final Random _random = Random(); + + static int _lastPushTime; + + static final List _lastRandChars = List(12); + + static String generatePushChildName() { + int now = DateTime.now().millisecondsSinceEpoch; + final bool duplicateTime = (now == _lastPushTime); + _lastPushTime = now; + + final List timeStampChars = List(8); + for (int i = 7; i >= 0; i--) { + timeStampChars[i] = PUSH_CHARS[now % 64]; + now = (now / 64).floor(); + } + assert(now == 0); + + final StringBuffer result = StringBuffer(timeStampChars.join()); + + if (!duplicateTime) { + for (int i = 0; i < 12; i++) { + _lastRandChars[i] = _random.nextInt(64); + } + } else { + _incrementArray(); + } + for (int i = 0; i < 12; i++) { + result.write(PUSH_CHARS[_lastRandChars[i]]); + } + assert(result.length == 20); + return result.toString(); + } + + static void _incrementArray() { + for (int i = 11; i >= 0; i--) { + if (_lastRandChars[i] != 63) { + _lastRandChars[i] = _lastRandChars[i] + 1; + return; + } + _lastRandChars[i] = 0; + } + } +} diff --git a/packages/cloud_firestore/lib/src/write_batch.dart b/packages/cloud_firestore/lib/src/write_batch.dart new file mode 100644 index 0000000..6a41ee7 --- /dev/null +++ b/packages/cloud_firestore/lib/src/write_batch.dart @@ -0,0 +1,112 @@ +// Copyright 2018, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// A [WriteBatch] is a series of write operations to be performed as one unit. +/// +/// Operations done on a [WriteBatch] do not take effect until you [commit]. +/// +/// Once committed, no further operations can be performed on the [WriteBatch], +/// nor can it be committed again. +class WriteBatch { + WriteBatch._(this._firestore) + : _handle = Firestore.channel.invokeMethod( + 'WriteBatch#create', {'app': _firestore.app.name}); + + final Firestore _firestore; + Future _handle; + final List> _actions = >[]; + + /// Indicator to whether or not this [WriteBatch] has been committed. + bool _committed = false; + + /// Commits all of the writes in this write batch as a single atomic unit. + /// + /// Calling this method prevents any future operations from being added. + Future commit() async { + if (!_committed) { + _committed = true; + await Future.wait(_actions); + await Firestore.channel.invokeMethod( + 'WriteBatch#commit', {'handle': await _handle}); + } else { + throw StateError("This batch has already been committed."); + } + } + + /// Deletes the document referred to by [document]. + void delete(DocumentReference document) { + if (!_committed) { + _handle.then((dynamic handle) { + _actions.add( + Firestore.channel.invokeMethod( + 'WriteBatch#delete', + { + 'app': _firestore.app.name, + 'handle': handle, + 'path': document.path, + }, + ), + ); + }); + } else { + throw StateError( + "This batch has been committed and can no longer be changed."); + } + } + + /// Writes to the document referred to by [document]. + /// + /// If the document does not yet exist, it will be created. + /// + /// If [merge] is true, the provided data will be merged into an + /// existing document instead of overwriting. + void setData(DocumentReference document, Map data, + {bool merge = false}) { + if (!_committed) { + _handle.then((dynamic handle) { + _actions.add( + Firestore.channel.invokeMethod( + 'WriteBatch#setData', + { + 'app': _firestore.app.name, + 'handle': handle, + 'path': document.path, + 'data': data, + 'options': {'merge': merge}, + }, + ), + ); + }); + } else { + throw StateError( + "This batch has been committed and can no longer be changed."); + } + } + + /// Updates fields in the document referred to by [document]. + /// + /// If the document does not exist, the operation will fail. + void updateData(DocumentReference document, Map data) { + if (!_committed) { + _handle.then((dynamic handle) { + _actions.add( + Firestore.channel.invokeMethod( + 'WriteBatch#updateData', + { + 'app': _firestore.app.name, + 'handle': handle, + 'path': document.path, + 'data': data, + }, + ), + ); + }); + } else { + throw StateError( + "This batch has been committed and can no longer be changed."); + } + } +} diff --git a/packages/cloud_firestore/pubspec.yaml b/packages/cloud_firestore/pubspec.yaml new file mode 100755 index 0000000..fc10833 --- /dev/null +++ b/packages/cloud_firestore/pubspec.yaml @@ -0,0 +1,30 @@ +name: cloud_firestore +description: Flutter plugin for Cloud Firestore, a cloud-hosted, noSQL database with + live synchronization and offline support on Android and iOS. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/cloud_firestore +version: 0.12.5 + +flutter: + plugin: + androidPackage: io.flutter.plugins.firebase.cloudfirestore + iosPrefix: FLT + pluginClass: CloudFirestorePlugin + +dependencies: + flutter: + sdk: flutter + meta: "^1.0.5" + collection: "^1.14.3" + firebase_core: "^0.4.0" + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + test: any + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/packages/cloud_firestore/test/cloud_firestore_test.dart b/packages/cloud_firestore/test/cloud_firestore_test.dart new file mode 100755 index 0000000..8f10b56 --- /dev/null +++ b/packages/cloud_firestore/test/cloud_firestore_test.dart @@ -0,0 +1,1194 @@ +// 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 'dart:typed_data'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('$Firestore', () { + int mockHandleId = 0; + FirebaseApp app; + Firestore firestore; + final List log = []; + CollectionReference collectionReference; + Query collectionGroupQuery; + Transaction transaction; + const Map kMockDocumentSnapshotData = { + '1': 2 + }; + const Map kMockSnapshotMetadata = { + "hasPendingWrites": false, + "isFromCache": false, + }; + setUp(() async { + mockHandleId = 0; + // Required for FirebaseApp.configure + FirebaseApp.channel.setMockMethodCallHandler( + (MethodCall methodCall) async {}, + ); + app = await FirebaseApp.configure( + name: 'testApp', + options: const FirebaseOptions( + googleAppID: '1:1234567890:ios:42424242424242', + gcmSenderID: '1234567890', + ), + ); + firestore = Firestore(app: app); + collectionReference = firestore.collection('foo'); + collectionGroupQuery = firestore.collectionGroup('bar'); + transaction = Transaction(0, firestore); + Firestore.channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'Query#addSnapshotListener': + final int handle = mockHandleId++; + // Wait before sending a message back. + // Otherwise the first request didn't have the time to finish. + Future.delayed(Duration.zero).then((_) { + // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. + // https://github.com/flutter/flutter/issues/33446 + // ignore: deprecated_member_use + BinaryMessages.handlePlatformMessage( + Firestore.channel.name, + Firestore.channel.codec.encodeMethodCall( + MethodCall('QuerySnapshot', { + 'app': app.name, + 'handle': handle, + 'paths': ["${methodCall.arguments['path']}/0"], + 'documents': [kMockDocumentSnapshotData], + 'metadatas': >[kMockSnapshotMetadata], + 'documentChanges': [ + { + 'oldIndex': -1, + 'newIndex': 0, + 'type': 'DocumentChangeType.added', + 'document': kMockDocumentSnapshotData, + 'metadata': kMockSnapshotMetadata, + }, + ], + }), + ), + (_) {}, + ); + }); + return handle; + case 'Query#addDocumentListener': + final int handle = mockHandleId++; + // Wait before sending a message back. + // Otherwise the first request didn't have the time to finish. + Future.delayed(Duration.zero).then((_) { + // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. + // https://github.com/flutter/flutter/issues/33446 + // ignore: deprecated_member_use + BinaryMessages.handlePlatformMessage( + Firestore.channel.name, + Firestore.channel.codec.encodeMethodCall( + MethodCall('DocumentSnapshot', { + 'handle': handle, + 'path': methodCall.arguments['path'], + 'data': kMockDocumentSnapshotData, + 'metadata': kMockSnapshotMetadata, + }), + ), + (_) {}, + ); + }); + return handle; + case 'Query#getDocuments': + return { + 'paths': ["${methodCall.arguments['path']}/0"], + 'documents': [kMockDocumentSnapshotData], + 'metadatas': >[kMockSnapshotMetadata], + 'documentChanges': [ + { + 'oldIndex': -1, + 'newIndex': 0, + 'type': 'DocumentChangeType.added', + 'document': kMockDocumentSnapshotData, + 'metadata': kMockSnapshotMetadata, + }, + ], + }; + case 'DocumentReference#setData': + return true; + case 'DocumentReference#get': + if (methodCall.arguments['path'] == 'foo/bar') { + return { + 'path': 'foo/bar', + 'data': {'key1': 'val1'}, + 'metadata': kMockSnapshotMetadata, + }; + } else if (methodCall.arguments['path'] == 'foo/notExists') { + return { + 'path': 'foo/notExists', + 'data': null, + 'metadata': kMockSnapshotMetadata, + }; + } + throw PlatformException(code: 'UNKNOWN_PATH'); + case 'Firestore#runTransaction': + return {'1': 3}; + case 'Transaction#get': + if (methodCall.arguments['path'] == 'foo/bar') { + return { + 'path': 'foo/bar', + 'data': {'key1': 'val1'}, + 'metadata': kMockSnapshotMetadata, + }; + } else if (methodCall.arguments['path'] == 'foo/notExists') { + return { + 'path': 'foo/notExists', + 'data': null, + 'metadata': kMockSnapshotMetadata, + }; + } + throw PlatformException(code: 'UNKNOWN_PATH'); + case 'Transaction#set': + return null; + case 'Transaction#update': + return null; + case 'Transaction#delete': + return null; + case 'WriteBatch#create': + return 1; + default: + return null; + } + }); + log.clear(); + }); + + test('multiple apps', () async { + expect(Firestore.instance, equals(Firestore())); + final FirebaseApp app = FirebaseApp(name: firestore.app.name); + expect(firestore, equals(Firestore(app: app))); + }); + + test('settings', () async { + final FirebaseApp app = const FirebaseApp(name: "testApp2"); + final Firestore firestoreWithSettings = Firestore(app: app); + await firestoreWithSettings.settings( + persistenceEnabled: true, + host: null, + sslEnabled: true, + timestampsInSnapshotsEnabled: true, + cacheSizeBytes: 500000, + ); + expect(log, [ + isMethodCall('Firestore#settings', arguments: { + 'app': firestoreWithSettings.app.name, + 'persistenceEnabled': true, + 'host': null, + 'sslEnabled': true, + 'timestampsInSnapshotsEnabled': true, + 'cacheSizeBytes': 500000, + }), + ]); + }); + + group('Transaction', () { + test('runTransaction', () async { + final Map result = await firestore.runTransaction( + (Transaction tx) async {}, + timeout: const Duration(seconds: 3)); + + expect(log, [ + isMethodCall('Firestore#runTransaction', arguments: { + 'app': app.name, + 'transactionId': 0, + 'transactionTimeout': 3000 + }), + ]); + expect(result, equals({'1': 3})); + }); + + test('get', () async { + final DocumentReference documentReference = + firestore.document('foo/bar'); + final DocumentSnapshot snapshot = + await transaction.get(documentReference); + expect(snapshot.reference.firestore, firestore); + expect(log, [ + isMethodCall('Transaction#get', arguments: { + 'app': app.name, + 'transactionId': 0, + 'path': documentReference.path + }) + ]); + }); + + test('get notExists', () async { + final DocumentReference documentReference = + firestore.document('foo/notExists'); + await transaction.get(documentReference); + expect(log, [ + isMethodCall('Transaction#get', arguments: { + 'app': app.name, + 'transactionId': 0, + 'path': documentReference.path + }) + ]); + }); + + test('delete', () async { + final DocumentReference documentReference = + firestore.document('foo/bar'); + await transaction.delete(documentReference); + expect(log, [ + isMethodCall('Transaction#delete', arguments: { + 'app': app.name, + 'transactionId': 0, + 'path': documentReference.path + }) + ]); + }); + + test('update', () async { + final DocumentReference documentReference = + firestore.document('foo/bar'); + final DocumentSnapshot documentSnapshot = await documentReference.get(); + final Map data = documentSnapshot.data; + data['key2'] = 'val2'; + await transaction.set(documentReference, data); + expect(log, [ + isMethodCall('DocumentReference#get', arguments: { + 'app': app.name, + 'path': 'foo/bar', + 'source': 'default', + }), + isMethodCall('Transaction#set', arguments: { + 'app': app.name, + 'transactionId': 0, + 'path': documentReference.path, + 'data': {'key1': 'val1', 'key2': 'val2'} + }) + ]); + }); + + test('set', () async { + final DocumentReference documentReference = + firestore.document('foo/bar'); + final DocumentSnapshot documentSnapshot = await documentReference.get(); + final Map data = documentSnapshot.data; + data['key2'] = 'val2'; + await transaction.set(documentReference, data); + expect(log, [ + isMethodCall('DocumentReference#get', arguments: { + 'app': app.name, + 'path': 'foo/bar', + 'source': 'default', + }), + isMethodCall('Transaction#set', arguments: { + 'app': app.name, + 'transactionId': 0, + 'path': documentReference.path, + 'data': {'key1': 'val1', 'key2': 'val2'} + }) + ]); + }); + }); + + group('Blob', () { + test('hashCode equality', () async { + final Uint8List bytesA = Uint8List(8); + bytesA.setAll(0, [0, 2, 4, 6, 8, 10, 12, 14]); + final Blob a = Blob(bytesA); + final Uint8List bytesB = Uint8List(8); + bytesB.setAll(0, [0, 2, 4, 6, 8, 10, 12, 14]); + final Blob b = Blob(bytesB); + expect(a.hashCode == b.hashCode, isTrue); + }); + test('hashCode not equal', () async { + final Uint8List bytesA = Uint8List(8); + bytesA.setAll(0, [0, 2, 4, 6, 8, 10, 12, 14]); + final Blob a = Blob(bytesA); + final Uint8List bytesB = Uint8List(8); + bytesB.setAll(0, [1, 2, 4, 6, 8, 10, 12, 14]); + final Blob b = Blob(bytesB); + expect(a.hashCode == b.hashCode, isFalse); + }); + }); + + group('CollectionsReference', () { + test('id', () async { + expect(collectionReference.id, equals('foo')); + }); + test('parent', () async { + final DocumentReference docRef = collectionReference.document('bar'); + expect(docRef.parent().id, equals('foo')); + expect(collectionReference.parent(), isNull); + }); + test('path', () async { + expect(collectionReference.path, equals('foo')); + }); + test('listen', () async { + final QuerySnapshot snapshot = + await collectionReference.snapshots().first; + final DocumentSnapshot document = snapshot.documents[0]; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('foo/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + // Flush the async removeListener call + await Future.delayed(Duration.zero); + expect(log, [ + isMethodCall( + 'Query#addSnapshotListener', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'parameters': { + 'where': >[], + 'orderBy': >[], + } + }, + ), + isMethodCall( + 'Query#removeListener', + arguments: {'handle': 0}, + ), + ]); + }); + test('where', () async { + final StreamSubscription subscription = + collectionReference + .where('createdAt', isLessThan: 100) + .snapshots() + .listen((QuerySnapshot querySnapshot) {}); + subscription.cancel(); + await Future.delayed(Duration.zero); + expect( + log, + equals([ + isMethodCall( + 'Query#addSnapshotListener', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'parameters': { + 'where': >[ + ['createdAt', '<', 100], + ], + 'orderBy': >[], + } + }, + ), + isMethodCall( + 'Query#removeListener', + arguments: {'handle': 0}, + ), + ]), + ); + }); + test('where field isNull', () async { + final StreamSubscription subscription = + collectionReference + .where('profile', isNull: true) + .snapshots() + .listen((QuerySnapshot querySnapshot) {}); + subscription.cancel(); + await Future.delayed(Duration.zero); + expect( + log, + equals([ + isMethodCall( + 'Query#addSnapshotListener', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'parameters': { + 'where': >[ + ['profile', '==', null], + ], + 'orderBy': >[], + } + }, + ), + isMethodCall( + 'Query#removeListener', + arguments: {'handle': 0}, + ), + ]), + ); + }); + test('orderBy', () async { + final StreamSubscription subscription = + collectionReference + .orderBy('createdAt') + .snapshots() + .listen((QuerySnapshot querySnapshot) {}); + subscription.cancel(); + await Future.delayed(Duration.zero); + expect( + log, + equals([ + isMethodCall( + 'Query#addSnapshotListener', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'parameters': { + 'where': >[], + 'orderBy': >[ + ['createdAt', false] + ], + } + }, + ), + isMethodCall( + 'Query#removeListener', + arguments: {'handle': 0}, + ), + ]), + ); + }); + }); + + group('DocumentReference', () { + test('listen', () async { + final DocumentSnapshot snapshot = + await firestore.document('path/to/foo').snapshots().first; + expect(snapshot.documentID, equals('foo')); + expect(snapshot.reference.path, equals('path/to/foo')); + expect(snapshot.data, equals(kMockDocumentSnapshotData)); + // Flush the async removeListener call + await Future.delayed(Duration.zero); + expect( + log, + [ + isMethodCall( + 'Query#addDocumentListener', + arguments: { + 'app': app.name, + 'path': 'path/to/foo', + }, + ), + isMethodCall( + 'Query#removeListener', + arguments: {'handle': 0}, + ), + ], + ); + }); + test('set', () async { + await collectionReference + .document('bar') + .setData({'bazKey': 'quxValue'}); + expect( + log, + [ + isMethodCall( + 'DocumentReference#setData', + arguments: { + 'app': app.name, + 'path': 'foo/bar', + 'data': {'bazKey': 'quxValue'}, + 'options': {'merge': false}, + }, + ), + ], + ); + }); + test('merge set', () async { + await collectionReference + .document('bar') + .setData({'bazKey': 'quxValue'}, merge: true); + expect( + log, + [ + isMethodCall( + 'DocumentReference#setData', + arguments: { + 'app': app.name, + 'path': 'foo/bar', + 'data': {'bazKey': 'quxValue'}, + 'options': {'merge': true}, + }, + ), + ], + ); + }); + test('update', () async { + await collectionReference + .document('bar') + .updateData({'bazKey': 'quxValue'}); + expect( + log, + [ + isMethodCall( + 'DocumentReference#updateData', + arguments: { + 'app': app.name, + 'path': 'foo/bar', + 'data': {'bazKey': 'quxValue'}, + }, + ), + ], + ); + }); + test('delete', () async { + await collectionReference.document('bar').delete(); + expect( + log, + equals([ + isMethodCall( + 'DocumentReference#delete', + arguments: { + 'app': app.name, + 'path': 'foo/bar', + }, + ), + ]), + ); + }); + test('get', () async { + final DocumentSnapshot snapshot = + await collectionReference.document('bar').get(source: Source.cache); + expect(snapshot.reference.firestore, firestore); + expect( + log, + equals([ + isMethodCall( + 'DocumentReference#get', + arguments: { + 'app': app.name, + 'path': 'foo/bar', + 'source': 'cache', + }, + ), + ]), + ); + log.clear(); + expect(snapshot.reference.path, equals('foo/bar')); + expect(snapshot.data.containsKey('key1'), equals(true)); + expect(snapshot.data['key1'], equals('val1')); + expect(snapshot.exists, isTrue); + + final DocumentSnapshot snapshot2 = await collectionReference + .document('notExists') + .get(source: Source.serverAndCache); + expect(snapshot2.data, isNull); + expect(snapshot2.exists, isFalse); + expect( + log, + equals([ + isMethodCall( + 'DocumentReference#get', + arguments: { + 'app': app.name, + 'path': 'foo/notExists', + 'source': 'default', + }, + ), + ]), + ); + + try { + await collectionReference.document('baz').get(); + } on PlatformException catch (e) { + expect(e.code, equals('UNKNOWN_PATH')); + } + }); + test('collection', () async { + final CollectionReference colRef = + collectionReference.document('bar').collection('baz'); + expect(colRef.path, equals('foo/bar/baz')); + }); + test('parent', () async { + final CollectionReference colRef = + collectionReference.document('bar').collection('baz'); + expect(colRef.parent().documentID, equals('bar')); + }); + }); + + group('Query', () { + test('getDocumentsFromCollection', () async { + QuerySnapshot snapshot = + await collectionReference.getDocuments(source: Source.server); + DocumentSnapshot document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('foo/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // startAtDocument + snapshot = + await collectionReference.startAtDocument(document).getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('foo/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // startAfterDocument + snapshot = await collectionReference + .startAfterDocument(document) + .getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('foo/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // endAtDocument + snapshot = + await collectionReference.endAtDocument(document).getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('foo/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // endBeforeDocument + snapshot = await collectionReference + .endBeforeDocument(document) + .getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('foo/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + expect( + log, + equals( + [ + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'source': 'server', + 'parameters': { + 'where': >[], + 'orderBy': >[], + }, + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'source': 'default', + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'startAtDocument': { + 'id': '0', + 'path': 'foo/0', + 'data': kMockDocumentSnapshotData, + }, + }, + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'source': 'default', + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'startAfterDocument': { + 'id': '0', + 'path': 'foo/0', + 'data': kMockDocumentSnapshotData, + }, + }, + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'source': 'default', + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'endAtDocument': { + 'id': '0', + 'path': 'foo/0', + 'data': kMockDocumentSnapshotData, + }, + }, + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'foo', + 'isCollectionGroup': false, + 'source': 'default', + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'endBeforeDocument': { + 'id': '0', + 'path': 'foo/0', + 'data': kMockDocumentSnapshotData, + }, + }, + }, + ), + ], + ), + ); + }); + test('getDocumentsFromCollectionGroup', () async { + QuerySnapshot snapshot = await collectionGroupQuery.getDocuments(); + DocumentSnapshot document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // startAtDocument + snapshot = + await collectionGroupQuery.startAtDocument(document).getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // startAfterDocument + snapshot = await collectionGroupQuery + .startAfterDocument(document) + .getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // endAtDocument + snapshot = + await collectionGroupQuery.endAtDocument(document).getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // endBeforeDocument + snapshot = await collectionGroupQuery + .endBeforeDocument(document) + .getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + expect( + log, + equals( + [ + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'startAtDocument': { + 'id': '0', + 'path': 'bar/0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'startAfterDocument': { + 'id': '0', + 'path': 'bar/0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'endAtDocument': { + 'id': '0', + 'path': 'bar/0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'source': 'default', + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'endBeforeDocument': { + 'id': '0', + 'path': 'bar/0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + ], + ), + ); + }); + }); + + group('FirestoreMessageCodec', () { + const MessageCodec codec = FirestoreMessageCodec(); + final DateTime testTime = DateTime(2015, 10, 30, 11, 16); + final Timestamp timestamp = Timestamp.fromDate(testTime); + test('should encode and decode simple messages', () { + _checkEncodeDecode(codec, testTime); + _checkEncodeDecode(codec, timestamp); + _checkEncodeDecode( + codec, const GeoPoint(37.421939, -122.083509)); + _checkEncodeDecode(codec, firestore.document('foo/bar')); + }); + test('should encode and decode composite message', () { + final List message = [ + testTime, + const GeoPoint(37.421939, -122.083509), + firestore.document('foo/bar'), + ]; + _checkEncodeDecode(codec, message); + }); + test('encode and decode blob', () { + final Uint8List bytes = Uint8List(4); + bytes[0] = 128; + final Blob message = Blob(bytes); + _checkEncodeDecode(codec, message); + }); + + test('encode and decode FieldValue', () { + _checkEncodeDecode(codec, FieldValue.arrayUnion([123])); + _checkEncodeDecode(codec, FieldValue.arrayRemove([123])); + _checkEncodeDecode(codec, FieldValue.delete()); + _checkEncodeDecode(codec, FieldValue.serverTimestamp()); + _checkEncodeDecode(codec, FieldValue.increment(1.0)); + _checkEncodeDecode(codec, FieldValue.increment(1)); + }); + }); + + group('Timestamp', () { + test('is accurate for dates after epoch', () { + final DateTime date = DateTime.fromMillisecondsSinceEpoch(22501); + final Timestamp timestamp = Timestamp.fromDate(date); + + expect(timestamp.seconds, equals(22)); + expect(timestamp.nanoseconds, equals(501000000)); + }); + + test('is accurate for dates before epoch', () { + final DateTime date = DateTime.fromMillisecondsSinceEpoch(-1250); + final Timestamp timestamp = Timestamp.fromDate(date); + + expect(timestamp.seconds, equals(-2)); + expect(timestamp.nanoseconds, equals(750000000)); + }); + + test('creates equivalent timestamps regardless of factory', () { + const int kMilliseconds = 22501; + const int kMicroseconds = 22501000; + final DateTime date = + DateTime.fromMicrosecondsSinceEpoch(kMicroseconds); + + final Timestamp timestamp = Timestamp(22, 501000000); + final Timestamp milliTimestamp = + Timestamp.fromMillisecondsSinceEpoch(kMilliseconds); + final Timestamp microTimestamp = + Timestamp.fromMicrosecondsSinceEpoch(kMicroseconds); + final Timestamp dateTimestamp = Timestamp.fromDate(date); + + expect(timestamp, equals(milliTimestamp)); + expect(milliTimestamp, equals(microTimestamp)); + expect(microTimestamp, equals(dateTimestamp)); + }); + + test('correctly compares timestamps', () { + final Timestamp alpha = Timestamp.fromDate(DateTime(2017, 5, 11)); + final Timestamp beta1 = Timestamp.fromDate(DateTime(2018, 2, 19)); + final Timestamp beta2 = Timestamp.fromDate(DateTime(2018, 4, 2)); + final Timestamp beta3 = Timestamp.fromDate(DateTime(2018, 4, 20)); + final Timestamp preview = Timestamp.fromDate(DateTime(2018, 6, 20)); + final List inOrder = [ + alpha, + beta1, + beta2, + beta3, + preview + ]; + + final List timestamps = [ + beta2, + beta3, + alpha, + preview, + beta1 + ]; + timestamps.sort(); + expect(_deepEqualsList(timestamps, inOrder), isTrue); + }); + + test('rejects dates outside RFC 3339 range', () { + final List invalidDates = [ + DateTime.fromMillisecondsSinceEpoch(-70000000000000), + DateTime.fromMillisecondsSinceEpoch(300000000000000), + ]; + + invalidDates.forEach((DateTime date) { + expect(() => Timestamp.fromDate(date), throwsArgumentError); + }); + }); + }); + + group('WriteBatch', () { + test('set', () async { + final WriteBatch batch = firestore.batch(); + batch.setData( + collectionReference.document('bar'), + {'bazKey': 'quxValue'}, + ); + await batch.commit(); + expect( + log, + [ + isMethodCall('WriteBatch#create', arguments: { + 'app': app.name, + }), + isMethodCall( + 'WriteBatch#setData', + arguments: { + 'app': app.name, + 'handle': 1, + 'path': 'foo/bar', + 'data': {'bazKey': 'quxValue'}, + 'options': {'merge': false}, + }, + ), + isMethodCall( + 'WriteBatch#commit', + arguments: { + 'handle': 1, + }, + ), + ], + ); + }); + test('merge set', () async { + final WriteBatch batch = firestore.batch(); + batch.setData( + collectionReference.document('bar'), + {'bazKey': 'quxValue'}, + merge: true, + ); + await batch.commit(); + expect( + log, + [ + isMethodCall('WriteBatch#create', arguments: { + 'app': app.name, + }), + isMethodCall('WriteBatch#setData', arguments: { + 'app': app.name, + 'handle': 1, + 'path': 'foo/bar', + 'data': {'bazKey': 'quxValue'}, + 'options': {'merge': true}, + }), + isMethodCall( + 'WriteBatch#commit', + arguments: { + 'handle': 1, + }, + ), + ], + ); + }); + test('update', () async { + final WriteBatch batch = firestore.batch(); + batch.updateData( + collectionReference.document('bar'), + {'bazKey': 'quxValue'}, + ); + await batch.commit(); + expect( + log, + [ + isMethodCall( + 'WriteBatch#create', + arguments: { + 'app': app.name, + }, + ), + isMethodCall( + 'WriteBatch#updateData', + arguments: { + 'app': app.name, + 'handle': 1, + 'path': 'foo/bar', + 'data': {'bazKey': 'quxValue'}, + }, + ), + isMethodCall( + 'WriteBatch#commit', + arguments: { + 'handle': 1, + }, + ), + ], + ); + }); + test('delete', () async { + final WriteBatch batch = firestore.batch(); + batch.delete(collectionReference.document('bar')); + await batch.commit(); + expect( + log, + [ + isMethodCall( + 'WriteBatch#create', + arguments: { + 'app': app.name, + }, + ), + isMethodCall( + 'WriteBatch#delete', + arguments: { + 'app': app.name, + 'handle': 1, + 'path': 'foo/bar', + }, + ), + isMethodCall( + 'WriteBatch#commit', + arguments: { + 'handle': 1, + }, + ), + ], + ); + }); + }); + }); +} + +void _checkEncodeDecode(MessageCodec codec, T message) { + final ByteData encoded = codec.encodeMessage(message); + final T decoded = codec.decodeMessage(encoded); + if (message == null) { + expect(encoded, isNull); + expect(decoded, isNull); + } else { + expect(_deepEquals(message, decoded), isTrue); + final ByteData encodedAgain = codec.encodeMessage(decoded); + expect( + encodedAgain.buffer.asUint8List(), + orderedEquals(encoded.buffer.asUint8List()), + ); + } +} + +bool _deepEquals(dynamic valueA, dynamic valueB) { + if (valueA is TypedData) + return valueB is TypedData && _deepEqualsTypedData(valueA, valueB); + if (valueA is List) return valueB is List && _deepEqualsList(valueA, valueB); + if (valueA is Map) return valueB is Map && _deepEqualsMap(valueA, valueB); + if (valueA is double && valueA.isNaN) return valueB is double && valueB.isNaN; + if (valueA is FieldValue) { + return valueB is FieldValue && _deepEqualsFieldValue(valueA, valueB); + } + return valueA == valueB; +} + +bool _deepEqualsTypedData(TypedData valueA, TypedData valueB) { + if (valueA is ByteData) { + return valueB is ByteData && + _deepEqualsList( + valueA.buffer.asUint8List(), valueB.buffer.asUint8List()); + } + if (valueA is Uint8List) + return valueB is Uint8List && _deepEqualsList(valueA, valueB); + if (valueA is Int32List) + return valueB is Int32List && _deepEqualsList(valueA, valueB); + if (valueA is Int64List) + return valueB is Int64List && _deepEqualsList(valueA, valueB); + if (valueA is Float64List) + return valueB is Float64List && _deepEqualsList(valueA, valueB); + throw 'Unexpected typed data: $valueA'; +} + +bool _deepEqualsList(List valueA, List valueB) { + if (valueA.length != valueB.length) return false; + for (int i = 0; i < valueA.length; i++) { + if (!_deepEquals(valueA[i], valueB[i])) return false; + } + return true; +} + +bool _deepEqualsMap( + Map valueA, Map valueB) { + if (valueA.length != valueB.length) return false; + for (final dynamic key in valueA.keys) { + if (!valueB.containsKey(key) || !_deepEquals(valueA[key], valueB[key])) + return false; + } + return true; +} + +bool _deepEqualsFieldValue(FieldValue valueA, FieldValue valueB) { + if (valueA.type != valueB.type) return false; + if (valueA.value == null) return valueB.value == null; + if (valueA.value is List) return _deepEqualsList(valueA.value, valueB.value); + if (valueA.value is Map) return _deepEqualsMap(valueA.value, valueB.value); + return valueA.value == valueB.value; +} diff --git a/packages/firebase_core/CHANGELOG.md b/packages/firebase_core/CHANGELOG.md new file mode 100644 index 0000000..1931bd5 --- /dev/null +++ b/packages/firebase_core/CHANGELOG.md @@ -0,0 +1,133 @@ +## 0.4.0+3 + +* Add missing template type parameter to `invokeMethod` calls. +* Bump minimum Flutter version to 1.5.0. +* Replace invokeMethod with invokeMapMethod wherever necessary. + +## 0.4.0+2 + +* Update user agent name. Set to `flutter-fire-core` for consistency with other + libraries. + +## 0.4.0+1 + +* Send user agent to Firebase. + +## 0.4.0 + +* Update Android dependencies to latest. + +## 0.3.4 + +* Updates Android firebase-core dependency to a version that is compatible with other Flutterfire plugins. + +## 0.3.3 + +* Remove Gradle BoM to avoid Gradle version issues. + +## 0.3.2 + +* Move Android dependency to Gradle BoM to help maintain compatability + with other FlutterFire plugins. + +## 0.3.1+1 + +* Add nil check on static functions to prevent crashes or unwanted behaviors. + +## 0.3.1 + +* Remove an assertion that can interfere with hot-restart. + +## 0.3.0+2 + +* Remove categories. + +## 0.3.0+1 + +* Log a more detailed warning at build time about the previous AndroidX + migration. + +## 0.3.0 + +* **Breaking change**. Migrate from the deprecated original Android Support + Library to AndroidX. This shouldn't result in any functional changes, but it + requires any Android apps using this plugin to [also + migrate](https://developer.android.com/jetpack/androidx/migrate) if they're + using the original support library. + +## 0.2.5+1 + +* Bump Android dependencies to latest. + +## 0.2.5 + +* Bump Android and Firebase dependency versions. + +## 0.2.4 + +* Updated Gradle tooling to match Android Studio 3.1.2. + +## 0.2.3 + +* Updated Google Play Services dependencies to version 15.0.0. + +## 0.2.2 + +* Simplified podspec for Cocoapods 1.5.0, avoiding link issues in app archives. + +## 0.2.1 + +* Fix setting project ID on Android. + +## 0.2.0 + +* **Breaking change**. Options API is now async to interoperate with native code that configures Firebase apps. +* Provide a getter for the default app +* Fix setting of GCM sender ID on iOS + +## 0.1.2 + +* Fix projectID on iOS + +## 0.1.1 + +* Fix behavior of constructor for named Firebase apps. + +## 0.1.0 + +* **Breaking change**. Set SDK constraints to match the Flutter beta release. + +## 0.0.7 + +* Fixed Dart 2 type errors. + +## 0.0.6 + +* Enabled use in Swift projects. + +## 0.0.5 + +* Moved to the io.flutter.plugins org. + +## 0.0.4 + +* Fixed warnings from the Dart 2.0 analyzer. +* Simplified and upgraded Android project template to Android SDK 27. +* Updated package description. + +# 0.0.3 + +* **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin + 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in + order to use this version of the plugin. Instructions can be found + [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). + +## 0.0.2 + +* Fixes for database URL on Android +* Make GCM sender id optional on Android +* Relax GMS dependency to 11.+ + +## 0.0.1 + +* Initial Release diff --git a/packages/firebase_core/LICENSE b/packages/firebase_core/LICENSE new file mode 100644 index 0000000..000b461 --- /dev/null +++ b/packages/firebase_core/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/firebase_core/README.md b/packages/firebase_core/README.md new file mode 100644 index 0000000..bc72824 --- /dev/null +++ b/packages/firebase_core/README.md @@ -0,0 +1,16 @@ +# Firebase Core for Flutter + +[![pub package](https://img.shields.io/pub/v/firebase_core.svg)](https://pub.dartlang.org/packages/firebase_core) + +A Flutter plugin to use the Firebase Core API, which enables connecting to multiple Firebase apps. + +For Flutter plugins for other Firebase products, see [FlutterFire.md](https://github.com/flutter/plugins/blob/master/FlutterFire.md). + +*Note*: This plugin is still under development, and some APIs might not be available yet. [Feedback](https://github.com/flutter/flutter/issues) and [Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome! + +## Usage +To use this plugin, add `firebase_core` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). + +## Getting Started + +See the `example` directory for a complete sample app using Firebase Core. diff --git a/packages/firebase_core/android/build.gradle b/packages/firebase_core/android/build.gradle new file mode 100644 index 0000000..6d7d2c6 --- /dev/null +++ b/packages/firebase_core/android/build.gradle @@ -0,0 +1,51 @@ +def PLUGIN = "firebase_core"; +def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; +gradle.buildFinished { buildResult -> + if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { + println ' *********************************************************' + println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' + println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' + println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' + println ' *********************************************************' + rootProject.ext.set(ANDROIDX_WARNING, true); + } +} + +group 'io.flutter.plugins.firebase.core' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + dependencies { + api 'com.google.firebase:firebase-core:16.0.9' + implementation 'com.google.firebase:firebase-common:16.1.0' + } +} diff --git a/packages/firebase_core/android/gradle.properties b/packages/firebase_core/android/gradle.properties new file mode 100644 index 0000000..8bd86f6 --- /dev/null +++ b/packages/firebase_core/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/packages/firebase_core/android/settings.gradle b/packages/firebase_core/android/settings.gradle new file mode 100644 index 0000000..6b9f703 --- /dev/null +++ b/packages/firebase_core/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'firebase_core' diff --git a/packages/firebase_core/android/src/main/AndroidManifest.xml b/packages/firebase_core/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..25d4e41 --- /dev/null +++ b/packages/firebase_core/android/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/packages/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FirebaseCorePlugin.java b/packages/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FirebaseCorePlugin.java new file mode 100644 index 0000000..3d60df1 --- /dev/null +++ b/packages/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FirebaseCorePlugin.java @@ -0,0 +1,100 @@ +// 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.firebase.core; + +import android.content.Context; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import io.flutter.plugin.common.MethodCall; +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.lang.String; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FirebaseCorePlugin implements MethodCallHandler { + + private final Context context; + + public static void registerWith(PluginRegistry.Registrar registrar) { + final MethodChannel channel = + new MethodChannel(registrar.messenger(), "plugins.flutter.io/firebase_core"); + channel.setMethodCallHandler(new FirebaseCorePlugin(registrar.context())); + } + + private FirebaseCorePlugin(Context context) { + this.context = context; + } + + private Map asMap(FirebaseApp app) { + Map appMap = new HashMap<>(); + appMap.put("name", app.getName()); + FirebaseOptions options = app.getOptions(); + Map optionsMap = new HashMap<>(); + optionsMap.put("googleAppID", options.getApplicationId()); + optionsMap.put("GCMSenderID", options.getGcmSenderId()); + optionsMap.put("APIKey", options.getApiKey()); + optionsMap.put("databaseURL", options.getDatabaseUrl()); + optionsMap.put("storageBucket", options.getStorageBucket()); + optionsMap.put("projectID", options.getProjectId()); + appMap.put("options", optionsMap); + return appMap; + } + + @Override + public void onMethodCall(MethodCall call, final Result result) { + switch (call.method) { + case "FirebaseApp#configure": + { + Map arguments = call.arguments(); + String name = (String) arguments.get("name"); + @SuppressWarnings("unchecked") + Map optionsMap = (Map) arguments.get("options"); + FirebaseOptions options = + new FirebaseOptions.Builder() + .setApiKey(optionsMap.get("APIKey")) + .setApplicationId(optionsMap.get("googleAppID")) + .setDatabaseUrl(optionsMap.get("databaseURL")) + .setGcmSenderId(optionsMap.get("GCMSenderID")) + .setProjectId(optionsMap.get("projectID")) + .setStorageBucket(optionsMap.get("storageBucket")) + .build(); + FirebaseApp.initializeApp(context, options, name); + result.success(null); + break; + } + case "FirebaseApp#allApps": + { + List> apps = new ArrayList<>(); + for (FirebaseApp app : FirebaseApp.getApps(context)) { + apps.add(asMap(app)); + } + result.success(apps); + break; + } + case "FirebaseApp#appNamed": + { + String name = (String) call.arguments(); + try { + FirebaseApp app = FirebaseApp.getInstance(name); + result.success(asMap(app)); + } catch (IllegalStateException ex) { + // App doesn't exist, so successfully return null. + result.success(null); + } + break; + } + default: + { + result.notImplemented(); + break; + } + } + } +} diff --git a/packages/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FlutterFirebaseAppRegistrar.java b/packages/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FlutterFirebaseAppRegistrar.java new file mode 100644 index 0000000..68a5149 --- /dev/null +++ b/packages/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FlutterFirebaseAppRegistrar.java @@ -0,0 +1,18 @@ +package io.flutter.plugins.firebase.core; + +import com.google.firebase.components.Component; +import com.google.firebase.components.ComponentRegistrar; +import com.google.firebase.platforminfo.LibraryVersionComponent; +import java.util.Collections; +import java.util.List; + +public class FlutterFirebaseAppRegistrar implements ComponentRegistrar { + private static final String LIBRARY_NAME = "flutter-fire-core"; + private static final String LIBRARY_VERSION = "0.4.0+3"; + + @Override + public List> getComponents() { + return Collections.>singletonList( + LibraryVersionComponent.create(LIBRARY_NAME, LIBRARY_VERSION)); + } +} diff --git a/packages/firebase_core/example/.metadata b/packages/firebase_core/example/.metadata new file mode 100644 index 0000000..28ce4e5 --- /dev/null +++ b/packages/firebase_core/example/.metadata @@ -0,0 +1,8 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f993cc36dd4fdb8e3c741cbf66fd0cdeea975e37 + channel: unknown diff --git a/packages/firebase_core/example/README.md b/packages/firebase_core/example/README.md new file mode 100644 index 0000000..ea0fa78 --- /dev/null +++ b/packages/firebase_core/example/README.md @@ -0,0 +1,8 @@ +# firebase_core_example + +Demonstrates how to use the firebase_core plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/firebase_core/example/android.iml b/packages/firebase_core/example/android.iml new file mode 100644 index 0000000..462b903 --- /dev/null +++ b/packages/firebase_core/example/android.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/firebase_core/example/android/app/build.gradle b/packages/firebase_core/example/android/app/build.gradle new file mode 100644 index 0000000..bcd12c9 --- /dev/null +++ b/packages/firebase_core/example/android/app/build.gradle @@ -0,0 +1,60 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "io.flutter.plugins.firebasecoreexample" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/packages/firebase_core/example/android/app/gradle.properties b/packages/firebase_core/example/android/app/gradle.properties new file mode 100644 index 0000000..5465fec --- /dev/null +++ b/packages/firebase_core/example/android/app/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/packages/firebase_core/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_core/example/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9a4163a --- /dev/null +++ b/packages/firebase_core/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/firebase_core/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_core/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..48cbadd --- /dev/null +++ b/packages/firebase_core/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/packages/firebase_core/example/android/app/src/main/java/io/flutter/plugins/firebasecoreexample/MainActivity.java b/packages/firebase_core/example/android/app/src/main/java/io/flutter/plugins/firebasecoreexample/MainActivity.java new file mode 100644 index 0000000..7af8a09 --- /dev/null +++ b/packages/firebase_core/example/android/app/src/main/java/io/flutter/plugins/firebasecoreexample/MainActivity.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.firebasecoreexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/firebase_core/example/android/app/src/main/res/drawable/launch_background.xml b/packages/firebase_core/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/firebase_core/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/firebase_core/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/firebase_core/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/packages/firebase_core/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/firebase_core/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/firebase_core/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/packages/firebase_core/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/firebase_core/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/firebase_core/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/packages/firebase_core/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/firebase_core/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/firebase_core/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/packages/firebase_core/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/firebase_core/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/firebase_core/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/packages/firebase_core/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/firebase_core/example/android/app/src/main/res/values/styles.xml b/packages/firebase_core/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/packages/firebase_core/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/packages/firebase_core/example/android/build.gradle b/packages/firebase_core/example/android/build.gradle new file mode 100644 index 0000000..541636c --- /dev/null +++ b/packages/firebase_core/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/firebase_core/example/android/gradle.properties b/packages/firebase_core/example/android/gradle.properties new file mode 100644 index 0000000..8bd86f6 --- /dev/null +++ b/packages/firebase_core/example/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/packages/firebase_core/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_core/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2819f02 --- /dev/null +++ b/packages/firebase_core/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/packages/firebase_core/example/android/settings.gradle b/packages/firebase_core/example/android/settings.gradle new file mode 100644 index 0000000..840d7be --- /dev/null +++ b/packages/firebase_core/example/android/settings.gradle @@ -0,0 +1,16 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} + diff --git a/packages/firebase_core/example/firebase_core_example.iml b/packages/firebase_core/example/firebase_core_example.iml new file mode 100644 index 0000000..4881df8 --- /dev/null +++ b/packages/firebase_core/example/firebase_core_example.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/firebase_core/example/firebase_core_example_android.iml b/packages/firebase_core/example/firebase_core_example_android.iml new file mode 100644 index 0000000..0ca70ed --- /dev/null +++ b/packages/firebase_core/example/firebase_core_example_android.iml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_core/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_core/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6c2de80 --- /dev/null +++ b/packages/firebase_core/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + UIRequiredDeviceCapabilities + + arm64 + + MinimumOSVersion + 8.0 + + diff --git a/packages/firebase_core/example/ios/Flutter/Debug.xcconfig b/packages/firebase_core/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/packages/firebase_core/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/firebase_core/example/ios/Flutter/Release.xcconfig b/packages/firebase_core/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/packages/firebase_core/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/firebase_core/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_core/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..77192aa --- /dev/null +++ b/packages/firebase_core/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,497 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2AE3C69805AB6FB8C037C7BA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 39D23D0C629B8A857DE66538 /* libPods-Runner.a */; }; + 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 39D23D0C629B8A857DE66538 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 2AE3C69805AB6FB8C037C7BA /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 39F1FF2B4D76A033C08A8FD9 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 39F1FF2B4D76A033C08A8FD9 /* Pods */, + C5757EE16C6DD6ADC639F4D9 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C5757EE16C6DD6ADC639F4D9 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 39D23D0C629B8A857DE66538 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 4A37569BEFBE2FD279304116 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 531483E699D87EBBC063AF7F /* [CP] Embed Pods Frameworks */, + 177D2DB1F56033C34D20320F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 177D2DB1F56033C34D20320F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 4A37569BEFBE2FD279304116 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 531483E699D87EBBC063AF7F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebaseCoreExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebaseCoreExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/firebase_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/firebase_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..1c95807 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/firebase_core/example/ios/Runner/AppDelegate.h b/packages/firebase_core/example/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..36e21bb --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/firebase_core/example/ios/Runner/AppDelegate.m b/packages/firebase_core/example/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..59a72e9 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d22f10b --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/firebase_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/firebase_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_core/example/ios/Runner/Base.lproj/Main.storyboard b/packages/firebase_core/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_core/example/ios/Runner/Info.plist b/packages/firebase_core/example/ios/Runner/Info.plist new file mode 100644 index 0000000..02b2714 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + firebase_core_example + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/firebase_core/example/ios/Runner/main.m b/packages/firebase_core/example/ios/Runner/main.m new file mode 100644 index 0000000..dff6597 --- /dev/null +++ b/packages/firebase_core/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/firebase_core/example/lib/main.dart b/packages/firebase_core/example/lib/main.dart new file mode 100644 index 0000000..de23153 --- /dev/null +++ b/packages/firebase_core/example/lib/main.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:firebase_core/firebase_core.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + final String name = 'foo'; + final FirebaseOptions options = const FirebaseOptions( + googleAppID: '1:297855924061:ios:c6de2b69b03a5be8', + gcmSenderID: '297855924061', + apiKey: 'AIzaSyBq6mcufFXfyqr79uELCiqM_O_1-G72PVU', + ); + + Future _configure() async { + final FirebaseApp app = await FirebaseApp.configure( + name: name, + options: options, + ); + assert(app != null); + print('Configured $app'); + } + + Future _allApps() async { + final List apps = await FirebaseApp.allApps(); + print('Currently configured apps: $apps'); + } + + Future _options() async { + final FirebaseApp app = await FirebaseApp.appNamed(name); + final FirebaseOptions options = await app?.options; + print('Current options for app $name: $options'); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Firebase Core example app'), + ), + body: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RaisedButton( + onPressed: _configure, child: const Text('initialize')), + RaisedButton(onPressed: _allApps, child: const Text('allApps')), + RaisedButton(onPressed: _options, child: const Text('options')), + ], + ), + ), + ), + ); + } +} diff --git a/packages/firebase_core/example/pubspec.yaml b/packages/firebase_core/example/pubspec.yaml new file mode 100644 index 0000000..a020d14 --- /dev/null +++ b/packages/firebase_core/example/pubspec.yaml @@ -0,0 +1,24 @@ +name: firebase_core_example +description: Demonstrates how to use the firebase_core plugin. + +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + firebase_core: + path: ../ + +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + uses-material-design: true diff --git a/packages/firebase_core/firebase_core.iml b/packages/firebase_core/firebase_core.iml new file mode 100644 index 0000000..a6a44fe --- /dev/null +++ b/packages/firebase_core/firebase_core.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/firebase_core/firebase_core_android.iml b/packages/firebase_core/firebase_core_android.iml new file mode 100644 index 0000000..0ebb6c9 --- /dev/null +++ b/packages/firebase_core/firebase_core_android.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_core/ios/Assets/.gitkeep b/packages/firebase_core/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/firebase_core/ios/Classes/FirebaseCorePlugin.h b/packages/firebase_core/ios/Classes/FirebaseCorePlugin.h new file mode 100644 index 0000000..42a71d2 --- /dev/null +++ b/packages/firebase_core/ios/Classes/FirebaseCorePlugin.h @@ -0,0 +1,8 @@ +// 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 + +@interface FLTFirebaseCorePlugin : NSObject +@end diff --git a/packages/firebase_core/ios/Classes/FirebaseCorePlugin.m b/packages/firebase_core/ios/Classes/FirebaseCorePlugin.m new file mode 100644 index 0000000..9bb1d95 --- /dev/null +++ b/packages/firebase_core/ios/Classes/FirebaseCorePlugin.m @@ -0,0 +1,96 @@ +// 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 "FirebaseCorePlugin.h" + +#import + +#define LIBRARY_NAME @"flutter-fire-core" +#define LIBRARY_VERSION @"0.4.0+3" + +static NSDictionary *getDictionaryFromFIROptions(FIROptions *options) { + if (!options) { + return nil; + } + return @{ + @"googleAppID" : options.googleAppID ?: [NSNull null], + @"bundleID" : options.bundleID ?: [NSNull null], + @"GCMSenderID" : options.GCMSenderID ?: [NSNull null], + @"APIKey" : options.APIKey ?: [NSNull null], + @"clientID" : options.clientID ?: [NSNull null], + @"trackingID" : options.trackingID ?: [NSNull null], + @"projectID" : options.projectID ?: [NSNull null], + @"androidClientID" : options.androidClientID ?: [NSNull null], + @"databaseUrl" : options.databaseURL ?: [NSNull null], + @"storageBucket" : options.storageBucket ?: [NSNull null], + @"deepLinkURLScheme" : options.deepLinkURLScheme ?: [NSNull null], + }; +} + +static NSDictionary *getDictionaryFromFIRApp(FIRApp *app) { + if (!app) { + return nil; + } + return @{@"name" : app.name, @"options" : getDictionaryFromFIROptions(app.options)}; +} + +@implementation FLTFirebaseCorePlugin ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_core" + binaryMessenger:[registrar messenger]]; + FLTFirebaseCorePlugin *instance = [[FLTFirebaseCorePlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; + + SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:"); + if ([FIRApp respondsToSelector:sel]) { + [FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION]; + } +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"FirebaseApp#configure" isEqualToString:call.method]) { + NSString *name = call.arguments[@"name"]; + NSDictionary *optionsDictionary = call.arguments[@"options"]; + FIROptions *options = + [[FIROptions alloc] initWithGoogleAppID:optionsDictionary[@"googleAppID"] + GCMSenderID:optionsDictionary[@"GCMSenderID"]]; + if (![optionsDictionary[@"bundleID"] isEqual:[NSNull null]]) + options.bundleID = optionsDictionary[@"bundleID"]; + if (![optionsDictionary[@"APIKey"] isEqual:[NSNull null]]) + options.APIKey = optionsDictionary[@"APIKey"]; + if (![optionsDictionary[@"clientID"] isEqual:[NSNull null]]) + options.clientID = optionsDictionary[@"clientID"]; + if (![optionsDictionary[@"trackingID"] isEqual:[NSNull null]]) + options.trackingID = optionsDictionary[@"trackingID"]; + if (![optionsDictionary[@"projectID"] isEqual:[NSNull null]]) + options.projectID = optionsDictionary[@"projectID"]; + if (![optionsDictionary[@"androidClientID"] isEqual:[NSNull null]]) + options.androidClientID = optionsDictionary[@"androidClientID"]; + if (![optionsDictionary[@"databaseURL"] isEqual:[NSNull null]]) + options.databaseURL = optionsDictionary[@"databaseURL"]; + if (![optionsDictionary[@"storageBucket"] isEqual:[NSNull null]]) + options.storageBucket = optionsDictionary[@"storageBucket"]; + if (![optionsDictionary[@"deepLinkURLScheme"] isEqual:[NSNull null]]) + options.deepLinkURLScheme = optionsDictionary[@"deepLinkURLScheme"]; + [FIRApp configureWithName:name options:options]; + result(nil); + } else if ([@"FirebaseApp#allApps" isEqualToString:call.method]) { + NSDictionary *allApps = [FIRApp allApps]; + NSMutableArray *appsList = [NSMutableArray array]; + for (NSString *name in allApps) { + FIRApp *app = allApps[name]; + [appsList addObject:getDictionaryFromFIRApp(app)]; + } + result(appsList.count > 0 ? appsList : nil); + } else if ([@"FirebaseApp#appNamed" isEqualToString:call.method]) { + NSString *name = call.arguments; + FIRApp *app = [FIRApp appNamed:name]; + result(getDictionaryFromFIRApp(app)); + } else { + result(FlutterMethodNotImplemented); + } +} + +@end diff --git a/packages/firebase_core/ios/firebase_core.podspec b/packages/firebase_core/ios/firebase_core.podspec new file mode 100644 index 0000000..caa91ef --- /dev/null +++ b/packages/firebase_core/ios/firebase_core.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'firebase_core' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.dependency 'Firebase/Core' + s.ios.deployment_target = '8.0' + s.static_framework = true +end diff --git a/packages/firebase_core/lib/firebase_core.dart b/packages/firebase_core/lib/firebase_core.dart new file mode 100644 index 0000000..7972063 --- /dev/null +++ b/packages/firebase_core/lib/firebase_core.dart @@ -0,0 +1,15 @@ +// Copyright 2017, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library firebase_core; + +import 'dart:async'; +import 'dart:io' show Platform; +import 'dart:ui' show hashValues; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; + +part 'src/firebase_app.dart'; +part 'src/firebase_options.dart'; diff --git a/packages/firebase_core/lib/src/firebase_app.dart b/packages/firebase_core/lib/src/firebase_app.dart new file mode 100644 index 0000000..6fefe9c --- /dev/null +++ b/packages/firebase_core/lib/src/firebase_app.dart @@ -0,0 +1,104 @@ +// Copyright 2017, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of firebase_core; + +class FirebaseApp { + @visibleForTesting + const FirebaseApp({@required this.name}) : assert(name != null); + + /// The name of this app. + final String name; + + static final String defaultAppName = + Platform.isIOS ? '__FIRAPP_DEFAULT' : '[DEFAULT]'; + + @visibleForTesting + static const MethodChannel channel = MethodChannel( + 'plugins.flutter.io/firebase_core', + ); + + /// A copy of the options for this app. These are non-modifiable. + /// + /// This getter is asynchronous because apps can also be configured by native + /// code. + Future get options async { + final Map app = + await channel.invokeMapMethod( + 'FirebaseApp#appNamed', + name, + ); + assert(app != null); + return FirebaseOptions.from(app['options']); + } + + /// Returns a previously created FirebaseApp instance with the given name, + /// or null if no such app exists. + static Future appNamed(String name) async { + // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. + // https://github.com/flutter/flutter/issues/26431 + // ignore: strong_mode_implicit_dynamic_method + final Map app = + await channel.invokeMapMethod( + 'FirebaseApp#appNamed', + name, + ); + return app == null ? null : FirebaseApp(name: app['name']); + } + + /// Returns the default (first initialized) instance of the FirebaseApp. + static final FirebaseApp instance = FirebaseApp(name: defaultAppName); + + /// Configures an app with the given [name] and [options]. + /// + /// Configuring the default app is not currently supported. Plugins that + /// can interact with the default app should configure it automatically at + /// plugin registration time. + /// + /// Changing the options of a configured app is not supported. + static Future configure({ + @required String name, + @required FirebaseOptions options, + }) async { + assert(name != null); + assert(name != defaultAppName); + assert(options != null); + assert(options.googleAppID != null); + final FirebaseApp existingApp = await FirebaseApp.appNamed(name); + if (existingApp != null) { + return existingApp; + } + await channel.invokeMethod( + 'FirebaseApp#configure', + {'name': name, 'options': options.asMap}, + ); + return FirebaseApp(name: name); + } + + /// Returns a list of all extant FirebaseApp instances, or null if there are + /// no FirebaseApp instances. + static Future> allApps() async { + final List result = await channel.invokeListMethod( + 'FirebaseApp#allApps', + ); + return result + ?.map( + (dynamic app) => FirebaseApp(name: app['name']), + ) + ?.toList(); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (other is! FirebaseApp) return false; + return other.name == name; + } + + @override + int get hashCode => name.hashCode; + + @override + String toString() => '$FirebaseApp($name)'; +} diff --git a/packages/firebase_core/lib/src/firebase_options.dart b/packages/firebase_core/lib/src/firebase_options.dart new file mode 100644 index 0000000..bbd8886 --- /dev/null +++ b/packages/firebase_core/lib/src/firebase_options.dart @@ -0,0 +1,150 @@ +// Copyright 2017, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of firebase_core; + +class FirebaseOptions { + const FirebaseOptions({ + this.apiKey, + this.bundleID, + this.clientID, + this.trackingID, + this.gcmSenderID, + this.projectID, + this.androidClientID, + @required this.googleAppID, + this.databaseURL, + this.deepLinkURLScheme, + this.storageBucket, + }) : assert(googleAppID != null); + + @visibleForTesting + FirebaseOptions.from(Map map) + : apiKey = map['APIKey'], + bundleID = map['bundleID'], + clientID = map['clientID'], + trackingID = map['trackingID'], + gcmSenderID = map['GCMSenderID'], + projectID = map['projectID'], + androidClientID = map['androidClientID'], + googleAppID = map['googleAppID'], + databaseURL = map['databaseURL'], + deepLinkURLScheme = map['deepLinkURLScheme'], + storageBucket = map['storageBucket'] { + assert(googleAppID != null); + } + + /// An API key used for authenticating requests from your app, e.g. + /// "AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to + /// Google servers. + /// + /// This property is required on Android. + final String apiKey; + + /// The iOS bundle ID for the application. Defaults to + /// `[[NSBundle mainBundle] bundleID]` when not set manually or in a plist. + /// + /// This property is used on iOS only. + final String bundleID; + + /// The OAuth2 client ID for iOS application used to authenticate Google + /// users, for example "12345.apps.googleusercontent.com", used for signing in + /// with Google. + /// + /// This property is used on iOS only. + final String clientID; + + /// The tracking ID for Google Analytics, e.g. "UA-12345678-1", used to + /// configure Google Analytics. + /// + /// This property is used on iOS only. + final String trackingID; + + /// The Project Number from the Google Developer’s console, for example + /// "012345678901", used to configure Google Cloud Messaging. + /// + /// This property is required on iOS. + final String gcmSenderID; + + /// The Project ID from the Firebase console, for example "abc-xyz-123." + final String projectID; + + /// The Android client ID, for example "12345.apps.googleusercontent.com." + /// + /// This property is used on iOS only. + final String androidClientID; + + /// The Google App ID that is used to uniquely identify an instance of an app. + /// + /// This property cannot be `null`. + final String googleAppID; + + /// The database root URL, e.g. "http://abc-xyz-123.firebaseio.com." + /// + /// This property should be set for apps that use Firebase Database. + final String databaseURL; + + /// The URL scheme used to set up Durable Deep Link service. + /// + /// This property is used on iOS only. + final String deepLinkURLScheme; + + /// The Google Cloud Storage bucket name, e.g. + /// "abc-xyz-123.storage.firebase.com." + final String storageBucket; + + @visibleForTesting + Map get asMap { + return { + 'APIKey': apiKey, + 'bundleID': bundleID, + 'clientID': clientID, + 'trackingID': trackingID, + 'GCMSenderID': gcmSenderID, + 'projectID': projectID, + 'androidClientID': androidClientID, + 'googleAppID': googleAppID, + 'databaseURL': databaseURL, + 'deepLinkURLScheme': deepLinkURLScheme, + 'storageBucket': storageBucket, + }; + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (other is! FirebaseOptions) return false; + return other.apiKey == apiKey && + other.bundleID == bundleID && + other.clientID == clientID && + other.trackingID == trackingID && + other.gcmSenderID == gcmSenderID && + other.projectID == projectID && + other.androidClientID == androidClientID && + other.googleAppID == googleAppID && + other.databaseURL == databaseURL && + other.deepLinkURLScheme == deepLinkURLScheme && + other.storageBucket == storageBucket; + } + + @override + int get hashCode { + return hashValues( + apiKey, + bundleID, + clientID, + trackingID, + gcmSenderID, + projectID, + androidClientID, + googleAppID, + databaseURL, + deepLinkURLScheme, + storageBucket, + ); + } + + @override + String toString() => asMap.toString(); +} diff --git a/packages/firebase_core/pubspec.yaml b/packages/firebase_core/pubspec.yaml new file mode 100644 index 0000000..504e44f --- /dev/null +++ b/packages/firebase_core/pubspec.yaml @@ -0,0 +1,25 @@ +name: firebase_core +description: Flutter plugin for Firebase Core, enabling connecting to multiple + Firebase apps. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/firebase_core +version: 0.4.0+3 + +flutter: + plugin: + androidPackage: io.flutter.plugins.firebase.core + iosPrefix: FLT + pluginClass: FirebaseCorePlugin + +dependencies: + flutter: + sdk: flutter + meta: "^1.0.5" + +dev_dependencies: + flutter_test: + sdk: flutter + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/packages/firebase_core/test/firebase_core_test.dart b/packages/firebase_core/test/firebase_core_test.dart new file mode 100755 index 0000000..d42ccd9 --- /dev/null +++ b/packages/firebase_core/test/firebase_core_test.dart @@ -0,0 +1,137 @@ +// Copyright 2017, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('$FirebaseApp', () { + final List log = []; + const FirebaseApp testApp = FirebaseApp( + name: 'testApp', + ); + const FirebaseOptions testOptions = FirebaseOptions( + apiKey: 'testAPIKey', + bundleID: 'testBundleID', + clientID: 'testClientID', + trackingID: 'testTrackingID', + gcmSenderID: 'testGCMSenderID', + projectID: 'testProjectID', + androidClientID: 'testAndroidClientID', + googleAppID: 'testGoogleAppID', + databaseURL: 'testDatabaseURL', + deepLinkURLScheme: 'testDeepLinkURLScheme', + storageBucket: 'testStorageBucket', + ); + + setUp(() async { + FirebaseApp.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'FirebaseApp#appNamed': + if (methodCall.arguments != 'testApp') return null; + return { + 'name': 'testApp', + 'options': { + 'APIKey': 'testAPIKey', + 'bundleID': 'testBundleID', + 'clientID': 'testClientID', + 'trackingID': 'testTrackingID', + 'GCMSenderID': 'testGCMSenderID', + 'projectID': 'testProjectID', + 'androidClientID': 'testAndroidClientID', + 'googleAppID': 'testGoogleAppID', + 'databaseURL': 'testDatabaseURL', + 'deepLinkURLScheme': 'testDeepLinkURLScheme', + 'storageBucket': 'testStorageBucket', + }, + }; + case 'FirebaseApp#allApps': + return >[ + { + 'name': 'testApp', + }, + ]; + default: + return null; + } + }); + log.clear(); + }); + + test('configure', () async { + final FirebaseApp reconfiguredApp = await FirebaseApp.configure( + name: 'testApp', + options: testOptions, + ); + expect(reconfiguredApp, equals(testApp)); + final FirebaseApp newApp = await FirebaseApp.configure( + name: 'newApp', + options: testOptions, + ); + expect(newApp.name, equals('newApp')); + expect( + log, + [ + isMethodCall( + 'FirebaseApp#appNamed', + arguments: 'testApp', + ), + isMethodCall( + 'FirebaseApp#appNamed', + arguments: 'newApp', + ), + isMethodCall( + 'FirebaseApp#configure', + arguments: { + 'name': 'newApp', + 'options': testOptions.asMap, + }, + ), + ], + ); + }); + + test('appNamed', () async { + final FirebaseApp existingApp = await FirebaseApp.appNamed('testApp'); + expect(existingApp.name, equals('testApp')); + expect((await existingApp.options), equals(testOptions)); + final FirebaseApp missingApp = await FirebaseApp.appNamed('missingApp'); + expect(missingApp, isNull); + expect( + log, + [ + isMethodCall( + 'FirebaseApp#appNamed', + arguments: 'testApp', + ), + isMethodCall( + 'FirebaseApp#appNamed', + arguments: 'testApp', + ), + isMethodCall( + 'FirebaseApp#appNamed', + arguments: 'missingApp', + ), + ], + ); + }); + + test('allApps', () async { + final List allApps = await FirebaseApp.allApps(); + expect(allApps, equals([testApp])); + expect( + log, + [ + isMethodCall( + 'FirebaseApp#allApps', + arguments: null, + ), + ], + ); + }); + }); +}