From 2575fb49567da2ce767e0674df018b94544f773e Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 6 Oct 2017 12:00:40 +0100 Subject: [PATCH 01/21] [firestore] Support all `onSnapshot` parameter options --- .../firestore/RNFirebaseFirestore.java | 10 +- ...NFirebaseFirestoreCollectionReference.java | 18 +- .../RNFirebaseFirestoreDocumentReference.java | 9 +- .../firestore/RNFirebaseFirestore.m | 10 +- .../RNFirebaseFirestoreCollectionReference.h | 2 +- .../RNFirebaseFirestoreCollectionReference.m | 16 +- .../RNFirebaseFirestoreDocumentReference.h | 2 +- .../RNFirebaseFirestoreDocumentReference.m | 10 +- lib/modules/firestore/DocumentReference.js | 74 ++- lib/modules/firestore/Query.js | 77 ++- tests/ios/Podfile.lock | 32 +- .../firestore/collectionReferenceTests.js | 536 +++++++++++------- .../tests/firestore/documentReferenceTests.js | 455 +++++++++------ 13 files changed, 807 insertions(+), 444 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java index 1fc4d4d1..a55a4a6b 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java @@ -57,9 +57,10 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { @ReactMethod public void collectionOnSnapshot(String appName, String path, ReadableArray filters, - ReadableArray orders, ReadableMap options, String listenerId) { + ReadableArray orders, ReadableMap options, String listenerId, + ReadableMap queryListenOptions) { RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); - ref.onSnapshot(listenerId); + ref.onSnapshot(listenerId, queryListenOptions); } @@ -145,9 +146,10 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { } @ReactMethod - public void documentOnSnapshot(String appName, String path, String listenerId) { + public void documentOnSnapshot(String appName, String path, String listenerId, + ReadableMap docListenOptions) { RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); - ref.onSnapshot(listenerId); + ref.onSnapshot(listenerId, docListenOptions); } @ReactMethod diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java index e8512d19..263cde63 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java @@ -12,10 +12,12 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.DocumentListenOptions; import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.ListenerRegistration; import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QueryListenOptions; import com.google.firebase.firestore.QuerySnapshot; import java.util.HashMap; @@ -71,7 +73,7 @@ public class RNFirebaseFirestoreCollectionReference { } } - public void onSnapshot(final String listenerId) { + public void onSnapshot(final String listenerId, final ReadableMap queryListenOptions) { if (!collectionSnapshotListeners.containsKey(listenerId)) { final EventListener listener = new EventListener() { @Override @@ -87,7 +89,19 @@ public class RNFirebaseFirestoreCollectionReference { } } }; - ListenerRegistration listenerRegistration = this.query.addSnapshotListener(listener); + QueryListenOptions options = new QueryListenOptions(); + if (queryListenOptions != null) { + if (queryListenOptions.hasKey("includeDocumentMetadataChanges") + && queryListenOptions.getBoolean("includeDocumentMetadataChanges")) { + options.includeDocumentMetadataChanges(); + } + if (queryListenOptions.hasKey("includeQueryMetadataChanges") + && queryListenOptions.getBoolean("includeQueryMetadataChanges")) { + options.includeQueryMetadataChanges(); + } + } + + ListenerRegistration listenerRegistration = this.query.addSnapshotListener(options, listener); collectionSnapshotListeners.put(listenerId, listenerRegistration); } } diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java index f35d2056..026d04f5 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java @@ -10,6 +10,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.DocumentListenOptions; import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.EventListener; @@ -85,7 +86,7 @@ public class RNFirebaseFirestoreDocumentReference { } } - public void onSnapshot(final String listenerId) { + public void onSnapshot(final String listenerId, final ReadableMap docListenOptions) { if (!documentSnapshotListeners.containsKey(listenerId)) { final EventListener listener = new EventListener() { @Override @@ -101,7 +102,11 @@ public class RNFirebaseFirestoreDocumentReference { } } }; - ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(listener); + DocumentListenOptions options = new DocumentListenOptions(); + if (docListenOptions != null && docListenOptions.hasKey("includeMetadataChanges") && docListenOptions.getBoolean("includeMetadataChanges")) { + options.includeMetadataChanges(); + } + ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(options, listener); documentSnapshotListeners.put(listenerId, listenerRegistration); } } diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestore.m b/ios/RNFirebase/firestore/RNFirebaseFirestore.m index 9a7df690..ed056a1c 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestore.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestore.m @@ -42,9 +42,10 @@ RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *) appName filters:(NSArray *) filters orders:(NSArray *) orders options:(NSDictionary *) options - listenerId:(nonnull NSString *) listenerId) { + listenerId:(nonnull NSString *) listenerId + queryListenOptions:(NSDictionary *) queryListenOptions) { RNFirebaseFirestoreCollectionReference *ref = [self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options]; - [ref onSnapshot:listenerId]; + [ref onSnapshot:listenerId queryListenOptions:queryListenOptions]; } RCT_EXPORT_METHOD(documentBatch:(NSString *) appName @@ -128,9 +129,10 @@ RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *) appName RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appName path:(NSString *) path - listenerId:(nonnull NSString *) listenerId) { + listenerId:(nonnull NSString *) listenerId + docListenOptions:(NSDictionary *) docListenOptions) { RNFirebaseFirestoreDocumentReference *ref = [self getDocumentForAppPath:appName path:path]; - [ref onSnapshot:listenerId]; + [ref onSnapshot:listenerId docListenOptions:docListenOptions]; } RCT_EXPORT_METHOD(documentSet:(NSString *) appName diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h index b03904b4..7371f2b1 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h @@ -22,7 +22,7 @@ - (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options; - (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; + (void)offSnapshot:(NSString *)listenerId; -- (void)onSnapshot:(NSString *)listenerId; +- (void)onSnapshot:(NSString *)listenerId queryListenOptions:(NSDictionary *) queryListenOptions; + (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot; @end diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m index 1cc0e3ab..64f162bd 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m @@ -49,7 +49,8 @@ static NSMutableDictionary *_listeners; } } -- (void)onSnapshot:(NSString *) listenerId { +- (void)onSnapshot:(NSString *) listenerId +queryListenOptions:(NSDictionary *) queryListenOptions { if (_listeners[listenerId] == nil) { id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { if (error) { @@ -63,7 +64,18 @@ static NSMutableDictionary *_listeners; [self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot]; } }; - id listener = [_query addSnapshotListener:listenerBlock]; + + FIRQueryListenOptions *options = [[FIRQueryListenOptions alloc] init]; + if (queryListenOptions) { + if (queryListenOptions[@"includeDocumentMetadataChanges"]) { + [options includeDocumentMetadataChanges:TRUE]; + } + if (queryListenOptions[@"includeQueryMetadataChanges"]) { + [options includeQueryMetadataChanges:TRUE]; + } + } + + id listener = [_query addSnapshotListenerWithOptions:options listener:listenerBlock]; _listeners[listenerId] = listener; } } diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h index 53fdab87..238388f6 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h @@ -22,7 +22,7 @@ - (void)delete:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; + (void)offSnapshot:(NSString *)listenerId; -- (void)onSnapshot:(NSString *)listenerId; +- (void)onSnapshot:(NSString *)listenerId docListenOptions:(NSDictionary *) docListenOptions; - (void)set:(NSDictionary *)data options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (BOOL)hasListeners; diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m index 3f3c0d16..0a458731 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m @@ -61,7 +61,8 @@ static NSMutableDictionary *_listeners; } } -- (void)onSnapshot:(NSString *) listenerId { +- (void)onSnapshot:(NSString *) listenerId + docListenOptions:(NSDictionary *) docListenOptions { if (_listeners[listenerId] == nil) { id listenerBlock = ^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) { if (error) { @@ -75,8 +76,11 @@ static NSMutableDictionary *_listeners; [self handleDocumentSnapshotEvent:listenerId documentSnapshot:snapshot]; } }; - - id listener = [_ref addSnapshotListener:listenerBlock]; + FIRDocumentListenOptions *options = [[FIRDocumentListenOptions alloc] init]; + if (docListenOptions && docListenOptions[@"includeMetadataChanges"]) { + [options includeMetadataChanges:TRUE]; + } + id listener = [_ref addSnapshotListenerWithOptions:options listener:listenerBlock]; _listeners[listenerId] = listener; } } diff --git a/lib/modules/firestore/DocumentReference.js b/lib/modules/firestore/DocumentReference.js index c5cc9634..2210bca1 100644 --- a/lib/modules/firestore/DocumentReference.js +++ b/lib/modules/firestore/DocumentReference.js @@ -5,12 +5,21 @@ import CollectionReference from './CollectionReference'; import DocumentSnapshot from './DocumentSnapshot'; import Path from './Path'; -import { firestoreAutoId } from '../../utils'; +import { firestoreAutoId, isFunction, isObject } from '../../utils'; export type WriteOptions = { merge?: boolean, } +type DocumentListenOptions = { + includeMetadataChanges: boolean, +} + +type Observer = { + next: (DocumentSnapshot) => void, + error?: (Object) => void, +} + /** * @class DocumentReference */ @@ -60,13 +69,64 @@ export default class DocumentReference { .then(result => new DocumentSnapshot(this._firestore, result)); } - onSnapshot(onNext: Function, onError?: Function): () => void { - // TODO: Validation + onSnapshot( + optionsOrObserverOrOnNext: DocumentListenOptions | Observer | (DocumentSnapshot) => void, + observerOrOnNextOrOnError?: Observer | (DocumentSnapshot) => void | (Object) => void, + onError?: (Object) => void + ) { + let observer = {}; + let docListenOptions = {}; + // Called with: onNext, ?onError + if (isFunction(optionsOrObserverOrOnNext)) { + observer.next = optionsOrObserverOrOnNext; + if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { + throw new Error('DocumentReference.onSnapshot failed: Second argument must be a valid function.'); + } + observer.error = observerOrOnNextOrOnError; + } else if (optionsOrObserverOrOnNext && isObject(optionsOrObserverOrOnNext)) { + // Called with: Observer + if (optionsOrObserverOrOnNext.next) { + if (isFunction(optionsOrObserverOrOnNext.next)) { + if (optionsOrObserverOrOnNext.error && !isFunction(optionsOrObserverOrOnNext.error)) { + throw new Error('DocumentReference.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = optionsOrObserverOrOnNext; + } else { + throw new Error('DocumentReference.onSnapshot failed: Observer.next must be a valid function.'); + } + } else if (optionsOrObserverOrOnNext.includeMetadataChanges) { + docListenOptions = optionsOrObserverOrOnNext; + // Called with: Options, onNext, ?onError + if (isFunction(observerOrOnNextOrOnError)) { + observer.next = observerOrOnNextOrOnError; + if (onError && !isFunction(onError)) { + throw new Error('DocumentReference.onSnapshot failed: Third argument must be a valid function.'); + } + observer.error = onError; + // Called with Options, Observer + } else if (observerOrOnNextOrOnError && isObject(observerOrOnNextOrOnError) && observerOrOnNextOrOnError.next) { + if (isFunction(observerOrOnNextOrOnError.next)) { + if (observerOrOnNextOrOnError.error && !isFunction(observerOrOnNextOrOnError.error)) { + throw new Error('DocumentReference.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = observerOrOnNextOrOnError; + } else { + throw new Error('DocumentReference.onSnapshot failed: Observer.next must be a valid function.'); + } + } else { + throw new Error('DocumentReference.onSnapshot failed: Second argument must be a function or observer.'); + } + } else { + throw new Error('DocumentReference.onSnapshot failed: First argument must be a function, observer or options.'); + } + } else { + throw new Error('DocumentReference.onSnapshot failed: Called with invalid arguments.'); + } const listenerId = firestoreAutoId(); const listener = (nativeDocumentSnapshot) => { const documentSnapshot = new DocumentSnapshot(this, nativeDocumentSnapshot); - onNext(documentSnapshot); + observer.next(documentSnapshot); }; // Listen to snapshot events @@ -76,16 +136,16 @@ export default class DocumentReference { ); // Listen for snapshot error events - if (onError) { + if (observer.error) { this._firestore.on( this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`), - onError, + observer.error, ); } // Add the native listener this._firestore._native - .documentOnSnapshot(this.path, listenerId); + .documentOnSnapshot(this.path, listenerId, docListenOptions); // Return an unsubscribe method return this._offDocumentSnapshot.bind(this, listenerId, listener); diff --git a/lib/modules/firestore/Query.js b/lib/modules/firestore/Query.js index cdf5daa5..74d1f449 100644 --- a/lib/modules/firestore/Query.js +++ b/lib/modules/firestore/Query.js @@ -5,8 +5,7 @@ import DocumentSnapshot from './DocumentSnapshot'; import Path from './Path'; import QuerySnapshot from './QuerySnapshot'; -import INTERNALS from '../../internals'; -import { firestoreAutoId } from '../../utils'; +import { firestoreAutoId, isFunction, isObject } from '../../utils'; const DIRECTIONS = { ASC: 'ASCENDING', @@ -44,6 +43,15 @@ type QueryOptions = { startAt?: any[], } export type Operator = '<' | '<=' | '=' | '==' | '>' | '>='; +type QueryListenOptions = { + includeQueryMetadataChanges: boolean, + includeQueryMetadataChanges: boolean, +} + +type Observer = { + next: (DocumentSnapshot) => void, + error?: (Object) => void, +} /** * @class Query @@ -116,13 +124,65 @@ export default class Query { this._fieldOrders, options); } - onSnapshot(onNext: () => any, onError?: () => any): () => void { - // TODO: Validation + onSnapshot( + optionsOrObserverOrOnNext: QueryListenOptions | Observer | (DocumentSnapshot) => void, + observerOrOnNextOrOnError?: Observer | (DocumentSnapshot) => void | (Object) => void, + onError?: (Object) => void, + ) { + let observer = {}; + let queryListenOptions = {}; + // Called with: onNext, ?onError + if (isFunction(optionsOrObserverOrOnNext)) { + observer.next = optionsOrObserverOrOnNext; + if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { + throw new Error('Query.onSnapshot failed: Second argument must be a valid function.'); + } + observer.error = observerOrOnNextOrOnError; + } else if (optionsOrObserverOrOnNext && isObject(optionsOrObserverOrOnNext)) { + // Called with: Observer + if (optionsOrObserverOrOnNext.next) { + if (isFunction(optionsOrObserverOrOnNext.next)) { + if (optionsOrObserverOrOnNext.error && !isFunction(optionsOrObserverOrOnNext.error)) { + throw new Error('Query.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = optionsOrObserverOrOnNext; + } else { + throw new Error('Query.onSnapshot failed: Observer.next must be a valid function.'); + } + } else if (optionsOrObserverOrOnNext.includeDocumentMetadataChanges || optionsOrObserverOrOnNext.includeQueryMetadataChanges) { + queryListenOptions = optionsOrObserverOrOnNext; + // Called with: Options, onNext, ?onError + if (isFunction(observerOrOnNextOrOnError)) { + observer.next = observerOrOnNextOrOnError; + if (onError && !isFunction(onError)) { + throw new Error('Query.onSnapshot failed: Third argument must be a valid function.'); + } + observer.error = onError; + // Called with Options, Observer + } else if (observerOrOnNextOrOnError && isObject(observerOrOnNextOrOnError) && observerOrOnNextOrOnError.next) { + if (isFunction(observerOrOnNextOrOnError.next)) { + if (observerOrOnNextOrOnError.error && !isFunction(observerOrOnNextOrOnError.error)) { + throw new Error('Query.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = observerOrOnNextOrOnError; + } else { + throw new Error('Query.onSnapshot failed: Observer.next must be a valid function.'); + } + } else { + throw new Error('Query.onSnapshot failed: Second argument must be a function or observer.'); + } + } else { + throw new Error('Query.onSnapshot failed: First argument must be a function, observer or options.'); + } + } else { + throw new Error('Query.onSnapshot failed: Called with invalid arguments.'); + } + const listenerId = firestoreAutoId(); const listener = (nativeQuerySnapshot) => { const querySnapshot = new QuerySnapshot(this._firestore, this, nativeQuerySnapshot); - onNext(querySnapshot); + observer.next(querySnapshot); }; // Listen to snapshot events @@ -132,10 +192,10 @@ export default class Query { ); // Listen for snapshot error events - if (onError) { + if (observer.error) { this._firestore.on( this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`), - onError, + observer.error, ); } @@ -146,7 +206,8 @@ export default class Query { this._fieldFilters, this._fieldOrders, this._queryOptions, - listenerId + listenerId, + queryListenOptions, ); // Return an unsubscribe method diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index eae603f0..198a5f69 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -129,30 +129,30 @@ PODS: - nanopb/decode (0.3.8) - nanopb/encode (0.3.8) - Protobuf (3.4.0) - - React (0.49.0-rc.6): - - React/Core (= 0.49.0-rc.6) - - React/BatchedBridge (0.49.0-rc.6): + - React (0.49.1): + - React/Core (= 0.49.1) + - React/BatchedBridge (0.49.1): - React/Core - React/cxxreact_legacy - - React/Core (0.49.0-rc.6): - - yoga (= 0.49.0-rc.6.React) - - React/cxxreact_legacy (0.49.0-rc.6): + - React/Core (0.49.1): + - yoga (= 0.49.1.React) + - React/cxxreact_legacy (0.49.1): - React/jschelpers_legacy - - React/fishhook (0.49.0-rc.6) - - React/jschelpers_legacy (0.49.0-rc.6) - - React/RCTBlob (0.49.0-rc.6): + - React/fishhook (0.49.1) + - React/jschelpers_legacy (0.49.1) + - React/RCTBlob (0.49.1): - React/Core - - React/RCTNetwork (0.49.0-rc.6): + - React/RCTNetwork (0.49.1): - React/Core - - React/RCTText (0.49.0-rc.6): + - React/RCTText (0.49.1): - React/Core - - React/RCTWebSocket (0.49.0-rc.6): + - React/RCTWebSocket (0.49.1): - React/Core - React/fishhook - React/RCTBlob - RNFirebase (3.0.0): - React - - yoga (0.49.0-rc.6.React) + - yoga (0.49.1.React) DEPENDENCIES: - Firebase/AdMob @@ -207,9 +207,9 @@ SPEC CHECKSUMS: leveldb-library: 10fb39c39e243db4af1828441162405bbcec1404 nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8 - React: e6ef6a41ec6dd1b7941417d60ca582bf5e9c739d - RNFirebase: 2ceda3aef595ea1379bf5bfcc1cd9ee7d6c05d3b - yoga: f9485d2ebf0ca773db2d727ea71b1aa8c9f3e075 + React: cf892fb84b7d06bf5fea7f328e554c6dcabe85ee + RNFirebase: 901a473c68fcbaa28125c56a911923f2fbe5d61b + yoga: 3abf02d6d9aeeb139b4c930eb1367feae690a35a PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45 diff --git a/tests/src/tests/firestore/collectionReferenceTests.js b/tests/src/tests/firestore/collectionReferenceTests.js index ab2a1f92..1145c03d 100644 --- a/tests/src/tests/firestore/collectionReferenceTests.js +++ b/tests/src/tests/firestore/collectionReferenceTests.js @@ -53,262 +53,360 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); context('onSnapshot()', () => { - it('calls callback with the initial data and then when document changes', () => { - return new Promise(async (resolve) => { - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + it('calls callback with the initial data and then when document changes', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callback(doc.data())); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); }); - - callback.should.be.calledWith(currentDocValue); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(newDocValue); - callback.should.be.calledTwice(); - - // Tear down - - unsubscribe(); - - resolve(); }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { - it('calls callback with the initial data and then when document is added', () => { - return new Promise(async (resolve) => { - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + it('calls callback with the initial data and then when document is added', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callback(doc.data())); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); }); - - callback.should.be.calledWith(currentDocValue); - - const docRef = firebase.native.firestore().doc('document-tests/doc2'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(currentDocValue); - callback.should.be.calledWith(newDocValue); - callback.should.be.calledThrice(); - - // Tear down - - unsubscribe(); - - resolve(); }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc2'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(currentDocValue); + callback.should.be.calledWith(newDocValue); + callback.should.be.calledThrice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { it('doesn\'t call callback when the ref is updated with the same value', async () => { - return new Promise(async (resolve) => { - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = collectionRef.onSnapshot((snapshot) => { + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(currentDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + }); + }); + + context('onSnapshot()', () => { + it('allows binding multiple callbacks to the same ref', async () => { + // Setup + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackA(doc.data())); + resolve2(); + }); + }); + await new Promise((resolve2) => { + unsubscribeB = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackB(doc.data())); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDocValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDocValue); + callbackB.should.be.calledOnce(); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + }); + }); + + context('onSnapshot()', () => { + it('listener stops listening when unsubscribed', async () => { + // Setup + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackA(doc.data())); + resolve2(); + }); + }); + await new Promise((resolve2) => { + unsubscribeB = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackB(doc.data())); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDocValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDocValue); + callbackB.should.be.calledOnce(); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(currentDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackB.should.be.calledWith(currentDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + }); + }); + + context('onSnapshot()', () => { + it('supports options and callback', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot({ includeQueryMetadataChanges: true, includeDocumentMetadataChanges: true }, (snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + + // Tear down + + unsubscribe(); + }); + }); + + context('onSnapshot()', () => { + it('supports observer', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { snapshot.forEach(doc => callback(doc.data())); resolve2(); - }); - }); - - callback.should.be.calledWith(currentDocValue); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(currentDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledOnce(); // Callback is not called again - - // Tear down - - unsubscribe(); - - resolve(); + }, + }; + unsubscribe = collectionRef.onSnapshot(observer); }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { - it('allows binding multiple callbacks to the same ref', () => { - return new Promise(async (resolve) => { - // Setup - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + it('supports options and observer', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const callback = sinon.spy(); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackA(doc.data())); + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { + snapshot.forEach(doc => callback(doc.data())); resolve2(); - }); - }); - await new Promise((resolve2) => { - unsubscribeB = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackB(doc.data())); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDocValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDocValue); - callbackB.should.be.calledOnce(); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDocValue); - callbackB.should.be.calledWith(newDocValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Tear down - - unsubscribeA(); - unsubscribeB(); - - resolve(); + }, + }; + unsubscribe = collectionRef.onSnapshot({ includeQueryMetadataChanges: true, includeDocumentMetadataChanges: true }, observer); }); - }); - }); - context('onSnapshot()', () => { - it('listener stops listening when unsubscribed', () => { - return new Promise(async (resolve) => { - // Setup - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + callback.should.be.calledWith(currentDocValue); - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackA(doc.data())); - resolve2(); - }); - }); - await new Promise((resolve2) => { - unsubscribeB = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackB(doc.data())); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDocValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDocValue); - callbackB.should.be.calledOnce(); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDocValue); - callbackB.should.be.calledWith(newDocValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Unsubscribe A - - unsubscribeA(); - - await docRef.set(currentDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackB.should.be.calledWith(currentDocValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - // Unsubscribe B - - unsubscribeB(); - - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - resolve(); + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + + // Tear down + + unsubscribe(); }); }); diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js index e527c226..846e86a7 100644 --- a/tests/src/tests/firestore/documentReferenceTests.js +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -2,7 +2,7 @@ import sinon from 'sinon'; import 'should-sinon'; import should from 'should'; -function collectionReferenceTests({ describe, it, context, firebase }) { +function documentReferenceTests({ describe, it, context, firebase }) { describe('DocumentReference', () => { context('class', () => { it('should return instance methods', () => { @@ -29,219 +29,324 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); context('onSnapshot()', () => { - it('calls callback with the initial data and then when value changes', () => { - return new Promise(async (resolve) => { - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; - const newDataValue = { name: 'updated' }; + it('calls callback with the initial data and then when value changes', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = docRef.onSnapshot((snapshot) => { - callback(snapshot.data()); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot((snapshot) => { + callback(snapshot.data()); + resolve2(); }); - - callback.should.be.calledWith(currentDataValue); - - // Update the document - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(newDataValue); - callback.should.be.calledTwice(); - - // Tear down - - unsubscribe(); - - resolve(); }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { it('doesn\'t call callback when the ref is updated with the same value', async () => { - return new Promise(async (resolve) => { - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; - const callback = sinon.spy(); + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot((snapshot) => { + callback(snapshot.data()); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDataValue); + + await docRef.set(currentDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + }); + }); + + context('onSnapshot()', () => { + it('allows binding multiple callbacks to the same ref', async () => { + // Setup + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = docRef.onSnapshot((snapshot) => { + callbackA(snapshot.data()); + resolve2(); + }); + }); + + await new Promise((resolve2) => { + unsubscribeB = docRef.onSnapshot((snapshot) => { + callbackB(snapshot.data()); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDataValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDataValue); + callbackB.should.be.calledOnce(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDataValue); + callbackB.should.be.calledWith(newDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + }); + }); + + context('onSnapshot()', () => { + it('listener stops listening when unsubscribed', async () => { + // Setup + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = docRef.onSnapshot((snapshot) => { + callbackA(snapshot.data()); + resolve2(); + }); + }); + + await new Promise((resolve2) => { + unsubscribeB = docRef.onSnapshot((snapshot) => { + callbackB(snapshot.data()); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDataValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDataValue); + callbackB.should.be.calledOnce(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDataValue); + callbackB.should.be.calledWith(newDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(currentDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackB.should.be.calledWith(currentDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + }); + }); + + context('onSnapshot()', () => { + it('supports options and callbacks', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callback = sinon.spy(); // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = docRef.onSnapshot((snapshot) => { - callback(snapshot.data()); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot({ includeMetadataChanges: true }, (snapshot) => { + callback(snapshot.data()); + resolve2(); }); + }); - callback.should.be.calledWith(currentDataValue); + callback.should.be.calledWith(currentDataValue); - await docRef.set(currentDataValue); + // Update the document - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); // Assertions - callback.should.be.calledOnce(); // Callback is not called again + callback.should.be.calledWith(newDataValue); // Tear down - unsubscribe(); - - resolve(); - }); + unsubscribe(); }); }); context('onSnapshot()', () => { - it('allows binding multiple callbacks to the same ref', () => { - return new Promise(async (resolve) => { - // Setup - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; - const newDataValue = { name: 'updated' }; + it('supports observer', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const callback = sinon.spy(); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = docRef.onSnapshot((snapshot) => { - callbackA(snapshot.data()); + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { + callback(snapshot.data()); resolve2(); - }); - }); - - await new Promise((resolve2) => { - unsubscribeB = docRef.onSnapshot((snapshot) => { - callbackB(snapshot.data()); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDataValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDataValue); - callbackB.should.be.calledOnce(); - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDataValue); - callbackB.should.be.calledWith(newDataValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Tear down - - unsubscribeA(); - unsubscribeB(); - - resolve(); + }, + }; + unsubscribe = docRef.onSnapshot(observer); }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { - it('listener stops listening when unsubscribed', () => { - return new Promise(async (resolve) => { - // Setup - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; - const newDataValue = { name: 'updated' }; + it('supports options and observer', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const callback = sinon.spy(); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = docRef.onSnapshot((snapshot) => { - callbackA(snapshot.data()); + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { + callback(snapshot.data()); resolve2(); - }); - }); - - await new Promise((resolve2) => { - unsubscribeB = docRef.onSnapshot((snapshot) => { - callbackB(snapshot.data()); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDataValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDataValue); - callbackB.should.be.calledOnce(); - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDataValue); - callbackB.should.be.calledWith(newDataValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Unsubscribe A - - unsubscribeA(); - - await docRef.set(currentDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackB.should.be.calledWith(currentDataValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - // Unsubscribe B - - unsubscribeB(); - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - resolve(); + }, + }; + unsubscribe = docRef.onSnapshot({ includeMetadataChanges: true }, observer); }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + + // Tear down + + unsubscribe(); }); }); @@ -296,4 +401,4 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); } -export default collectionReferenceTests; +export default documentReferenceTests; From 62ab50ec7716fcbc805029511b7bb80ae36ce046 Mon Sep 17 00:00:00 2001 From: Michele Bombardi Date: Fri, 6 Oct 2017 13:11:32 +0200 Subject: [PATCH 02/21] Missing new app function typings --- index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.d.ts b/index.d.ts index 9f50442f..9272a526 100644 --- a/index.d.ts +++ b/index.d.ts @@ -51,6 +51,8 @@ declare module "react-native-firebase" { static initializeApp(options?: any | RNFirebase.configurationOptions, name?: string): FireBase; + static app(name?: string): FireBase; + [key: string]: any; } From 46136e6c4d19fc772fa34da2c091100366d2ff49 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 6 Oct 2017 12:36:41 +0100 Subject: [PATCH 03/21] [firestore] Support `update` variations on DocumentReference and WriteBatch --- lib/modules/firestore/DocumentReference.js | 22 ++++++++++++++++-- lib/modules/firestore/WriteBatch.js | 23 ++++++++++++++++--- .../tests/firestore/documentReferenceTests.js | 16 +++++++++++-- tests/src/tests/firestore/firestoreTests.js | 2 +- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/lib/modules/firestore/DocumentReference.js b/lib/modules/firestore/DocumentReference.js index 2210bca1..986f6165 100644 --- a/lib/modules/firestore/DocumentReference.js +++ b/lib/modules/firestore/DocumentReference.js @@ -5,7 +5,7 @@ import CollectionReference from './CollectionReference'; import DocumentSnapshot from './DocumentSnapshot'; import Path from './Path'; -import { firestoreAutoId, isFunction, isObject } from '../../utils'; +import { firestoreAutoId, isFunction, isObject, isString } from '../../utils'; export type WriteOptions = { merge?: boolean, @@ -156,7 +156,25 @@ export default class DocumentReference { .documentSet(this.path, data, writeOptions); } - update(data: Object): Promise { + update(...args: Object | string[]): Promise { + let data = {}; + if (args.length === 1) { + if (!isObject(args[0])) { + throw new Error('DocumentReference.update failed: If using a single argument, it must be an object.'); + } + data = args[0]; + } else if (args.length % 2 === 1) { + throw new Error('DocumentReference.update failed: Must have either a single object argument, or equal numbers of key/value pairs.'); + } else { + for (let i = 0; i < args.length; i += 2) { + const key = args[i]; + const value = args[i + 1]; + if (!isString(key)) { + throw new Error(`DocumentReference.update failed: Argument at index ${i} must be a string`); + } + data[key] = value; + } + } return this._firestore._native .documentUpdate(this.path, data); } diff --git a/lib/modules/firestore/WriteBatch.js b/lib/modules/firestore/WriteBatch.js index 7ada92d9..0ed02094 100644 --- a/lib/modules/firestore/WriteBatch.js +++ b/lib/modules/firestore/WriteBatch.js @@ -3,6 +3,7 @@ * WriteBatch representation wrapper */ import DocumentReference from './DocumentReference'; +import { isObject, isString } from '../../utils'; import type { WriteOptions } from './DocumentReference'; @@ -58,11 +59,27 @@ export default class WriteBatch { return this; } - // TODO: Update to new method signature - update(docRef: DocumentReference, data: Object): WriteBatch { + update(docRef: DocumentReference, ...args: Object | string[]): WriteBatch { // TODO: Validation // validate.isDocumentReference('docRef', docRef); - // validate.isDocument('data', data, true); + let data = {}; + if (args.length === 1) { + if (!isObject(args[0])) { + throw new Error('DocumentReference.update failed: If using two arguments, the second must be an object.'); + } + data = args[0]; + } else if (args.length % 2 === 1) { + throw new Error('DocumentReference.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.'); + } else { + for (let i = 0; i < args.length; i += 2) { + const key = args[i]; + const value = args[i + 1]; + if (!isString(key)) { + throw new Error(`DocumentReference.update failed: Argument at index ${i + 1} must be a string`); + } + data[key] = value; + } + } this._writes.push({ data, diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js index 846e86a7..4fbd4ca4 100644 --- a/tests/src/tests/firestore/documentReferenceTests.js +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -388,10 +388,22 @@ function documentReferenceTests({ describe, it, context, firebase }) { }); context('update()', () => { - it('should update Document', () => { + it('should update Document using object', () => { return firebase.native.firestore() .doc('document-tests/doc1') - .set({ name: 'updated' }) + .update({ name: 'updated' }) + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); + doc.data().name.should.equal('updated'); + }); + }); + }); + + context('update()', () => { + it('should update Document using key/value pairs', () => { + return firebase.native.firestore() + .doc('document-tests/doc1') + .update('name', 'updated') .then(async () => { const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); doc.data().name.should.equal('updated'); diff --git a/tests/src/tests/firestore/firestoreTests.js b/tests/src/tests/firestore/firestoreTests.js index 26a29893..29fc9703 100644 --- a/tests/src/tests/firestore/firestoreTests.js +++ b/tests/src/tests/firestore/firestoreTests.js @@ -36,7 +36,7 @@ function firestoreTests({ describe, it, context, firebase }) { .set(nycRef, { name: 'New York City' }) .set(sfRef, { name: 'San Francisco' }) .update(nycRef, { population: 1000000 }) - .update(sfRef, { name: 'San Fran' }) + .update(sfRef, 'name', 'San Fran') .set(lRef, { population: 3000000 }, { merge: true }) .delete(ayRef) .commit() From c4e19c4462cd88003ef4023dd33c4d0b462f4654 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 6 Oct 2017 14:41:13 +0100 Subject: [PATCH 04/21] #451, round 2 --- lib/modules/messaging/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/messaging/index.js b/lib/modules/messaging/index.js index b085428e..c06937bd 100644 --- a/lib/modules/messaging/index.js +++ b/lib/modules/messaging/index.js @@ -25,7 +25,7 @@ const WILL_PRESENT_RESULT = { None: 'UNNotificationPresentationOptionNone', }; -const FirebaseMessaging = NativeModules.FirebaseMessaging; +const FirebaseMessaging = NativeModules.RNFirebaseMessaging; /** * IOS only finish function From 638ee0b0b89ab8f74e510982120185762853e5f2 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 6 Oct 2017 15:53:38 +0100 Subject: [PATCH 05/21] [ios] Add InstanceID and Firestore framework search paths --- ios/RNFirebase.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ios/RNFirebase.xcodeproj/project.pbxproj b/ios/RNFirebase.xcodeproj/project.pbxproj index ed509361..25f52a52 100644 --- a/ios/RNFirebase.xcodeproj/project.pbxproj +++ b/ios/RNFirebase.xcodeproj/project.pbxproj @@ -416,6 +416,8 @@ "${SRCROOT}/../../../ios/Pods/FirebaseCore/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseCrash/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseDatabase/Frameworks", + "${SRCROOT}/../../../ios/Pods/FirebaseFirestore/Frameworks", + "${SRCROOT}/../../../ios/Pods/FirebaseInstanceID/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseMessaging/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebasePerformance/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseRemoteConfig/Frameworks", @@ -452,6 +454,8 @@ "${SRCROOT}/../../../ios/Pods/FirebaseCore/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseCrash/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseDatabase/Frameworks", + "${SRCROOT}/../../../ios/Pods/FirebaseFirestore/Frameworks", + "${SRCROOT}/../../../ios/Pods/FirebaseInstanceID/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseMessaging/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebasePerformance/Frameworks", "${SRCROOT}/../../../ios/Pods/FirebaseRemoteConfig/Frameworks", From 99699e97bd5162fb681c3f25bae6f30c66a75131 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 6 Oct 2017 16:19:37 +0100 Subject: [PATCH 06/21] [android] misc cleanup of redundant code / play services code --- .../invertase/firebase/RNFirebaseModule.java | 61 +++++++++++++------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java b/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java index c069683f..9a778357 100644 --- a/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java +++ b/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java @@ -37,18 +37,6 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life return TAG; } - @ReactMethod - public void promptPlayServices() { - GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); - int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); - - if (status != ConnectionResult.SUCCESS && gapi.isUserResolvableError(status)) { - Activity activity = getCurrentActivity(); - if (activity != null) { - gapi.getErrorDialog(activity, status, 2404).show(); - } - } - } @ReactMethod public void initializeApp(String appName, ReadableMap options, Callback callback) { @@ -84,6 +72,9 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life } } + /** + * @return + */ private WritableMap getPlayServicesStatus() { GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); final int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); @@ -99,18 +90,50 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life return result; } + /** + * Prompt the device user to update play services + */ + @ReactMethod + public void promptPlayServices() { + GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); + int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); + + if (status != ConnectionResult.SUCCESS && gapi.isUserResolvableError(status)) { + Activity activity = getCurrentActivity(); + if (activity != null) { + gapi.getErrorDialog(activity, status, status).show(); + } + } + } + + /** + * Prompt the device user to update play services + */ + @ReactMethod + public void makePlayServicesAvailable() { + GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); + int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); + + if (status != ConnectionResult.SUCCESS) { + Activity activity = getCurrentActivity(); + if (activity != null) { + gapi.makeGooglePlayServicesAvailable(activity); + } + } + } + @Override public void onHostResume() { - WritableMap params = Arguments.createMap(); - params.putBoolean("isForeground", true); - Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params); +// WritableMap params = Arguments.createMap(); +// params.putBoolean("isForeground", true); +// Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params); } @Override public void onHostPause() { - WritableMap params = Arguments.createMap(); - params.putBoolean("isForeground", false); - Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params); +// WritableMap params = Arguments.createMap(); +// params.putBoolean("isForeground", false); +// Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params); } @Override @@ -137,7 +160,7 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life appProps.put("messagingSenderId", appOptions.getGcmSenderId()); appProps.put("projectId", appOptions.getProjectId()); appProps.put("storageBucket", appOptions.getStorageBucket()); - // TODO no way to get client id currently from app options + // TODO no way to get client id currently from app options - firebase sdk issue appMapsList.add(appProps); } From a26b6df623b9582265d449407482216da82e871e Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 6 Oct 2017 21:45:54 +0100 Subject: [PATCH 07/21] [android][firestore] changes in type detection, as suggested by @mirkonasato --- .../firebase/firestore/FirestoreSerialize.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java index 6a0ef635..9df68623 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java +++ b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java @@ -8,7 +8,6 @@ import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.QuerySnapshot; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -155,11 +154,9 @@ public class FirestoreSerialize { writableArray.pushDouble(((Float) item).doubleValue()); } else if (itemClass == String.class) { writableArray.pushString(item.toString()); - } else if (itemClass == Map.class) { + } else if (Map.class.isAssignableFrom(itemClass)) { writableArray.pushMap((objectMapToWritable((Map) item))); - } else if (itemClass == Arrays.class) { - writableArray.pushArray(objectArrayToWritable((Object[]) item)); - } else if (itemClass == List.class || itemClass == ArrayList.class) { + } else if (List.class.isAssignableFrom(itemClass)) { List list = (List) item; Object[] listAsArray = list.toArray(new Object[list.size()]); writableArray.pushArray(objectArrayToWritable(listAsArray)); @@ -194,11 +191,9 @@ public class FirestoreSerialize { map.putDouble(key, ((Float) value).doubleValue()); } else if (valueClass == String.class) { map.putString(key, value.toString()); - } else if (valueClass == Map.class) { + } else if (Map.class.isAssignableFrom(valueClass)) { map.putMap(key, (objectMapToWritable((Map) value))); - } else if (valueClass == Arrays.class) { - map.putArray(key, objectArrayToWritable((Object[]) value)); - } else if (valueClass == List.class || valueClass == ArrayList.class) { + } else if (List.class.isAssignableFrom(valueClass)) { List list = (List) value; Object[] array = list.toArray(new Object[list.size()]); map.putArray(key, objectArrayToWritable(array)); From b88d89b196e450846a19e9058e6391822f2dba46 Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 6 Oct 2017 22:37:27 +0100 Subject: [PATCH 08/21] [android][firestore] log type rather than value on error --- .../io/invertase/firebase/firestore/FirestoreSerialize.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java index 9df68623..6ab033ae 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java +++ b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java @@ -7,7 +7,6 @@ import com.google.firebase.firestore.DocumentChange; import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.QuerySnapshot; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -161,7 +160,7 @@ public class FirestoreSerialize { Object[] listAsArray = list.toArray(new Object[list.size()]); writableArray.pushArray(objectArrayToWritable(listAsArray)); } else { - throw new RuntimeException("Cannot convert object of type " + item); + throw new RuntimeException("Cannot convert object of type " + itemClass); } } @@ -198,7 +197,7 @@ public class FirestoreSerialize { Object[] array = list.toArray(new Object[list.size()]); map.putArray(key, objectArrayToWritable(array)); } else { - throw new RuntimeException("Cannot convert object of type " + value); + throw new RuntimeException("Cannot convert object of type " + valueClass); } } } From a090bd3480e498ef8e90c1a092f134bd098a7053 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 00:24:00 +0100 Subject: [PATCH 09/21] [android][utils] new utils() module - home of all non firebase utilities and library configuration options, including play services availability checks --- .../invertase/firebase/RNFirebaseModule.java | 77 +++++----- lib/firebase-app.js | 2 + lib/firebase.js | 42 +----- lib/modules/utils/index.js | 135 ++++++++++++++++++ 4 files changed, 182 insertions(+), 74 deletions(-) create mode 100644 lib/modules/utils/index.js diff --git a/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java b/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java index 9a778357..9a38c745 100644 --- a/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java +++ b/android/src/main/java/io/invertase/firebase/RNFirebaseModule.java @@ -1,22 +1,23 @@ package io.invertase.firebase; +import android.util.Log; import android.app.Activity; +import android.content.IntentSender; -import java.util.ArrayList; -import java.util.List; import java.util.Map; +import java.util.List; import java.util.HashMap; +import java.util.ArrayList; // react -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; // play services import com.google.android.gms.common.ConnectionResult; @@ -25,7 +26,7 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; @SuppressWarnings("WeakerAccess") -public class RNFirebaseModule extends ReactContextBaseJavaModule implements LifecycleEventListener { +public class RNFirebaseModule extends ReactContextBaseJavaModule { private static final String TAG = "RNFirebase"; public RNFirebaseModule(ReactApplicationContext reactContext) { @@ -42,12 +43,12 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life public void initializeApp(String appName, ReadableMap options, Callback callback) { FirebaseOptions.Builder builder = new FirebaseOptions.Builder(); - builder.setApplicationId(options.getString("appId")); - builder.setGcmSenderId(options.getString("messagingSenderId")); builder.setApiKey(options.getString("apiKey")); + builder.setApplicationId(options.getString("appId")); builder.setProjectId(options.getString("projectId")); builder.setDatabaseUrl(options.getString("databaseURL")); builder.setStorageBucket(options.getString("storageBucket")); + builder.setGcmSenderId(options.getString("messagingSenderId")); // todo firebase sdk has no client id setter FirebaseApp.initializeApp(getReactApplicationContext(), builder.build(), appName); @@ -84,8 +85,9 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life result.putBoolean("isAvailable", true); } else { result.putBoolean("isAvailable", false); - result.putBoolean("isUserResolvableError", gapi.isUserResolvableError(status)); result.putString("error", gapi.getErrorString(status)); + result.putBoolean("isUserResolvableError", gapi.isUserResolvableError(status)); + result.putBoolean("hasResolution", new ConnectionResult(status).hasResolution()); } return result; } @@ -94,7 +96,7 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life * Prompt the device user to update play services */ @ReactMethod - public void promptPlayServices() { + public void promptForPlayServices() { GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); @@ -106,6 +108,27 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life } } + /** + * Prompt the device user to update play services + */ + @ReactMethod + public void resolutionForPlayServices() { + int status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(getReactApplicationContext()); + ConnectionResult connectionResult = new ConnectionResult(status); + + if (!connectionResult.isSuccess() && connectionResult.hasResolution()) { + Activity activity = getCurrentActivity(); + if (activity != null) { + try { + connectionResult.startResolutionForResult(activity, status); + } catch (IntentSender.SendIntentException error) { + Log.d(TAG, "resolutionForPlayServices", error); + } + } + } + } + + /** * Prompt the device user to update play services */ @@ -122,32 +145,16 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life } } - @Override - public void onHostResume() { -// WritableMap params = Arguments.createMap(); -// params.putBoolean("isForeground", true); -// Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params); - } - - @Override - public void onHostPause() { -// WritableMap params = Arguments.createMap(); -// params.putBoolean("isForeground", false); -// Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params); - } - - @Override - public void onHostDestroy() { - - } @Override public Map getConstants() { FirebaseApp firebaseApp; - Map constants = new HashMap<>(); - List firebaseAppList = FirebaseApp.getApps(getReactApplicationContext()); - List> appMapsList = new ArrayList>(); + Map constants = new HashMap<>(); + List> appMapsList = new ArrayList<>(); + List firebaseAppList = FirebaseApp.getApps(getReactApplicationContext()); + + // TODO no way to get client id currently from app options - firebase sdk issue for (FirebaseApp app : firebaseAppList) { String appName = app.getName(); FirebaseOptions appOptions = app.getOptions(); @@ -156,16 +163,16 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life appProps.put("name", appName); appProps.put("apiKey", appOptions.getApiKey()); appProps.put("appId", appOptions.getApplicationId()); + appProps.put("projectId", appOptions.getProjectId()); appProps.put("databaseURL", appOptions.getDatabaseUrl()); appProps.put("messagingSenderId", appOptions.getGcmSenderId()); - appProps.put("projectId", appOptions.getProjectId()); appProps.put("storageBucket", appOptions.getStorageBucket()); - // TODO no way to get client id currently from app options - firebase sdk issue + appMapsList.add(appProps); } constants.put("apps", appMapsList); - constants.put("googleApiAvailability", getPlayServicesStatus()); + constants.put("playServicesAvailability", getPlayServicesStatus()); return constants; } } diff --git a/lib/firebase-app.js b/lib/firebase-app.js index 4f451203..1a4742e9 100644 --- a/lib/firebase-app.js +++ b/lib/firebase-app.js @@ -13,6 +13,7 @@ import Storage, { statics as StorageStatics } from './modules/storage'; import Database, { statics as DatabaseStatics } from './modules/database'; import Messaging, { statics as MessagingStatics } from './modules/messaging'; import Firestore, { statics as FirestoreStatics } from './modules/firestore'; +import Utils, { statics as UtilsStatics } from './modules/utils'; const FirebaseCoreModule = NativeModules.RNFirebase; @@ -37,6 +38,7 @@ export default class FirebaseApp { this.messaging = this._staticsOrModuleInstance(MessagingStatics, Messaging); this.perf = this._staticsOrModuleInstance({}, Performance); this.storage = this._staticsOrModuleInstance(StorageStatics, Storage); + this.utils = this._staticsOrModuleInstance(UtilsStatics, Utils); this._extendedProps = {}; } diff --git a/lib/firebase.js b/lib/firebase.js index c67b4f8f..ee8d3ddb 100644 --- a/lib/firebase.js +++ b/lib/firebase.js @@ -4,11 +4,9 @@ */ import { NativeModules, NativeEventEmitter } from 'react-native'; -import { isObject, isString } from './utils'; - import INTERNALS from './internals'; -import PACKAGE from './../package.json'; import FirebaseApp from './firebase-app'; +import { isObject, isString } from './utils'; // module imports import AdMob, { statics as AdMobStatics } from './modules/admob'; @@ -21,6 +19,7 @@ import Storage, { statics as StorageStatics } from './modules/storage'; import Database, { statics as DatabaseStatics } from './modules/database'; import Messaging, { statics as MessagingStatics } from './modules/messaging'; import Firestore, { statics as FirestoreStatics } from './modules/firestore'; +import Utils, { statics as UtilsStatics } from './modules/utils'; const FirebaseCoreModule = NativeModules.RNFirebase; @@ -52,6 +51,7 @@ class FirebaseCore { this.messaging = this._appNamespaceOrStatics(MessagingStatics, Messaging); this.perf = this._appNamespaceOrStatics(DatabaseStatics, Performance); this.storage = this._appNamespaceOrStatics(StorageStatics, Storage); + this.utils = this._appNamespaceOrStatics(UtilsStatics, Utils); } /** @@ -139,42 +139,6 @@ class FirebaseCore { return Object.values(INTERNALS.APPS); } - /** - * The current RNFirebase SDK version. - */ - get SDK_VERSION() { - return PACKAGE.version; - } - - /** - * The platform specific default app name - */ - get DEFAULT_APP_NAME() { - return INTERNALS.STRINGS.DEFAULT_APP_NAME; - } - - /** - * Returns props from the android GoogleApiAvailability sdk - * @android - * @return {RNFirebase.GoogleApiAvailabilityType|{isAvailable: boolean, status: number}} - */ - get googleApiAvailability(): GoogleApiAvailabilityType { - return FirebaseCoreModule.googleApiAvailability || { isAvailable: true, status: 0 }; - } - - /* - * CONFIG METHODS - */ - /** - * Set the global logging level for all logs. - * - * @param booleanOrDebugString - */ - setLogLevel(booleanOrDebugString) { - INTERNALS.OPTIONS.logLevel = booleanOrDebugString; - Log.setLevel(booleanOrDebugString); - } - /* * INTERNALS */ diff --git a/lib/modules/utils/index.js b/lib/modules/utils/index.js new file mode 100644 index 00000000..333cabd1 --- /dev/null +++ b/lib/modules/utils/index.js @@ -0,0 +1,135 @@ +// @flow +import { NativeModules } from 'react-native'; +import { version as ReactVersion } from 'react'; +import ReactNativeVersion from 'react-native/Libraries/Core/ReactNativeVersion'; + +import INTERNALS from './../../internals'; +import { isIOS } from './../../utils'; +import PACKAGE from './../../../package.json'; + +const FirebaseCoreModule = NativeModules.RNFirebase; + +export default class RNFirebaseUtils { + static _NAMESPACE = 'utils'; + static _NATIVE_DISABLED = true; + static _NATIVE_MODULE = 'RNFirebaseUtils'; + + /** + * + */ + checkPlayServicesAvailability() { + if (isIOS) return null; + if (!this.playServicesAvailability.isAvailable) { + if (INTERNALS.OPTIONS.promptOnMissingPlayServices && this.playServicesAvailability.isUserResolvableError) { + this.promptForPlayServices(); + } else { + const error = INTERNALS.STRINGS.ERROR_PLAY_SERVICES(this.playServicesAvailability.code); + if (INTERNALS.OPTIONS.errorOnMissingPlayServices) { + throw new Error(error); + } else { + console.warn(error); + } + } + } + + return null; + } + + promptForPlayServices() { + if (isIOS) return null; + return FirebaseCoreModule.promptForPlayServices(); + } + + resolutionForPlayServices() { + if (isIOS) return null; + return FirebaseCoreModule.resolutionForPlayServices(); + } + + makePlayServicesAvailable() { + if (isIOS) return null; + return FirebaseCoreModule.makePlayServicesAvailable(); + } + + + get sharedEventEmitter(): Object { + return INTERNALS.SharedEventEmitter; + } + + /** + * Set the global logging level for all logs. + * + * @param booleanOrDebugString + */ + set logLevel(booleanOrDebugString) { + INTERNALS.OPTIONS.logLevel = booleanOrDebugString; + } + + + /** + * Returns an array of all current database registrations id strings + */ + get databaseRegistrations(): Array { + return Object.keys(INTERNALS.SyncTree._reverseLookup); + } + + /** + * Call with a registration id string to get the details off this reg + */ + get getDatabaseRegistrationDetails(): Function { + return INTERNALS.SyncTree.getRegistration.bind(INTERNALS.SyncTree); + } + + /** + * Accepts an array or a single string of registration ids. + * This will remove the refs on both the js and native sides and their listeners. + * @return {function(this:T)} + */ + get removeDatabaseRegistration(): Function { + return INTERNALS.SyncTree.removeListenersForRegistrations.bind(INTERNALS.SyncTree); + } + + /** + * The platform specific default app name + */ + get DEFAULT_APP_NAME() { + return INTERNALS.STRINGS.DEFAULT_APP_NAME; + } + + /** + * Returns props from the android GoogleApiAvailability sdk + * @android + * @return {RNFirebase.GoogleApiAvailabilityType|{isAvailable: boolean, status: number}} + */ + get playServicesAvailability(): GoogleApiAvailabilityType { + return FirebaseCoreModule.playServicesAvailability || { isAvailable: true, status: 0 }; + } + + /** + * Enable/Disable automatic prompting of the play services update dialog + * @android + * @param bool + */ + set errorOnMissingPlayServices(bool: Boolean) { + INTERNALS.OPTIONS.errorOnMissingPlayServices = bool; + } + + /** + * Enable/Disable automatic prompting of the play services update dialog + * @android + * @param bool + */ + set promptOnMissingPlayServices(bool: Boolean) { + INTERNALS.OPTIONS.promptOnMissingPlayServices = bool; + } +} + + +export const statics = { + DEFAULT_APP_NAME: INTERNALS.STRINGS.DEFAULT_APP_NAME, + VERSIONS: { + react: ReactVersion, + 'react-native': Object.values(ReactNativeVersion.version).slice(0, 3).join('.'), + 'react-native-firebase': PACKAGE.version, + }, +}; + From 08fae27f70209cd3049b7312352aa739e4b74809 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 01:31:32 +0100 Subject: [PATCH 10/21] [flow] update GoogleApiAvailabilityType to include 'hasResolution' --- lib/flow.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/flow.js b/lib/flow.js index a96e34b3..f5295758 100644 --- a/lib/flow.js +++ b/lib/flow.js @@ -35,6 +35,7 @@ declare type GoogleApiAvailabilityType = { status: number, isAvailable: boolean, isUserResolvableError?: boolean, + hasResolution?: boolean, error?: string }; From 3d360348d5d346e6e081608be9b3735b635afb61 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 01:49:12 +0100 Subject: [PATCH 11/21] [utils] run play services check automatically after any module usage - once --- lib/firebase.js | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/firebase.js b/lib/firebase.js index ee8d3ddb..2ce9317c 100644 --- a/lib/firebase.js +++ b/lib/firebase.js @@ -6,7 +6,7 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; import INTERNALS from './internals'; import FirebaseApp from './firebase-app'; -import { isObject, isString } from './utils'; +import { isObject, isString, isAndroid } from './utils'; // module imports import AdMob, { statics as AdMobStatics } from './modules/admob'; @@ -27,18 +27,13 @@ class FirebaseCore { constructor() { this._nativeEmitters = {}; this._nativeSubscriptions = {}; + this._checkedPlayServices = false; if (!FirebaseCoreModule) { throw (new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE)); } - for (let i = 0, len = FirebaseCoreModule.apps.length; i < len; i++) { - const app = FirebaseCoreModule.apps[i]; - const options = Object.assign({}, app); - delete options.name; - INTERNALS.APPS[app.name] = new FirebaseApp(app.name, options); - INTERNALS.APPS[app.name]._initializeApp(true); - } + this._initializeNativeApps(); // modules this.admob = this._appNamespaceOrStatics(AdMobStatics, AdMob); @@ -54,6 +49,20 @@ class FirebaseCore { this.utils = this._appNamespaceOrStatics(UtilsStatics, Utils); } + /** + * Bootstraps all native app instances that were discovered on boot + * @private + */ + _initializeNativeApps() { + for (let i = 0, len = FirebaseCoreModule.apps.length; i < len; i++) { + const app = FirebaseCoreModule.apps[i]; + const options = Object.assign({}, app); + delete options.name; + INTERNALS.APPS[app.name] = new FirebaseApp(app.name, options); + INTERNALS.APPS[app.name]._initializeApp(true); + } + } + /** * Web SDK initializeApp * @@ -171,7 +180,6 @@ class FirebaseCore { /** * - * @param namespace * @param statics * @param InstanceClass * @return {function(FirebaseApp=)} @@ -179,8 +187,18 @@ class FirebaseCore { */ _appNamespaceOrStatics(statics = {}, InstanceClass): Function { const namespace = InstanceClass._NAMESPACE; + + // play services checks will run for the first time on any module + // usage - except for the utils module - to allow you to configure + // play services options + if (isAndroid && namespace !== Utils._NAMESPACE && !this._checkedPlayServices) { + this._checkedPlayServices = true; + this.utils().checkPlayServicesAvailability(); + } + const getNamespace = (app?: FirebaseApp) => { let _app = app; + // throw an error if it's not a valid app instance if (_app && !(_app instanceof FirebaseApp)) throw new Error(INTERNALS.STRINGS.ERROR_NOT_APP(namespace)); @@ -193,6 +211,7 @@ class FirebaseCore { Object.assign(getNamespace, statics, { nativeModuleExists: !!NativeModules[InstanceClass._NATIVE_MODULE], }); + return getNamespace; } From 1d2549556b2deaab914d650dfeaa644c2a4a6e48 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 01:53:05 +0100 Subject: [PATCH 12/21] [tests][android] add perf package back into initialized modules --- .../main/java/com/reactnativefirebasedemo/MainApplication.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java b/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java index 4047c71c..a9ec77f6 100644 --- a/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java +++ b/tests/android/app/src/main/java/com/reactnativefirebasedemo/MainApplication.java @@ -12,6 +12,7 @@ import io.invertase.firebase.crash.RNFirebaseCrashPackage; import io.invertase.firebase.database.RNFirebaseDatabasePackage; import io.invertase.firebase.firestore.RNFirebaseFirestorePackage; import io.invertase.firebase.messaging.RNFirebaseMessagingPackage; +import io.invertase.firebase.perf.RNFirebasePerformancePackage; import io.invertase.firebase.storage.RNFirebaseStoragePackage; import com.oblador.vectoricons.VectorIconsPackage; import com.facebook.react.ReactNativeHost; @@ -44,7 +45,7 @@ public class MainApplication extends Application implements ReactApplication { new RNFirebaseDatabasePackage(), new RNFirebaseFirestorePackage(), new RNFirebaseMessagingPackage(), - // new RNFirebasePerformancePackage(), + new RNFirebasePerformancePackage(), new RNFirebaseStoragePackage() ); } From 11da976a0a24c528d9e4de4cd2e1aed0c37b00f2 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:06:29 +0100 Subject: [PATCH 13/21] [internals][utils] added play services red box string creator --- lib/internals.js | 67 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/lib/internals.js b/lib/internals.js index 9d26cce7..159ba007 100644 --- a/lib/internals.js +++ b/lib/internals.js @@ -22,10 +22,43 @@ const GRADLE_DEPS = { admob: 'ads', }; +const PLAY_SERVICES_CODES = { + 1: { + code: 'SERVICE_MISSING', + message: 'Google Play services is missing on this device.', + }, + 2: { + code: 'SERVICE_VERSION_UPDATE_REQUIRED', + message: 'The installed version of Google Play services on this device is out of date.', + }, + 3: { + code: 'SERVICE_DISABLED', + message: 'The installed version of Google Play services has been disabled on this device.', + }, + 9: { + code: 'SERVICE_INVALID', + message: 'The version of the Google Play services installed on this device is not authentic.', + }, + 18: { + code: 'SERVICE_UPDATING', + message: 'Google Play services is currently being updated on this device.', + }, + 19: { + code: 'SERVICE_MISSING_PERMISSION', + message: 'Google Play service doesn\'t have one or more required permissions.', + }, +}; + export default { // default options OPTIONS: { logLevel: 'warn', + errorOnMissingPlayServices: true, + promptOnMissingPlayServices: true, + }, + + FLAGS: { + checkedPlayServices: false, }, // track all initialized firebase apps @@ -141,15 +174,15 @@ export default { /** * @return {string} */ - ERROR_UNSUPPORTED_CLASS_METHOD(classname, method) { - return `${classname}.${method}() is unsupported by the native Firebase SDKs.`; + ERROR_UNSUPPORTED_CLASS_METHOD(className, method) { + return `${className}.${method}() is unsupported by the native Firebase SDKs.`; }, /** * @return {string} */ - ERROR_UNSUPPORTED_CLASS_PROPERTY(classname, property) { - return `${classname}.${property} is unsupported by the native Firebase SDKs.`; + ERROR_UNSUPPORTED_CLASS_PROPERTY(className, property) { + return `${className}.${property} is unsupported by the native Firebase SDKs.`; }, /** @@ -159,6 +192,32 @@ export default { return `firebase.${module._NAMESPACE}().${method}() is unsupported by the native Firebase SDKs.`; }, + + /** + * @return {string} + */ + ERROR_PLAY_SERVICES(statusCode) { + const knownError = PLAY_SERVICES_CODES[statusCode]; + let start = 'Google Play Services is required to run firebase services on android but a valid installation was not found on this device.'; + + if (statusCode === 2) { + start = 'Google Play Services is out of date and may cause some firebase services like authentication to hang when used. It is recommended that you update it.'; + } + + // eslint-disable-next-line prefer-template + return `${start}\r\n\r\n` + + '-------------------------\r\n' + + (knownError ? + `${knownError.code}: ${knownError.message} (code ${statusCode})` : + `A specific play store availability reason reason was not available (unknown code: ${statusCode || null})` + ) + + '\r\n-------------------------' + + '\r\n\r\n' + + 'For more information on how to resolve this issue, configure Play Services checks or for guides on how to validate Play Services on your users devices see the link below:' + + '\r\n\r\nhttp://invertase.link/play-services'; + }, + + DEFAULT_APP_NAME, }, From 0eb38c4593f551a5366b34d06295fe64738a76e5 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:07:00 +0100 Subject: [PATCH 14/21] [tests][core] fixed broken test --- tests/src/tests/core/coreTests.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/tests/core/coreTests.js b/tests/src/tests/core/coreTests.js index 1ab33292..6aa41bb6 100644 --- a/tests/src/tests/core/coreTests.js +++ b/tests/src/tests/core/coreTests.js @@ -48,13 +48,14 @@ function coreTests({ describe, it }) { it('it should provide an array of apps', () => { should.equal(!!RNFirebase.apps.length, true); - should.equal(RNFirebase.apps[0]._name, RNFirebase.DEFAULT_APP_NAME); + should.equal(RNFirebase.apps[0]._name, RNFirebase.utils.DEFAULT_APP_NAME); should.equal(RNFirebase.apps[0].name, '[DEFAULT]'); return Promise.resolve(); }); + // todo move to UTILS module tests it('it should provide the sdk version', () => { - should.equal(!!RNFirebase.SDK_VERSION.length, true); + should.equal(!!RNFirebase.utils.VERSIONS['react-native-firebase'].length, true); return Promise.resolve(); }); From e74b760288f08ee65698e8f0a2de975fedd1417b Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:09:05 +0100 Subject: [PATCH 15/21] [utils] move play services check --- lib/firebase-app.js | 7 ++++++- lib/firebase.js | 9 --------- lib/modules/utils/index.js | 8 ++++++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/firebase-app.js b/lib/firebase-app.js index 1a4742e9..d93985b9 100644 --- a/lib/firebase-app.js +++ b/lib/firebase-app.js @@ -1,7 +1,7 @@ import { NativeModules } from 'react-native'; import INTERNALS from './internals'; -import { isObject } from './utils'; +import { isObject, isAndroid } from './utils'; import AdMob, { statics as AdMobStatics } from './modules/admob'; import Auth, { statics as AuthStatics } from './modules/auth'; @@ -152,6 +152,11 @@ export default class FirebaseApp { const getInstance = () => { const _name = `_${InstanceClass._NAMESPACE}`; + if (isAndroid && InstanceClass._NAMESPACE !== Utils._NAMESPACE && !INTERNALS.FLAGS.checkedPlayServices) { + INTERNALS.FLAGS.checkedPlayServices = true; + this.utils().checkPlayServicesAvailability(); + } + if (!this._namespaces[_name]) { this._namespaces[_name] = new InstanceClass(this); } diff --git a/lib/firebase.js b/lib/firebase.js index 2ce9317c..b841a92b 100644 --- a/lib/firebase.js +++ b/lib/firebase.js @@ -27,7 +27,6 @@ class FirebaseCore { constructor() { this._nativeEmitters = {}; this._nativeSubscriptions = {}; - this._checkedPlayServices = false; if (!FirebaseCoreModule) { throw (new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE)); @@ -188,14 +187,6 @@ class FirebaseCore { _appNamespaceOrStatics(statics = {}, InstanceClass): Function { const namespace = InstanceClass._NAMESPACE; - // play services checks will run for the first time on any module - // usage - except for the utils module - to allow you to configure - // play services options - if (isAndroid && namespace !== Utils._NAMESPACE && !this._checkedPlayServices) { - this._checkedPlayServices = true; - this.utils().checkPlayServicesAvailability(); - } - const getNamespace = (app?: FirebaseApp) => { let _app = app; diff --git a/lib/modules/utils/index.js b/lib/modules/utils/index.js index 333cabd1..57a9cce7 100644 --- a/lib/modules/utils/index.js +++ b/lib/modules/utils/index.js @@ -19,13 +19,17 @@ export default class RNFirebaseUtils { */ checkPlayServicesAvailability() { if (isIOS) return null; + + const code = this.playServicesAvailability.code; + if (!this.playServicesAvailability.isAvailable) { if (INTERNALS.OPTIONS.promptOnMissingPlayServices && this.playServicesAvailability.isUserResolvableError) { this.promptForPlayServices(); } else { - const error = INTERNALS.STRINGS.ERROR_PLAY_SERVICES(this.playServicesAvailability.code); + const error = INTERNALS.STRINGS.ERROR_PLAY_SERVICES(code); if (INTERNALS.OPTIONS.errorOnMissingPlayServices) { - throw new Error(error); + if (code === 2) console.warn(error); // only warn if it exists but may need an update + else throw new Error(error); } else { console.warn(error); } From f0fe05b0c6b4a0005597c172cd0b9dc81629381a Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:15:46 +0100 Subject: [PATCH 16/21] [utils] misc --- lib/modules/utils/index.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/modules/utils/index.js b/lib/modules/utils/index.js index 57a9cce7..29c7aa7e 100644 --- a/lib/modules/utils/index.js +++ b/lib/modules/utils/index.js @@ -54,7 +54,6 @@ export default class RNFirebaseUtils { return FirebaseCoreModule.makePlayServicesAvailable(); } - get sharedEventEmitter(): Object { return INTERNALS.SharedEventEmitter; } @@ -92,13 +91,6 @@ export default class RNFirebaseUtils { return INTERNALS.SyncTree.removeListenersForRegistrations.bind(INTERNALS.SyncTree); } - /** - * The platform specific default app name - */ - get DEFAULT_APP_NAME() { - return INTERNALS.STRINGS.DEFAULT_APP_NAME; - } - /** * Returns props from the android GoogleApiAvailability sdk * @android From fff73f3bcf8f7a7f0d8fd64a035dfd3a01a4c1ac Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:21:13 +0100 Subject: [PATCH 17/21] [docs] misc installation changes from @chrisbianca --- docs/installation-android.md | 39 +++++++++++++-------------- docs/installation-ios.md | 52 ++++++++++++------------------------ 2 files changed, 36 insertions(+), 55 deletions(-) diff --git a/docs/installation-android.md b/docs/installation-android.md index a6747852..08080f83 100644 --- a/docs/installation-android.md +++ b/docs/installation-android.md @@ -1,6 +1,10 @@ # Android Installation -## 1) Setup google-services.json +## 1) Link RNFirebase + +Run `react-native link react-native-firebase` + +## 2) Setup google-services.json Download the `google-services.json` file provided by Firebase in the _Add Firebase to Android_ platform menu in your Firebase configuration console. This file should be downloaded to `YOUR_PROJECT/android/app/google-services.json`. Next you'll have to add the google-services gradle plugin in order to parse it. @@ -23,28 +27,19 @@ In your app build.gradle file, add the gradle plugin at the VERY BOTTOM of the f apply plugin: 'com.google.gms.google-services' ``` -## 2) Link RNFirebase +## 3) Setup Firebase -RNFirebase is split into separate modules to allow you to only include the Firebase functionality that you need in your application. - -First add the project path to `android/settings.gradle`: - -```groovy -include ':react-native-firebase' -project(':react-native-firebase').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-firebase/android') -``` - -Now you need to include RNFirebase and the required Firebase dependencies in our `android/app/build.gradle` so that they are compiled as part of React Native. In the `dependencies` listing, add the appropriate `compile` lines: +Now you need to the required Firebase dependencies in our `android/app/build.gradle` so that they are compiled as part of React Native. In the `dependencies` listing, add the appropriate `compile` lines: ```groovy dependencies { - // RNFirebase required dependencies + // This should be added already compile(project(':react-native-firebase')) { transitive = false } - compile "com.google.firebase:firebase-core:11.4.2" - // If you are receiving Google Play API availability issues, add the following dependency + // RNFirebase required dependencies + compile "com.google.firebase:firebase-core:11.4.2" compile "com.google.android.gms:play-services-base:11.4.2" // RNFirebase optional dependencies @@ -60,7 +55,7 @@ dependencies { } ``` -Google Play services from 11.4.2 onwards require their dependencies to be downloaded from Google's Maven respository so add the +Google Play services from 11.2.0 onwards require their dependencies to be downloaded from Google's Maven respository so add the required reference to the repositories section of the *project* level build.gradle `android/build.gradle` @@ -83,13 +78,17 @@ allprojects { } ``` +## 4) Install RNFirebase modules + +RNFirebase is split into separate modules to allow you to only include the Firebase functionality that you need in your application. + To install `react-native-firebase` in your project, you'll need to import the packages you need from `io.invertase.firebase` in your project's `android/app/src/main/java/com/[app name]/MainApplication.java` and list them as packages for ReactNative in the `getPackages()` function: ```java package com.youcompany.application; // ... // Required package -import io.invertase.firebase.RNFirebasePackage; // <-- Add this line +import io.invertase.firebase.RNFirebasePackage; // <-- This should be added already // Optional packages - add as appropriate import io.invertase.firebase.admob.RNFirebaseAdMobPackage; //Firebase AdMob import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage; // Firebase Analytics @@ -109,7 +108,7 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { return Arrays.asList( new MainReactPackage(), - new RNFirebasePackage(), // <-- Add this line + new RNFirebasePackage(), // <-- This should be added already // Add these packages as appropriate new RNFirebaseAdMobPackage(), new RNFirebaseAnalyticsPackage(), @@ -128,7 +127,7 @@ public class MainApplication extends Application implements ReactApplication { } ``` -## 3) Cloud Messaging (optional) +## 5) Cloud Messaging (optional) If you plan on using [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/), add the following to `android/app/src/main/AndroidManifest.xml`. @@ -179,7 +178,7 @@ If you would like to schedule local notifications then you also need to add the ``` -## 4) Performance Monitoring (optional) +## 6) Performance Monitoring (optional) If you'd like to take advantage of Firebase's [Performance Monitoring](https://firebase.google.com/docs/perf-mon/), the following additions to your project setup are required: diff --git a/docs/installation-ios.md b/docs/installation-ios.md index 6ee7d179..3c3a702c 100644 --- a/docs/installation-ios.md +++ b/docs/installation-ios.md @@ -2,10 +2,14 @@ Please note that there is a known issue when using Cocoapods with the `use_frameworks!` enabled. This is explained [here](https://github.com/invertase/react-native-firebase/issues/252#issuecomment-316340974). Unfortunately we don't currently have a workaround, but are engaging with Firebase directly to try and resolve the problem. -## 1) Setup GoogleService-Info.plist +## 1) Link RNFirebase + +Run `react-native link react-native-firebase` + +## 2) Setup GoogleService-Info.plist Setup the `GoogleService-Info.plist` file by following the instructions and adding it to the root of your project at `ios/[YOUR APP NAME]/GoogleService-Info.plist` [here](https://firebase.google.com/docs/ios/setup#add_firebase_to_your_app). -### 1.1) Initialisation +### 2.1) Initialisation Make sure you've added the following to the top of your `ios/[YOUR APP NAME]]/AppDelegate.m` file: `#import ` @@ -14,11 +18,11 @@ and this to the `didFinishLaunchingWithOptions:(NSDictionary *)launchOptions` me `[FIRApp configure];` -## 2) Setup RNFirebase +## 3) Setup Firebase Pods -Unfortunately, due to the fact that Firebase is much easier to setup using Cocoapods, *we do not recommend* `react-native link` as it is not customisable enough for our needs and we have had numerous problems reported. +Firebase recommends using Cocoapods to install the Firebase SDK. -### 2.0) If you don't already have Cocoapods set up +### 3.0) If you don't already have Cocoapods set up Follow the instructions to install Cocoapods and create your Podfile [here](https://firebase.google.com/docs/ios/setup#add_the_sdk). **NOTE: The Podfile needs to be initialised in the `ios` directory of your project. Make sure to update cocoapods libs first by running `pod update`** @@ -50,18 +54,17 @@ Follow the instructions to install Cocoapods and create your Podfile [here](http - Uncomment the `# platform :ios, '9.0'` line by removing the `#` character - Change the version as required -### 2.1) Check the Podfile platform version +### 3.1) Check the Podfile platform version We recommend using a minimum platform version of at least 9.0 for your application to ensure that the correct version of the Firebase libraries are used. To do this, you need to uncomment or make sure the following line is present at the top of your `Podfile`: `platform :ios, '9.0'` -### 2.2) Add the required pods +### 3.2) Add the required pods Simply add the following to your `Podfile` either at the top level, or within the main project target: ```ruby # Required by RNFirebase pod 'Firebase/Core' -pod 'RNFirebase', :path => '../node_modules/react-native-firebase' # [OPTIONAL PODS] - comment out pods for firebase products you won't be using. pod 'Firebase/AdMob' @@ -75,27 +78,6 @@ pod 'Firebase/RemoteConfig' pod 'Firebase/Storage' ``` -If you do not already have React and Yoga installed as pods, then add Yoga and React to your `Podfile` as follows: - -```ruby -pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga" -pod 'React', :path => '../node_modules/react-native', :subspecs => [ - 'BatchedBridge', # Required For React Native 0.45.0+ - 'Core', - # Add any other subspecs you want to use in your project -] - -#Also add this at the very bottom of your Podfile - -post_install do |installer| - installer.pods_project.targets.each do |target| - if target.name == "React" - target.remove_from_project - end - end -end -``` - Run `pod install`. **NOTE: You need to use the `ios/[YOUR APP NAME].xcworkspace` instead of the `ios/[YOUR APP NAME].xcproj` file from now on.** @@ -106,24 +88,24 @@ Run `pod install`. **Resolution** - Run `npm install --save react-native-firebase` from the root of your project -## 3) Cloud Messaging (optional) +## 4) Cloud Messaging (optional) If you plan on using [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) then, you need to: **NOTE: FCM does not work on the iOS simulator, you must test is using a real device. This is a restriction enforced by Apple for some unknown reason.** -### 3.1) Set up certificates +### 4.1) Set up certificates Follow the instructions at https://firebase.google.com/docs/cloud-messaging/ios/certs -### 3.2) Enable capabilities +### 4.2) Enable capabilities In Xcode, enable the following capabilities: 1) Push Notifications 2) Background modes > Remote notifications -### 3.3) Update `AppDelegate.h` +### 4.3) Update `AppDelegate.h` Add the following import: @@ -133,7 +115,7 @@ Change the interface descriptor to: `@interface AppDelegate : UIResponder ` -### 3.4) Update `AppDelegate.m` +### 4.4) Update `AppDelegate.m` Add the following import: @@ -172,7 +154,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response } ``` -### 3.5) Debugging +### 4.5) Debugging If you're having problems with messages not being received, check out the following blog post for help: From c73cfa9bfacddb89a3a83f172917e4c00656152d Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:23:27 +0100 Subject: [PATCH 18/21] 3.0.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3010ee8a..8865f032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "3.0.0-alpha.5", + "version": "3.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 685b638e..74737936 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "3.0.0", + "version": "3.0.1", "author": "Invertase (http://invertase.io)", "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Messaging (FCM), Remote Config, Storage and Performance.", "main": "index", From 10247e067dfc7d20d9e3bd5c211db4e563d7c252 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 03:53:20 +0100 Subject: [PATCH 19/21] [utils] misc code comment --- lib/modules/utils/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/utils/index.js b/lib/modules/utils/index.js index 29c7aa7e..b3d9787d 100644 --- a/lib/modules/utils/index.js +++ b/lib/modules/utils/index.js @@ -101,7 +101,7 @@ export default class RNFirebaseUtils { } /** - * Enable/Disable automatic prompting of the play services update dialog + * Enable/Disable throwing an error or warning on detecting a play services problem * @android * @param bool */ From 4f5fdda1f0860e82dacba8949e4162ffa8366f29 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 13:14:45 +0100 Subject: [PATCH 20/21] [utils] remove rn48 breaking support issue --- lib/modules/utils/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/modules/utils/index.js b/lib/modules/utils/index.js index b3d9787d..942b6af1 100644 --- a/lib/modules/utils/index.js +++ b/lib/modules/utils/index.js @@ -1,7 +1,7 @@ // @flow import { NativeModules } from 'react-native'; -import { version as ReactVersion } from 'react'; -import ReactNativeVersion from 'react-native/Libraries/Core/ReactNativeVersion'; +// import { version as ReactVersion } from 'react'; +// import ReactNativeVersion from 'react-native/Libraries/Core/ReactNativeVersion'; import INTERNALS from './../../internals'; import { isIOS } from './../../utils'; @@ -122,10 +122,10 @@ export default class RNFirebaseUtils { export const statics = { DEFAULT_APP_NAME: INTERNALS.STRINGS.DEFAULT_APP_NAME, - VERSIONS: { - react: ReactVersion, - 'react-native': Object.values(ReactNativeVersion.version).slice(0, 3).join('.'), - 'react-native-firebase': PACKAGE.version, - }, + // VERSIONS: { + // react: ReactVersion, + // 'react-native': Object.values(ReactNativeVersion.version).slice(0, 3).join('.'), + // 'react-native-firebase': PACKAGE.version, + // }, }; From d93a4a0c4810264c0d6b7c352d1a2ed864309eed Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 7 Oct 2017 13:14:56 +0100 Subject: [PATCH 21/21] 3.0.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8865f032..9cc0f93c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "3.0.1", + "version": "3.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 74737936..4c2da247 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "3.0.1", + "version": "3.0.2", "author": "Invertase (http://invertase.io)", "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Messaging (FCM), Remote Config, Storage and Performance.", "main": "index",