From 89806d97f73ef9aea903c75e23d898bc3fdfff81 Mon Sep 17 00:00:00 2001 From: David Gruseck Date: Mon, 7 May 2018 17:35:10 +0200 Subject: [PATCH 01/35] [Android] Add the ability to remove notifications based on the tag --- .../notifications/DisplayNotificationTask.java | 7 ++++++- .../RNFirebaseNotificationManager.java | 12 +++++++++++- .../notifications/RNFirebaseNotifications.java | 6 ++++++ .../notifications/RNFirebaseNotifications.m | 13 ++++++++++--- lib/index.d.ts | 2 ++ .../notifications/AndroidNotification.js | 17 +++++++++++++++++ lib/modules/notifications/index.js | 15 +++++++++++++++ lib/modules/notifications/types.js | 1 + 8 files changed, 68 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java b/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java index cb2b069f..99aa6e38 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java +++ b/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java @@ -272,13 +272,18 @@ public class DisplayNotificationTask extends AsyncTask { } } + String tag = null; + if (android.containsKey("tag")) { + tag = android.getString("tag"); + } + // Create the notification intent PendingIntent contentIntent = createIntent(intentClass, notification, android.getString("clickAction")); nb = nb.setContentIntent(contentIntent); // Build the notification and send it Notification builtNotification = nb.build(); - notificationManager.notify(notificationId.hashCode(), builtNotification); + notificationManager.notify(tag, notificationId.hashCode(), builtNotification); if (reactContext != null) { Utils.sendEvent(reactContext, "notifications_notification_displayed", Arguments.fromBundle(notification)); diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index fccab1ee..2201129c 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -17,6 +17,7 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.service.notification.StatusBarNotification; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.RemoteInput; @@ -139,7 +140,7 @@ public class RNFirebaseNotificationManager { if (!notification.getBundle("schedule").containsKey("repeated") || !notification.getBundle("schedule").getBoolean("repeated")) { String notificationId = notification.getString("notificationId"); - preferences.edit().remove(notificationId).apply();; + preferences.edit().remove(notificationId).apply(); } if (Utils.isAppInForeground(context)) { @@ -181,6 +182,15 @@ public class RNFirebaseNotificationManager { promise.resolve(null); } + public void removeDeliveredNotificationsByTag(String tag, Promise promise) { + StatusBarNotification[] statusBarNotifications = notificationManager.getActiveNotifications(); + for (StatusBarNotification statusBarNotification : statusBarNotifications) { + if (statusBarNotification.getTag() == tag) { + notificationManager.cancel(statusBarNotification.getTag(), statusBarNotification.getId()); + } + } + promise.resolve(null); + } public void rescheduleNotifications() { ArrayList bundles = getScheduledNotifications(); diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index f8236e23..b096dfa4 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -112,6 +112,11 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen notificationManager.removeDeliveredNotification(notificationId, promise); } + @ReactMethod + public void removeDeliveredNotificationsByTag(String tag, Promise promise) { + notificationManager.removeDeliveredNotificationsByTag(tag, promise); + } + @ReactMethod public void setBadge(int badge, Promise promise) { // Store the badge count for later retrieval @@ -284,6 +289,7 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen } if (notification.getTag() != null) { androidMap.putString("group", notification.getTag()); + androidMap.putString("tag", notification.getTag()); } notificationMap.putMap("android", androidMap); diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index 274f4d7d..c30fbcad 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -343,6 +343,13 @@ RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId resolve(nil); } +RCT_EXPORT_METHOD(removeDeliveredNotificationsByTag:(NSString*) tag + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + // No implementation for ios + resolve(nil); +} + RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { @@ -372,7 +379,7 @@ RCT_EXPORT_METHOD(setBadge:(NSInteger) number resolve(nil); }); } - + RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { jsReady = TRUE; resolve(nil); @@ -487,11 +494,11 @@ RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPro NSString *identifier = a[@"identifier"]; NSURL *url = [NSURL fileURLWithPath:a[@"url"]]; NSMutableDictionary *attachmentOptions = nil; - + if (a[@"options"]) { NSDictionary *options = a[@"options"]; attachmentOptions = [[NSMutableDictionary alloc] init]; - + for (id key in options) { if ([key isEqualToString:@"typeHint"]) { attachmentOptions[UNNotificationAttachmentOptionsTypeHintKey] = options[key]; diff --git a/lib/index.d.ts b/lib/index.d.ts index bdb5eb6e..0af68363 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1181,6 +1181,7 @@ declare module 'react-native-firebase' { showWhen?: boolean; smallIcon?: any; sortKey?: string; + tag?: string; ticker?: string; timeoutAfter?: number; usesChronometer?: boolean; @@ -1233,6 +1234,7 @@ declare module 'react-native-firebase' { setShowWhen(showWhen: boolean): Notification; setSmallIcon(icon: string, level?: number): Notification; setSortKey(sortKey: string): Notification; + setTag(tag: string): Notification; setTicker(ticker: string): Notification; setTimeoutAfter(timeoutAfter: number): Notification; setUsesChronometer(usesChronometer: boolean): Notification; diff --git a/lib/modules/notifications/AndroidNotification.js b/lib/modules/notifications/AndroidNotification.js index 915b112b..0545707a 100644 --- a/lib/modules/notifications/AndroidNotification.js +++ b/lib/modules/notifications/AndroidNotification.js @@ -53,6 +53,7 @@ export default class AndroidNotification { _smallIcon: SmallIcon; _sortKey: string | void; // TODO: style: Style; // Need to figure out if this can work + _tag: string | void; _ticker: string | void; _timeoutAfter: number | void; _usesChronometer: boolean | void; @@ -106,6 +107,7 @@ export default class AndroidNotification { this._showWhen = data.showWhen; this._smallIcon = data.smallIcon; this._sortKey = data.sortKey; + this._tag = data.tag; this._ticker = data.ticker; this._timeoutAfter = data.timeoutAfter; this._usesChronometer = data.usesChronometer; @@ -238,6 +240,10 @@ export default class AndroidNotification { return this._sortKey; } + get tag(): ?string { + return this._tag; + } + get ticker(): ?string { return this._ticker; } @@ -615,6 +621,16 @@ export default class AndroidNotification { return this._notification; } + /** + * + * @param tag + * @returns {Notification} + */ + setTag(tag: string): Notification { + this._tag = tag; + return this._notification; + } + /** * * @param ticker @@ -709,6 +725,7 @@ export default class AndroidNotification { smallIcon: this._smallIcon, sortKey: this._sortKey, // TODO: style: Style, + tag: this._tag, ticker: this._ticker, timeoutAfter: this._timeoutAfter, usesChronometer: this._usesChronometer, diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index b42d3d65..6a6267c6 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -291,6 +291,21 @@ export default class Notifications extends ModuleBase { return getNativeModule(this).removeDeliveredNotification(notificationId); } + /** + * Remove a delivered notifications by tag. + * @param tag + */ + removeDeliveredNotificationsByTag(tag: string): Promise { + if (!tag) { + return Promise.reject( + new Error( + 'Notifications: removeDeliveredNotificationsByTag expects a `tag`' + ) + ); + } + return getNativeModule(this).removeDeliveredNotificationsByTag(tag); + } + /** * Schedule a notification * @param notification diff --git a/lib/modules/notifications/types.js b/lib/modules/notifications/types.js index 780eabab..21364518 100644 --- a/lib/modules/notifications/types.js +++ b/lib/modules/notifications/types.js @@ -170,6 +170,7 @@ export type NativeAndroidNotification = {| smallIcon: SmallIcon, sortKey?: string, // TODO: style: Style, + tag?: string, ticker?: string, timeoutAfter?: number, usesChronometer?: boolean, From 11ccbb42f6c45fcee094d51775873f4bb8b51bde Mon Sep 17 00:00:00 2001 From: Dariusz Luksza Date: Wed, 11 Jul 2018 17:05:27 +0200 Subject: [PATCH 02/35] Honor repeatInterval when rescheduling outdated notiication --- .../RNFirebaseNotificationManager.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index 185dc4d5..a2e96e2d 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -365,11 +365,26 @@ public class RNFirebaseNotificationManager { Calendar currentFireDate = Calendar.getInstance(); currentFireDate.setTimeInMillis(fireDate); - newFireDate.add(Calendar.DATE, 1); + newFireDate.set(Calendar.DATE, currentFireDate.get(Calendar.DATE)); newFireDate.set(Calendar.HOUR_OF_DAY, currentFireDate.get(Calendar.HOUR_OF_DAY)); newFireDate.set(Calendar.MINUTE, currentFireDate.get(Calendar.MINUTE)); newFireDate.set(Calendar.SECOND, currentFireDate.get(Calendar.SECOND)); + switch (schedule.getString("repeatInterval")) { + case "minute": + newFireDate.add(Calendar.MINUTE, 1); + break; + case "hour": + newFireDate.add(Calendar.HOUR, 1); + break; + case "day": + newFireDate.add(Calendar.DATE, 1); + break; + case "week": + newFireDate.add(Calendar.DATE, 7); + break; + } + fireDate = newFireDate.getTimeInMillis(); } From 9144314eac825d92f8fa9b3bff7e4307cd58eeff Mon Sep 17 00:00:00 2001 From: Dariusz Luksza Date: Wed, 11 Jul 2018 17:39:32 +0200 Subject: [PATCH 03/35] Warn about changing notification schedule date --- .../firebase/notifications/RNFirebaseNotificationManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index a2e96e2d..655fd61e 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -361,6 +361,7 @@ public class RNFirebaseNotificationManager { // If fireDate you specify is in the past, the alarm triggers immediately. // So we need to adjust the time for correct operation. if (fireDate < System.currentTimeMillis()) { + Log.w(TAG, "Scheduled notification date is in the past, will adjust it to be in future"); Calendar newFireDate = Calendar.getInstance(); Calendar currentFireDate = Calendar.getInstance(); currentFireDate.setTimeInMillis(fireDate); From c17cc502298548751acea0bc8e80cc68d89b2add Mon Sep 17 00:00:00 2001 From: Dariusz Luksza Date: Wed, 11 Jul 2018 18:11:56 +0200 Subject: [PATCH 04/35] Properly compute new schedule date when old is in past --- .../RNFirebaseNotificationManager.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index 655fd61e..66451d95 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -363,25 +363,28 @@ public class RNFirebaseNotificationManager { if (fireDate < System.currentTimeMillis()) { Log.w(TAG, "Scheduled notification date is in the past, will adjust it to be in future"); Calendar newFireDate = Calendar.getInstance(); - Calendar currentFireDate = Calendar.getInstance(); - currentFireDate.setTimeInMillis(fireDate); + Calendar pastFireDate = Calendar.getInstance(); + pastFireDate.setTimeInMillis(fireDate); - newFireDate.set(Calendar.DATE, currentFireDate.get(Calendar.DATE)); - newFireDate.set(Calendar.HOUR_OF_DAY, currentFireDate.get(Calendar.HOUR_OF_DAY)); - newFireDate.set(Calendar.MINUTE, currentFireDate.get(Calendar.MINUTE)); - newFireDate.set(Calendar.SECOND, currentFireDate.get(Calendar.SECOND)); + newFireDate.set(Calendar.SECOND, pastFireDate.get(Calendar.SECOND)); switch (schedule.getString("repeatInterval")) { case "minute": newFireDate.add(Calendar.MINUTE, 1); break; case "hour": + newFireDate.set(Calendar.MINUTE, pastFireDate.get(Calendar.MINUTE)); newFireDate.add(Calendar.HOUR, 1); break; case "day": + newFireDate.set(Calendar.MINUTE, pastFireDate.get(Calendar.MINUTE)); + newFireDate.set(Calendar.HOUR_OF_DAY, pastFireDate.get(Calendar.HOUR_OF_DAY)); newFireDate.add(Calendar.DATE, 1); break; case "week": + newFireDate.set(Calendar.MINUTE, pastFireDate.get(Calendar.MINUTE)); + newFireDate.set(Calendar.HOUR_OF_DAY, pastFireDate.get(Calendar.HOUR_OF_DAY)); + newFireDate.set(Calendar.DATE, pastFireDate.get(Calendar.DATE)); newFireDate.add(Calendar.DATE, 7); break; } From 54743d945d6fbc1a76550efe792386e225f5d5e6 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 12 Jul 2018 13:28:58 +0100 Subject: [PATCH 05/35] [config][android] Explicitly handle task exception --- .../invertase/firebase/config/RNFirebaseRemoteConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java index c2b4acd6..c9cc6bf4 100644 --- a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java +++ b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java @@ -16,6 +16,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.google.firebase.FirebaseApp; import com.google.firebase.remoteconfig.FirebaseRemoteConfig; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException; import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue; import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; @@ -121,7 +122,11 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule { if (task.isSuccessful()) { promise.resolve("remoteConfigFetchStatusSuccess"); } else { - promise.reject("config/failure", task.getException().getMessage(), task.getException()); + if (task.getException() instanceof FirebaseRemoteConfigFetchThrottledException) { + promise.reject("config/throttled", "fetch() operation cannot be completed successfully, due to throttling.", task.getException()); + } else { + promise.reject("config/failure", "fetch() operation cannot be completed successfully.", task.getException()); + } } } }); From fc39b35e1a4ccd98d63a960ab62697a52c623729 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 12 Jul 2018 13:44:37 +0100 Subject: [PATCH 06/35] [config][android] Handle success value to match ios --- .../io/invertase/firebase/config/RNFirebaseRemoteConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java index c9cc6bf4..465f3508 100644 --- a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java +++ b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java @@ -120,7 +120,7 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule { @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { - promise.resolve("remoteConfigFetchStatusSuccess"); + promise.resolve("config/success"); } else { if (task.getException() instanceof FirebaseRemoteConfigFetchThrottledException) { promise.reject("config/throttled", "fetch() operation cannot be completed successfully, due to throttling.", task.getException()); From c4b43a5184c0d3f495ae6d32cb1b929e05b66bfa Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 12 Jul 2018 14:14:11 +0100 Subject: [PATCH 07/35] [config][ios] Match iOS error messages to Android --- ios/RNFirebase/config/RNFirebaseRemoteConfig.m | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ios/RNFirebase/config/RNFirebaseRemoteConfig.m b/ios/RNFirebase/config/RNFirebaseRemoteConfig.m index fe9c4061..03fd73c7 100644 --- a/ios/RNFirebase/config/RNFirebaseRemoteConfig.m +++ b/ios/RNFirebase/config/RNFirebaseRemoteConfig.m @@ -19,6 +19,16 @@ NSString *convertFIRRemoteConfigFetchStatusToNSString(FIRRemoteConfigFetchStatus } } +NSString *convertFIRRemoteConfigFetchStatusToNSStringDescription(FIRRemoteConfigFetchStatus value) { + switch (value) { + case FIRRemoteConfigFetchStatusThrottled: + return @"fetch() operation cannot be completed successfully, due to throttling."; + case FIRRemoteConfigFetchStatusNoFetchYet: + default: + return @"fetch() operation cannot be completed successfully."; + } +} + NSString *convertFIRRemoteConfigSourceToNSString(FIRRemoteConfigSource value) { switch (value) { case FIRRemoteConfigSourceDefault: @@ -49,7 +59,7 @@ RCT_EXPORT_METHOD(fetch: (RCTPromiseRejectBlock) reject) { [[FIRRemoteConfig remoteConfig] fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) { if (error) { - reject(convertFIRRemoteConfigFetchStatusToNSString(status), error.localizedDescription, error); + reject(convertFIRRemoteConfigFetchStatusToNSString(status), convertFIRRemoteConfigFetchStatusToNSStringDescription(status), error); } else { resolve(convertFIRRemoteConfigFetchStatusToNSString(status)); } @@ -63,7 +73,7 @@ RCT_EXPORT_METHOD(fetchWithExpirationDuration: rejecter:(RCTPromiseRejectBlock)reject) { [[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:expirationDuration.doubleValue completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) { if (error) { - reject(convertFIRRemoteConfigFetchStatusToNSString(status), error.localizedDescription, error); + reject(convertFIRRemoteConfigFetchStatusToNSString(status), convertFIRRemoteConfigFetchStatusToNSStringDescription(status), error); } else { resolve(convertFIRRemoteConfigFetchStatusToNSString(status)); } From 8b81e74f508fc0c4e77b9bec974eff51b64cebc6 Mon Sep 17 00:00:00 2001 From: David Gruseck Date: Fri, 13 Jul 2018 09:22:38 +0200 Subject: [PATCH 08/35] move removeDeliveredNotificationsByTag to android specific file --- .../notifications/RNFirebaseNotifications.m | 7 ------- .../notifications/AndroidNotifications.js | 17 +++++++++++++++++ lib/modules/notifications/index.js | 15 --------------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index c30fbcad..2b8a0553 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -343,13 +343,6 @@ RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId resolve(nil); } -RCT_EXPORT_METHOD(removeDeliveredNotificationsByTag:(NSString*) tag - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - // No implementation for ios - resolve(nil); -} - RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { diff --git a/lib/modules/notifications/AndroidNotifications.js b/lib/modules/notifications/AndroidNotifications.js index bd644a60..f8ce2709 100644 --- a/lib/modules/notifications/AndroidNotifications.js +++ b/lib/modules/notifications/AndroidNotifications.js @@ -91,4 +91,21 @@ export default class AndroidNotifications { } return Promise.resolve(); } + + /** + * Remove a delivered notifications by tag. + * @param tag + */ + removeDeliveredNotificationsByTag(tag: string): Promise { + if (!tag) { + return Promise.reject( + new Error( + 'Notifications: removeDeliveredNotificationsByTag expects a `tag`' + ) + ); + } + return getNativeModule( + this._notifications + ).removeDeliveredNotificationsByTag(tag); + } } diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 6a6267c6..b42d3d65 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -291,21 +291,6 @@ export default class Notifications extends ModuleBase { return getNativeModule(this).removeDeliveredNotification(notificationId); } - /** - * Remove a delivered notifications by tag. - * @param tag - */ - removeDeliveredNotificationsByTag(tag: string): Promise { - if (!tag) { - return Promise.reject( - new Error( - 'Notifications: removeDeliveredNotificationsByTag expects a `tag`' - ) - ); - } - return getNativeModule(this).removeDeliveredNotificationsByTag(tag); - } - /** * Schedule a notification * @param notification From aa12de324881fa778aad3416605a44e68b02a11c Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 15 Jul 2018 00:43:12 +0100 Subject: [PATCH 09/35] [js][iid] getToken & deleteToken now have option args with defaults that use `app.options.messagingSenderId` as authorizedEntity and '*' as scope --- lib/modules/iid/index.js | 49 ++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/modules/iid/index.js b/lib/modules/iid/index.js index 4ede6748..773fb033 100644 --- a/lib/modules/iid/index.js +++ b/lib/modules/iid/index.js @@ -7,8 +7,8 @@ import { getNativeModule } from '../../utils/native'; import type App from '../core/app'; -export const MODULE_NAME = 'RNFirebaseInstanceId'; export const NAMESPACE = 'iid'; +export const MODULE_NAME = 'RNFirebaseInstanceId'; export default class InstanceId extends ModuleBase { constructor(app: App) { @@ -20,20 +20,51 @@ export default class InstanceId extends ModuleBase { }); } - delete(): Promise { - return getNativeModule(this).delete(); - } - + /** + * Get the current Instance ID. + * + * @returns {*} + */ get(): Promise { return getNativeModule(this).get(); } - getToken(authorizedEntity: string, scope: string): Promise { - return getNativeModule(this).getToken(authorizedEntity, scope); + /** + * Delete the current Instance ID. + * + * @returns {*} + */ + delete(): Promise { + return getNativeModule(this).delete(); } - deleteToken(authorizedEntity: string, scope: string): Promise { - return getNativeModule(this).deleteToken(authorizedEntity, scope); + /** + * Get a token that authorizes an Entity to perform an action on behalf + * of the application identified by Instance ID. + * + * @param authorizedEntity + * @param scope + * @returns {Promise} + */ + getToken(authorizedEntity?: string, scope?: string): Promise { + return getNativeModule(this).getToken( + authorizedEntity || this.app.options.messagingSenderId, + scope || '*' + ); + } + + /** + * Revokes access to a scope (action) for an entity previously authorized by getToken(). + * + * @param authorizedEntity + * @param scope + * @returns {Promise} + */ + deleteToken(authorizedEntity?: string, scope?: string): Promise { + return getNativeModule(this).deleteToken( + authorizedEntity || this.app.options.messagingSenderId, + scope || '*' + ); } } From 6a7d3ddc7a3c768368704a0fd7c67bb2f43680ef Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 15 Jul 2018 00:44:20 +0100 Subject: [PATCH 10/35] [types][iid] getToken & deleteToken now have option args with defaults that use `app.options.messagingSenderId` as authorizedEntity and '*' as scope --- lib/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 45c5d54b..65bf3579 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1089,8 +1089,8 @@ declare module 'react-native-firebase' { interface InstanceId { delete(): Promise; get(): Promise; - getToken(authorizedEntity: string, scope: string): Promise; - deleteToken(authorizedEntity: string, scope: string): Promise; + getToken(authorizedEntity?: string, scope?: string): Promise; + deleteToken(authorizedEntity?: string, scope?: string): Promise; } } From 8aac77b3ace533676b70f315a902f3dde7608fa0 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 15 Jul 2018 00:45:04 +0100 Subject: [PATCH 11/35] [tests][iid] more coverage including change: getToken & deleteToken now have option args with defaults that use `app.options.messagingSenderId` as authorizedEntity and '*' as scope --- bridge/e2e/iid/iid.e2e.js | 49 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/bridge/e2e/iid/iid.e2e.js b/bridge/e2e/iid/iid.e2e.js index 1c249e5a..99bbd9af 100644 --- a/bridge/e2e/iid/iid.e2e.js +++ b/bridge/e2e/iid/iid.e2e.js @@ -1,4 +1,4 @@ -describe('iid()', () => { +describe.only('iid()', () => { describe('get()', () => { it('returns instance id string', async () => { const iid = await firebase.iid().get(); @@ -10,13 +10,56 @@ describe('iid()', () => { it('deletes the current instance id', async () => { const iidBefore = await firebase.iid().get(); iidBefore.should.be.a.String(); - await firebase.iid().delete(); const iidAfter = await firebase.iid().get(); iidAfter.should.be.a.String(); - iidBefore.should.not.equal(iidAfter); }); }); + + describe('getToken()', () => { + it('should return an FCM token from getToken with arguments', async () => { + const authorizedEntity = firebase.iid().app.options.messagingSenderId; + + await firebase.iid().delete(); + const token = await firebase.iid().getToken(authorizedEntity, '*'); + token.should.be.a.String(); + }); + + it('should return an FCM token from getToken without arguments', async () => { + await firebase.iid().delete(); + const token = await firebase.iid().getToken(); + token.should.be.a.String(); + }); + + it('should return an FCM token from getToken with 1 argument', async () => { + const authorizedEntity = firebase.iid().app.options.messagingSenderId; + + await firebase.iid().delete(); + const token = await firebase.iid().getToken(authorizedEntity); + token.should.be.a.String(); + }); + }); + + describe('deleteToken()', () => { + it('should return nil from deleteToken with arguments', async () => { + const authorizedEntity = firebase.iid().app.options.messagingSenderId; + + const token = await firebase.iid().deleteToken(authorizedEntity, '*'); + should.not.exist(token); + }); + + it('should return nil from deleteToken without arguments', async () => { + const token = await firebase.iid().deleteToken(); + should.not.exist(token); + }); + + it('should return nil from deleteToken with 1 argument', async () => { + const authorizedEntity = firebase.iid().app.options.messagingSenderId; + + const token = await firebase.iid().deleteToken(authorizedEntity); + should.not.exist(token); + }); + }); }); From 7a66c165ed1bbf12329029453d59a4a18d32cca5 Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 15 Jul 2018 00:47:00 +0100 Subject: [PATCH 12/35] [tests][iid] remove test focus --- bridge/e2e/iid/iid.e2e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/e2e/iid/iid.e2e.js b/bridge/e2e/iid/iid.e2e.js index 99bbd9af..18f3a07f 100644 --- a/bridge/e2e/iid/iid.e2e.js +++ b/bridge/e2e/iid/iid.e2e.js @@ -1,4 +1,4 @@ -describe.only('iid()', () => { +describe('iid()', () => { describe('get()', () => { it('returns instance id string', async () => { const iid = await firebase.iid().get(); From bf1b3317f028c876c4dbd71fb62defae87963546 Mon Sep 17 00:00:00 2001 From: Damian Skrodzki Date: Wed, 18 Jul 2018 00:39:03 +0200 Subject: [PATCH 13/35] #1308 Fix rescheduling notification on phone reboot --- .../RNFirebaseNotificationManager.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java index 185dc4d5..3893d29c 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotificationManager.java @@ -320,15 +320,19 @@ public class RNFirebaseNotificationManager { String notificationId = notification.getString("notificationId"); Bundle schedule = notification.getBundle("schedule"); - // fireDate is stored in the Bundle as Long after notifications are rescheduled. - // This would lead to a fireDate of 0.0 when trying to extract a Double from the bundle. - // Instead always try extract a Long + // fireDate may be stored in the Bundle as 2 different types that we need to handle: + // 1. Double - when a call comes directly from React + // 2. Long - when notifications are rescheduled from boot service (Bundle is loaded from prefences). + // At the end we need Long value (timestamp) for the scheduler Long fireDate = -1L; - try { - fireDate = (long) schedule.getDouble("fireDate", -1); - } catch (ClassCastException e) { - fireDate = schedule.getLong("fireDate", -1); + Object fireDateObject = schedule.get("fireDate"); + if (fireDateObject instanceof Long) { + fireDate = (Long) fireDateObject; + } else if (fireDateObject instanceof Double) { + Double fireDateDouble = (Double) fireDateObject; + fireDate = fireDateDouble.longValue(); } + if (fireDate == -1) { if (promise == null) { Log.e(TAG, "Missing schedule information"); @@ -401,14 +405,14 @@ public class RNFirebaseNotificationManager { return; } - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate.longValue(), interval, pendingIntent); + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent); } else { if (schedule.containsKey("exact") && schedule.getBoolean("exact") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate.longValue(), pendingIntent); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate.longValue(), pendingIntent); + alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); } } From 0a1ab7b8dde15048453b0a6228209be2737fe3ff Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 18 Jul 2018 21:35:57 +0100 Subject: [PATCH 14/35] [config] Return null/nil values on success --- .../io/invertase/firebase/config/RNFirebaseRemoteConfig.java | 3 +-- ios/RNFirebase/config/RNFirebaseRemoteConfig.m | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java index 465f3508..4e1c5c09 100644 --- a/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java +++ b/android/src/main/java/io/invertase/firebase/config/RNFirebaseRemoteConfig.java @@ -120,7 +120,7 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule { @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { - promise.resolve("config/success"); + promise.resolve(null); } else { if (task.getException() instanceof FirebaseRemoteConfigFetchThrottledException) { promise.reject("config/throttled", "fetch() operation cannot be completed successfully, due to throttling.", task.getException()); @@ -159,7 +159,6 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule { map.putNull(NUMBER_VALUE); } - // TODO check with ios switch (value.getSource()) { case FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT: map.putString(SOURCE, "default"); diff --git a/ios/RNFirebase/config/RNFirebaseRemoteConfig.m b/ios/RNFirebase/config/RNFirebaseRemoteConfig.m index 03fd73c7..ecb7a849 100644 --- a/ios/RNFirebase/config/RNFirebaseRemoteConfig.m +++ b/ios/RNFirebase/config/RNFirebaseRemoteConfig.m @@ -61,7 +61,7 @@ RCT_EXPORT_METHOD(fetch: if (error) { reject(convertFIRRemoteConfigFetchStatusToNSString(status), convertFIRRemoteConfigFetchStatusToNSStringDescription(status), error); } else { - resolve(convertFIRRemoteConfigFetchStatusToNSString(status)); + resolve(nil); } }]; } @@ -75,7 +75,7 @@ RCT_EXPORT_METHOD(fetchWithExpirationDuration: if (error) { reject(convertFIRRemoteConfigFetchStatusToNSString(status), convertFIRRemoteConfigFetchStatusToNSStringDescription(status), error); } else { - resolve(convertFIRRemoteConfigFetchStatusToNSString(status)); + resolve(nil); } }]; } From 50e5a137b4af63e0e4d02ff2a2bf3b292855678d Mon Sep 17 00:00:00 2001 From: aMarCruz Date: Thu, 19 Jul 2018 18:56:43 -0500 Subject: [PATCH 15/35] Fix "Failed to resolve" errors in Android Studio 3.3 --- android/build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/android/build.gradle b/android/build.gradle index acb6ecc1..15dd4337 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -76,6 +76,15 @@ rootProject.gradle.buildFinished { buildResult -> } } +repositories { + google() + jcenter() + maven { + url "$rootDir/../node_modules/react-native/android" + name 'React Native (local)' + } +} + def supportVersion = rootProject.hasProperty('supportLibVersion') ? rootProject.supportLibVersion : DEFAULT_SUPPORT_LIB_VERSION dependencies { From 66aa5ccb67356bc22ef27507c93be737d1871b4b Mon Sep 17 00:00:00 2001 From: Salakar Date: Fri, 20 Jul 2018 16:28:07 +0100 Subject: [PATCH 16/35] [ios][messaging] fix #1286 - 'getToken resolves to null if called too early' --- ios/RNFirebase/messaging/RNFirebaseMessaging.m | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ios/RNFirebase/messaging/RNFirebaseMessaging.m b/ios/RNFirebase/messaging/RNFirebaseMessaging.m index bc9caa57..704875a0 100644 --- a/ios/RNFirebase/messaging/RNFirebaseMessaging.m +++ b/ios/RNFirebase/messaging/RNFirebaseMessaging.m @@ -108,7 +108,22 @@ didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage { // ** Start React Module methods ** RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - resolve([[FIRInstanceID instanceID] token]); + if (initialToken) { + resolve(initialToken); + } else if ([[FIRInstanceID instanceID] token]) { + resolve([[FIRInstanceID instanceID] token]); + } else { + NSString * senderId = [[FIRApp defaultApp] options].GCMSenderID; + [[FIRMessaging messaging] retrieveFCMTokenForSenderID:senderId completion:^(NSString * _Nullable FCMToken, NSError * _Nullable error) { + if (error) { + reject(@"messaging/fcm-token-error", @"Failed to retrieve FCM token.", error); + } else if (FCMToken) { + resolve(FCMToken); + } else { + resolve([NSNull null]); + } + }]; + } } RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { From 26338d8d6286bc83b597c6de2c25f6b904b68eec Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 21 Jul 2018 19:07:38 +0100 Subject: [PATCH 17/35] [android][database] implement DataSnapshotToMapAsyncTask to address performance issue - #1284 --- .../database/RNFirebaseDatabaseReference.java | 108 ++++++++++++++---- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java index 3d99b522..947992c8 100644 --- a/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java +++ b/android/src/main/java/io/invertase/firebase/database/RNFirebaseDatabaseReference.java @@ -3,8 +3,12 @@ package io.invertase.firebase.database; import java.util.Map; import java.util.List; import java.util.HashMap; +import java.lang.ref.WeakReference; import android.util.Log; +import android.os.AsyncTask; +import android.annotation.SuppressLint; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.facebook.react.bridge.Promise; @@ -33,6 +37,46 @@ class RNFirebaseDatabaseReference { private HashMap childEventListeners = new HashMap<>(); private HashMap valueEventListeners = new HashMap<>(); + /** + * AsyncTask to convert DataSnapshot instances to WritableMap instances. + * + * Introduced due to https://github.com/invertase/react-native-firebase/issues/1284 + */ + private static class DataSnapshotToMapAsyncTask extends AsyncTask { + + private WeakReference reactContextWeakReference; + private WeakReference referenceWeakReference; + + DataSnapshotToMapAsyncTask(ReactContext context, RNFirebaseDatabaseReference reference) { + referenceWeakReference = new WeakReference<>(reference); + reactContextWeakReference = new WeakReference<>(context); + } + + @Override + protected final WritableMap doInBackground(Object... params) { + DataSnapshot dataSnapshot = (DataSnapshot) params[0]; + @Nullable String previousChildName = (String) params[1]; + + try { + return RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, previousChildName); + } catch (RuntimeException e) { + if (isAvailable()) { + reactContextWeakReference.get().handleException(e); + } + throw e; + } + } + + @Override + protected void onPostExecute(WritableMap writableMap) { + // do nothing as overridden on usage + } + + Boolean isAvailable() { + return reactContextWeakReference.get() != null && referenceWeakReference.get() != null; + } + } + /** * RNFirebase wrapper around FirebaseDatabaseReference, * handles Query generation and event listeners. @@ -130,15 +174,22 @@ class RNFirebaseDatabaseReference { * @param promise */ private void addOnceValueEventListener(final Promise promise) { + @SuppressLint("StaticFieldLeak") + final DataSnapshotToMapAsyncTask asyncTask = new DataSnapshotToMapAsyncTask(reactContext, this) { + @Override + protected void onPostExecute(WritableMap writableMap) { + if (this.isAvailable()) promise.resolve(writableMap); + } + }; + ValueEventListener onceValueEventListener = new ValueEventListener() { @Override - public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, null); - promise.resolve(data); + public void onDataChange(@NonNull DataSnapshot dataSnapshot) { + asyncTask.execute(dataSnapshot, null); } @Override - public void onCancelled(DatabaseError error) { + public void onCancelled(@NonNull DatabaseError error) { RNFirebaseDatabase.handlePromise(promise, error); } }; @@ -156,7 +207,7 @@ class RNFirebaseDatabaseReference { private void addChildOnceEventListener(final String eventName, final Promise promise) { ChildEventListener childEventListener = new ChildEventListener() { @Override - public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + public void onChildAdded(@NonNull DataSnapshot dataSnapshot, String previousChildName) { if ("child_added".equals(eventName)) { query.removeEventListener(this); WritableMap data = RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, previousChildName); @@ -165,7 +216,7 @@ class RNFirebaseDatabaseReference { } @Override - public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { + public void onChildChanged(@NonNull DataSnapshot dataSnapshot, String previousChildName) { if ("child_changed".equals(eventName)) { query.removeEventListener(this); WritableMap data = RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, previousChildName); @@ -174,7 +225,7 @@ class RNFirebaseDatabaseReference { } @Override - public void onChildRemoved(DataSnapshot dataSnapshot) { + public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) { if ("child_removed".equals(eventName)) { query.removeEventListener(this); WritableMap data = RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, null); @@ -183,7 +234,7 @@ class RNFirebaseDatabaseReference { } @Override - public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { + public void onChildMoved(@NonNull DataSnapshot dataSnapshot, String previousChildName) { if ("child_moved".equals(eventName)) { query.removeEventListener(this); WritableMap data = RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, previousChildName); @@ -192,7 +243,7 @@ class RNFirebaseDatabaseReference { } @Override - public void onCancelled(DatabaseError error) { + public void onCancelled(@NonNull DatabaseError error) { query.removeEventListener(this); RNFirebaseDatabase.handlePromise(promise, error); } @@ -243,35 +294,35 @@ class RNFirebaseDatabaseReference { if (!hasEventListener(eventRegistrationKey)) { ChildEventListener childEventListener = new ChildEventListener() { @Override - public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + public void onChildAdded(@NonNull DataSnapshot dataSnapshot, String previousChildName) { if ("child_added".equals(eventType)) { handleDatabaseEvent("child_added", registration, dataSnapshot, previousChildName); } } @Override - public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { + public void onChildChanged(@NonNull DataSnapshot dataSnapshot, String previousChildName) { if ("child_changed".equals(eventType)) { handleDatabaseEvent("child_changed", registration, dataSnapshot, previousChildName); } } @Override - public void onChildRemoved(DataSnapshot dataSnapshot) { + public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) { if ("child_removed".equals(eventType)) { handleDatabaseEvent("child_removed", registration, dataSnapshot, null); } } @Override - public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { + public void onChildMoved(@NonNull DataSnapshot dataSnapshot, String previousChildName) { if ("child_moved".equals(eventType)) { handleDatabaseEvent("child_moved", registration, dataSnapshot, previousChildName); } } @Override - public void onCancelled(DatabaseError error) { + public void onCancelled(@NonNull DatabaseError error) { removeEventListener(eventRegistrationKey); handleDatabaseError(registration, error); } @@ -292,12 +343,12 @@ class RNFirebaseDatabaseReference { if (!hasEventListener(eventRegistrationKey)) { ValueEventListener valueEventListener = new ValueEventListener() { @Override - public void onDataChange(DataSnapshot dataSnapshot) { + public void onDataChange(@NonNull DataSnapshot dataSnapshot) { handleDatabaseEvent("value", registration, dataSnapshot, null); } @Override - public void onCancelled(DatabaseError error) { + public void onCancelled(@NonNull DatabaseError error) { removeEventListener(eventRegistrationKey); handleDatabaseError(registration, error); } @@ -314,16 +365,23 @@ class RNFirebaseDatabaseReference { * @param dataSnapshot * @param previousChildName */ - private void handleDatabaseEvent(String eventType, ReadableMap registration, DataSnapshot dataSnapshot, @Nullable String previousChildName) { - WritableMap event = Arguments.createMap(); - WritableMap data = RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, previousChildName); + private void handleDatabaseEvent(final String eventType, final ReadableMap registration, DataSnapshot dataSnapshot, @Nullable String previousChildName) { + @SuppressLint("StaticFieldLeak") + DataSnapshotToMapAsyncTask asyncTask = new DataSnapshotToMapAsyncTask(reactContext, this) { + @Override + protected void onPostExecute(WritableMap data) { + if (this.isAvailable()) { + WritableMap event = Arguments.createMap(); + event.putMap("data", data); + event.putString("key", key); + event.putString("eventType", eventType); + event.putMap("registration", Utils.readableMapToWritableMap(registration)); + Utils.sendEvent(reactContext, "database_sync_event", event); + } + } + }; - event.putMap("data", data); - event.putString("key", key); - event.putString("eventType", eventType); - event.putMap("registration", Utils.readableMapToWritableMap(registration)); - - Utils.sendEvent(reactContext, "database_sync_event", event); + asyncTask.execute(dataSnapshot, previousChildName); } /** From f17225d10117351ada1986540c861195c0ca35ea Mon Sep 17 00:00:00 2001 From: Salakar Date: Sat, 21 Jul 2018 20:31:32 +0100 Subject: [PATCH 18/35] [ios][config] resolve correct null values --- ios/RNFirebase/config/RNFirebaseRemoteConfig.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RNFirebase/config/RNFirebaseRemoteConfig.m b/ios/RNFirebase/config/RNFirebaseRemoteConfig.m index ecb7a849..59f8d17e 100644 --- a/ios/RNFirebase/config/RNFirebaseRemoteConfig.m +++ b/ios/RNFirebase/config/RNFirebaseRemoteConfig.m @@ -61,7 +61,7 @@ RCT_EXPORT_METHOD(fetch: if (error) { reject(convertFIRRemoteConfigFetchStatusToNSString(status), convertFIRRemoteConfigFetchStatusToNSStringDescription(status), error); } else { - resolve(nil); + resolve([NSNull null]); } }]; } @@ -75,7 +75,7 @@ RCT_EXPORT_METHOD(fetchWithExpirationDuration: if (error) { reject(convertFIRRemoteConfigFetchStatusToNSString(status), convertFIRRemoteConfigFetchStatusToNSStringDescription(status), error); } else { - resolve(nil); + resolve([NSNull null]); } }]; } From 109f8309cfcf708b21c964e797cdb1635478a090 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 24 Jul 2018 15:17:27 +0200 Subject: [PATCH 19/35] use localized title and body unless normal title and body are sent remove unused import --- .../java/io/invertase/firebase/Utils.java | 4 ++ .../RNFirebaseNotifications.java | 41 +++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index 6e84a96f..293f0f7c 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -156,4 +156,8 @@ public class Utils { return false; } + + public static int getResId(Context ctx, String resName) { + return ctx.getResources().getIdentifier(resName, "string", ctx.getPackageName()); + } } diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index 3e9704ce..cd0cdca1 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.RemoteInput; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; @@ -30,6 +31,8 @@ import io.invertase.firebase.Utils; import io.invertase.firebase.messaging.RNFirebaseMessagingService; import me.leolin.shortcutbadger.ShortcutBadger; +import static io.invertase.firebase.Utils.getResId; + public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements ActivityEventListener { private static final String BADGE_FILE = "BadgeCountFile"; private static final String BADGE_KEY = "BadgeCount"; @@ -269,7 +272,10 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen WritableMap dataMap = Arguments.createMap(); // Cross platform notification properties - notificationMap.putString("body", notification.getBody()); + String body = getNotificationBody(notification); + if (body != null) { + notificationMap.putString("body", body); + } if (message.getData() != null) { for (Map.Entry e : message.getData().entrySet()) { dataMap.putString(e.getKey(), e.getValue()); @@ -282,8 +288,9 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen if (notification.getSound() != null) { notificationMap.putString("sound", notification.getSound()); } - if (notification.getTitle() != null) { - notificationMap.putString("title", notification.getTitle()); + String title = getNotificationTitle(notification); + if (title != null) { + notificationMap.putString("title", title); } // Android specific notification properties @@ -308,6 +315,34 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen return notificationMap; } + private @Nullable String getNotificationTitle(RemoteMessage.Notification notification) { + String title, titleLocKey; + if ((title = notification.getTitle()) != null) { + return title; + } else if ((titleLocKey = notification.getTitleLocalizationKey()) != null) { + String[] titleLocArgs = notification.getTitleLocalizationArgs(); + Context ctx = getReactApplicationContext(); + int resId = getResId(ctx, titleLocKey); + return ctx.getResources().getString(resId, titleLocArgs); + } else { + return null; + } + } + + private @Nullable String getNotificationBody(RemoteMessage.Notification notification) { + String body = notification.getBody(), bodyLocKey = notification.getBodyLocalizationKey(); + if (body != null) { + return body; + } else if (bodyLocKey != null) { + String[] bodyLocArgs = notification.getBodyLocalizationArgs(); + Context ctx = getReactApplicationContext(); + int resId = getResId(ctx, bodyLocKey); + return ctx.getResources().getString(resId, bodyLocArgs); + } else { + return null; + } + } + private class RemoteNotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { From 28dfbdf253359d3ebba3b7f8d477f4fa91abc3b8 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 24 Jul 2018 16:01:16 +0200 Subject: [PATCH 20/35] cast to (Object[]) --- .../firebase/notifications/RNFirebaseNotifications.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index cd0cdca1..9555e48c 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -323,7 +323,7 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen String[] titleLocArgs = notification.getTitleLocalizationArgs(); Context ctx = getReactApplicationContext(); int resId = getResId(ctx, titleLocKey); - return ctx.getResources().getString(resId, titleLocArgs); + return ctx.getResources().getString(resId, (Object[]) titleLocArgs); } else { return null; } @@ -337,7 +337,7 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen String[] bodyLocArgs = notification.getBodyLocalizationArgs(); Context ctx = getReactApplicationContext(); int resId = getResId(ctx, bodyLocKey); - return ctx.getResources().getString(resId, bodyLocArgs); + return ctx.getResources().getString(resId, (Object[]) bodyLocArgs); } else { return null; } From deaf79427181d6a44b26255eb11fcbd050120d3c Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 24 Jul 2018 17:09:44 +0200 Subject: [PATCH 21/35] prefer localized fields to the regular ones --- .../notifications/RNFirebaseNotifications.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index 9555e48c..2672c8dc 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -316,28 +316,30 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen } private @Nullable String getNotificationTitle(RemoteMessage.Notification notification) { - String title, titleLocKey; - if ((title = notification.getTitle()) != null) { - return title; - } else if ((titleLocKey = notification.getTitleLocalizationKey()) != null) { + String title = notification.getTitle(); + String titleLocKey = notification.getTitleLocalizationKey(); + if (titleLocKey != null) { String[] titleLocArgs = notification.getTitleLocalizationArgs(); Context ctx = getReactApplicationContext(); int resId = getResId(ctx, titleLocKey); return ctx.getResources().getString(resId, (Object[]) titleLocArgs); + } else if (title != null) { + return title; } else { return null; } } private @Nullable String getNotificationBody(RemoteMessage.Notification notification) { - String body = notification.getBody(), bodyLocKey = notification.getBodyLocalizationKey(); - if (body != null) { - return body; - } else if (bodyLocKey != null) { + String body = notification.getBody(); + String bodyLocKey = notification.getBodyLocalizationKey(); + if (bodyLocKey != null) { String[] bodyLocArgs = notification.getBodyLocalizationArgs(); Context ctx = getReactApplicationContext(); int resId = getResId(ctx, bodyLocKey); return ctx.getResources().getString(resId, (Object[]) bodyLocArgs); + } else if (body != null) { + return body; } else { return null; } From b0fc98d0e6532e9ec9aaaef7f6e3c1b3c7d89d6d Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 24 Jul 2018 17:10:38 +0200 Subject: [PATCH 22/35] log error when resource not found --- android/src/main/java/io/invertase/firebase/Utils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index 293f0f7c..444f6162 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -158,6 +158,10 @@ public class Utils { } public static int getResId(Context ctx, String resName) { - return ctx.getResources().getIdentifier(resName, "string", ctx.getPackageName()); + int resourceId = ctx.getResources().getIdentifier(resName, "string", ctx.getPackageName()); + if (resourceId == 0) { + Log.e(TAG, "resource " + resName + "could not be found"); + } + return resourceId; } } From c3d56b173181d605ecc5d026e0cb69e17516472f Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 24 Jul 2018 17:12:42 +0200 Subject: [PATCH 23/35] reverse order for better readability --- .../RNFirebaseNotifications.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index 2672c8dc..92e564ee 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -315,21 +315,6 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen return notificationMap; } - private @Nullable String getNotificationTitle(RemoteMessage.Notification notification) { - String title = notification.getTitle(); - String titleLocKey = notification.getTitleLocalizationKey(); - if (titleLocKey != null) { - String[] titleLocArgs = notification.getTitleLocalizationArgs(); - Context ctx = getReactApplicationContext(); - int resId = getResId(ctx, titleLocKey); - return ctx.getResources().getString(resId, (Object[]) titleLocArgs); - } else if (title != null) { - return title; - } else { - return null; - } - } - private @Nullable String getNotificationBody(RemoteMessage.Notification notification) { String body = notification.getBody(); String bodyLocKey = notification.getBodyLocalizationKey(); @@ -345,6 +330,21 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen } } + private @Nullable String getNotificationTitle(RemoteMessage.Notification notification) { + String title = notification.getTitle(); + String titleLocKey = notification.getTitleLocalizationKey(); + if (titleLocKey != null) { + String[] titleLocArgs = notification.getTitleLocalizationArgs(); + Context ctx = getReactApplicationContext(); + int resId = getResId(ctx, titleLocKey); + return ctx.getResources().getString(resId, (Object[]) titleLocArgs); + } else if (title != null) { + return title; + } else { + return null; + } + } + private class RemoteNotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { From 5b516aeb091c6e172502c148f2d7f39f8c84b8c4 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 24 Jul 2018 17:22:19 +0200 Subject: [PATCH 24/35] add missing space --- android/src/main/java/io/invertase/firebase/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index 444f6162..458ae2ec 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -160,7 +160,7 @@ public class Utils { public static int getResId(Context ctx, String resName) { int resourceId = ctx.getResources().getIdentifier(resName, "string", ctx.getPackageName()); if (resourceId == 0) { - Log.e(TAG, "resource " + resName + "could not be found"); + Log.e(TAG, "resource " + resName + " could not be found"); } return resourceId; } From 3078bae754a97daa469d873df415a57b467c5bb9 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Wed, 25 Jul 2018 09:31:13 +0200 Subject: [PATCH 25/35] Update RNFirebaseNotifications.java --- .../firebase/notifications/RNFirebaseNotifications.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java index 92e564ee..58f7a3de 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java +++ b/android/src/main/java/io/invertase/firebase/notifications/RNFirebaseNotifications.java @@ -323,10 +323,8 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen Context ctx = getReactApplicationContext(); int resId = getResId(ctx, bodyLocKey); return ctx.getResources().getString(resId, (Object[]) bodyLocArgs); - } else if (body != null) { - return body; } else { - return null; + return body; } } @@ -338,10 +336,8 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen Context ctx = getReactApplicationContext(); int resId = getResId(ctx, titleLocKey); return ctx.getResources().getString(resId, (Object[]) titleLocArgs); - } else if (title != null) { - return title; } else { - return null; + return title; } } From f3e0d06608f6c8a2d971f48e11be8cd9c20dd5e5 Mon Sep 17 00:00:00 2001 From: Salakar Date: Wed, 25 Jul 2018 12:14:55 +0100 Subject: [PATCH 26/35] 4.3.8 --- 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 ec1b6a13..08fcdaa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "4.3.7", + "version": "4.3.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dd0aa9bc..0a5d5cf0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "4.3.7", + "version": "4.3.8", "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, Dynamic Links, Functions, Messaging (FCM), Remote Config, Storage and more.", "main": "dist/index.js", From 1d1a56ad3e4f1a0fb443860eb3bc3e36b45b3049 Mon Sep 17 00:00:00 2001 From: Pranjal Jain Date: Sun, 29 Jul 2018 06:25:08 +0530 Subject: [PATCH 27/35] fix(notifications): Fix alertOnlyOnce in android notifications --- .../firebase/notifications/DisplayNotificationTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java b/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java index ffc3c5d1..c1cebe80 100644 --- a/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java +++ b/android/src/main/java/io/invertase/firebase/notifications/DisplayNotificationTask.java @@ -192,7 +192,7 @@ public class DisplayNotificationTask extends AsyncTask { nb = nb.setOngoing(android.getBoolean("ongoing")); } if (android.containsKey("onlyAlertOnce")) { - nb = nb.setOngoing(android.getBoolean("onlyAlertOnce")); + nb = nb.setOnlyAlertOnce(android.getBoolean("onlyAlertOnce")); } if (android.containsKey("people")) { List people = android.getStringArrayList("people"); From 81fee9f57a27d29d013e45c318e9360794c275fe Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 29 Jul 2018 21:49:09 +0100 Subject: [PATCH 28/35] [ios][auth] fix checkActionCode bug - fixes #1304 --- ios/RNFirebase/auth/RNFirebaseAuth.m | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ios/RNFirebase/auth/RNFirebaseAuth.m b/ios/RNFirebase/auth/RNFirebaseAuth.m index 85b7c180..97e6e970 100644 --- a/ios/RNFirebase/auth/RNFirebaseAuth.m +++ b/ios/RNFirebase/auth/RNFirebaseAuth.m @@ -688,9 +688,23 @@ RCT_EXPORT_METHOD(checkActionCode: actionType = @"EMAIL_SIGNIN"; break; } + + NSMutableDictionary *data = [NSMutableDictionary dictionary]; - NSDictionary *result = @{@"data": @{@"email": [info dataForKey:FIRActionCodeEmailKey], @"fromEmail": [info dataForKey:FIRActionCodeFromEmailKey],}, @"actionType": actionType,}; + if ([info dataForKey:FIRActionCodeEmailKey] != nil) { + [data setValue:[info dataForKey:FIRActionCodeEmailKey] forKey:@"email"]; + } else { + [data setValue:[NSNull null] forKey:@"email"]; + } + if ([info dataForKey:FIRActionCodeFromEmailKey] != nil) { + [data setValue:[info dataForKey:FIRActionCodeFromEmailKey] forKey:@"fromEmail"]; + } else { + [data setValue:[NSNull null] forKey:@"fromEmail"]; + } + + NSDictionary *result = @{ @"data": data, @"actionType": actionType }; + resolve(result); } }]; From 061e1838dc9f09b3383b537e7326edd63df40c53 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Wed, 1 Aug 2018 12:45:54 +0100 Subject: [PATCH 29/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c74bccb6..3b64d103 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Please make sure to complete the issue template before opening an issue. Issues ## Feature Requests -For feature requests please use our [Canny Board](http://invertase.link/requests). +For feature requests please visit our [Freature Request Board](https://boards.invertase.io/react-native-firebase). ## Changelog From 3ae754bd22d471b9506771be41bccd0d14274ea1 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Wed, 1 Aug 2018 12:46:41 +0100 Subject: [PATCH 30/35] Update Feature_request.md --- .github/ISSUE_TEMPLATE/Feature_request.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 79c6fe08..eae3cc09 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,7 +1,7 @@ --- name: 🎁 Feature request -about: Please create feature requests on our canny board: [https://react-native-firebase.canny.io/feature-requests](https://react-native-firebase.canny.io/feature-requests) +about: For feature requests please visit our [Feature Request Board](https://boards.invertase.io/react-native-firebase). --- -[https://react-native-firebase.canny.io/feature-requests](https://react-native-firebase.canny.io/feature-requests) +For feature requests please visit our [Feature Request Board](https://boards.invertase.io/react-native-firebase). From 937c7a23f8f36d5a3a6f30c92bc13cc71d86c1b5 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Wed, 1 Aug 2018 12:47:11 +0100 Subject: [PATCH 31/35] Update Bug_report.md --- .github/ISSUE_TEMPLATE/Bug_report.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index ecef6077..567c9001 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,19 +1,19 @@ --- -name: ⚠️ Bug/Issue report -about: Please provide as much detail as possible to help us with a bug or issue. Issues +name: ⚠️ Bug/Issue report +about: Please provide as much detail as possible to help us with a bug or issue. Issues will be closed if they do not follow the template. --- - ### Issue From e7e89732c0863efca1f38bc36f54415ef36f0000 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Wed, 1 Aug 2018 12:47:35 +0100 Subject: [PATCH 32/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b64d103..1945275b 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Please make sure to complete the issue template before opening an issue. Issues ## Feature Requests -For feature requests please visit our [Freature Request Board](https://boards.invertase.io/react-native-firebase). +For feature requests please visit our [Feature Request Board](https://boards.invertase.io/react-native-firebase). ## Changelog From 0061ad98884ad3eba9da7ca1f056fc62bced2e67 Mon Sep 17 00:00:00 2001 From: Salakar Date: Wed, 1 Aug 2018 23:00:40 +0100 Subject: [PATCH 33/35] [android][firestore] cleanup --- .../java/io/invertase/firebase/Utils.java | 17 +- .../firestore/FirestoreSerialize.java | 46 +- .../firestore/RNFirebaseFirestore.java | 535 +++++++++++------- ...NFirebaseFirestoreCollectionReference.java | 141 +++-- .../RNFirebaseFirestoreDocumentReference.java | 133 +++-- .../firestore/RNFirebaseFirestorePackage.java | 3 +- ...RNFirebaseFirestoreTransactionHandler.java | 9 +- 7 files changed, 566 insertions(+), 318 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index 458ae2ec..0d8eba07 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -1,20 +1,21 @@ package io.invertase.firebase; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.Context; +import android.os.AsyncTask; import android.util.Log; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + import java.util.List; import java.util.Map; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.modules.core.DeviceEventManagerModule; - -import com.facebook.react.bridge.ReadableArray; - import javax.annotation.Nullable; 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 90291179..53913db9 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java +++ b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java @@ -52,18 +52,16 @@ public class FirestoreSerialize { if (documentSnapshot.exists()) { documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData())); } - // metadata - if (documentSnapshot.getMetadata() != null) { - WritableMap metadata = Arguments.createMap(); - metadata.putBoolean("fromCache", documentSnapshot.getMetadata().isFromCache()); - metadata.putBoolean("hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites()); - documentMap.putMap(KEY_METADATA, metadata); - } + // metadata + WritableMap metadata = Arguments.createMap(); + metadata.putBoolean("fromCache", documentSnapshot.getMetadata().isFromCache()); + metadata.putBoolean("hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites()); + documentMap.putMap(KEY_METADATA, metadata); return documentMap; } - public static WritableMap snapshotToWritableMap(QuerySnapshot querySnapshot) { + static WritableMap snapshotToWritableMap(QuerySnapshot querySnapshot) { WritableMap queryMap = Arguments.createMap(); List documentChanges = querySnapshot.getDocumentChanges(); @@ -78,12 +76,10 @@ public class FirestoreSerialize { queryMap.putArray(KEY_DOCUMENTS, documents); // metadata - if (querySnapshot.getMetadata() != null) { - WritableMap metadata = Arguments.createMap(); - metadata.putBoolean("fromCache", querySnapshot.getMetadata().isFromCache()); - metadata.putBoolean("hasPendingWrites", querySnapshot.getMetadata().hasPendingWrites()); - queryMap.putMap(KEY_METADATA, metadata); - } + WritableMap metadata = Arguments.createMap(); + metadata.putBoolean("fromCache", querySnapshot.getMetadata().isFromCache()); + metadata.putBoolean("hasPendingWrites", querySnapshot.getMetadata().hasPendingWrites()); + queryMap.putMap(KEY_METADATA, metadata); return queryMap; } @@ -94,7 +90,7 @@ public class FirestoreSerialize { * @param documentChanges List * @return WritableArray */ - static WritableArray documentChangesToWritableArray(List documentChanges) { + private static WritableArray documentChangesToWritableArray(List documentChanges) { WritableArray documentChangesWritable = Arguments.createArray(); for (DocumentChange documentChange : documentChanges) { documentChangesWritable.pushMap(documentChangeToWritableMap(documentChange)); @@ -108,7 +104,7 @@ public class FirestoreSerialize { * @param documentChange DocumentChange * @return WritableMap */ - static WritableMap documentChangeToWritableMap(DocumentChange documentChange) { + private static WritableMap documentChangeToWritableMap(DocumentChange documentChange) { WritableMap documentChangeMap = Arguments.createMap(); switch (documentChange.getType()) { @@ -122,8 +118,10 @@ public class FirestoreSerialize { documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "modified"); } - documentChangeMap.putMap(KEY_DOC_CHANGE_DOCUMENT, - snapshotToWritableMap(documentChange.getDocument())); + documentChangeMap.putMap( + KEY_DOC_CHANGE_DOCUMENT, + snapshotToWritableMap(documentChange.getDocument()) + ); documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex()); documentChangeMap.putInt(KEY_DOC_CHANGE_OLD_INDEX, documentChange.getOldIndex()); @@ -136,7 +134,7 @@ public class FirestoreSerialize { * @param map Map * @return WritableMap */ - static WritableMap objectMapToWritable(Map map) { + private static WritableMap objectMapToWritable(Map map) { WritableMap writableMap = Arguments.createMap(); for (Map.Entry entry : map.entrySet()) { WritableMap typeMap = buildTypeMap(entry.getValue()); @@ -224,7 +222,10 @@ public class FirestoreSerialize { return typeMap; } - static Map parseReadableMap(FirebaseFirestore firestore, ReadableMap readableMap) { + static Map parseReadableMap( + FirebaseFirestore firestore, + ReadableMap readableMap + ) { Map map = new HashMap<>(); if (readableMap != null) { ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); @@ -290,7 +291,10 @@ public class FirestoreSerialize { } } - public static List parseDocumentBatches(FirebaseFirestore firestore, ReadableArray readableArray) { + static List parseDocumentBatches( + FirebaseFirestore firestore, + ReadableArray readableArray + ) { List writes = new ArrayList<>(readableArray.size()); for (int i = 0; i < readableArray.size(); i++) { Map write = new HashMap<>(); 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 9387d275..d0d61fbc 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java @@ -18,13 +18,13 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; import com.google.firebase.FirebaseApp; -import com.google.firebase.firestore.FirebaseFirestoreSettings; -import com.google.firebase.firestore.Transaction; import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.FieldValue; import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.FirebaseFirestoreSettings; import com.google.firebase.firestore.SetOptions; +import com.google.firebase.firestore.Transaction; import com.google.firebase.firestore.WriteBatch; import java.util.HashMap; @@ -48,6 +48,184 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { * REACT NATIVE METHODS */ + /** + * Generates a js-like error from an exception and rejects the provided promise with it. + * + * @param exception Exception Exception normally from a task result. + * @param promise Promise react native promise + */ + static void promiseRejectException(Promise promise, FirebaseFirestoreException exception) { + WritableMap jsError = getJSError(exception); + promise.reject( + jsError.getString("code"), + jsError.getString("message"), + exception + ); + } + + /** + * Get a database instance for a specific firebase app instance + * + * @param appName appName + * @return FirebaseFirestore + */ + static FirebaseFirestore getFirestoreForApp(String appName) { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + return FirebaseFirestore.getInstance(firebaseApp); + } + + /** + * Convert as firebase DatabaseError instance into a writable map + * with the correct web-like error codes. + * + * @param nativeException nativeException + * @return WritableMap + */ + static WritableMap getJSError(FirebaseFirestoreException nativeException) { + WritableMap errorMap = Arguments.createMap(); + errorMap.putInt("nativeErrorCode", nativeException.getCode().value()); + errorMap.putString("nativeErrorMessage", nativeException.getMessage()); + + String code; + String message; + String service = "Firestore"; + + // TODO: Proper error mappings + switch (nativeException.getCode()) { + case OK: + code = ErrorUtils.getCodeWithService(service, "ok"); + message = ErrorUtils.getMessageWithService("Ok.", service, code); + break; + case CANCELLED: + code = ErrorUtils.getCodeWithService(service, "cancelled"); + message = ErrorUtils.getMessageWithService("The operation was cancelled.", service, code); + break; + case UNKNOWN: + code = ErrorUtils.getCodeWithService(service, "unknown"); + message = ErrorUtils.getMessageWithService( + "Unknown error or an error from a different error domain.", + service, + code + ); + break; + case INVALID_ARGUMENT: + code = ErrorUtils.getCodeWithService(service, "invalid-argument"); + message = ErrorUtils.getMessageWithService( + "Client specified an invalid argument.", + service, + code + ); + break; + case DEADLINE_EXCEEDED: + code = ErrorUtils.getCodeWithService(service, "deadline-exceeded"); + message = ErrorUtils.getMessageWithService( + "Deadline expired before operation could complete.", + service, + code + ); + break; + case NOT_FOUND: + code = ErrorUtils.getCodeWithService(service, "not-found"); + message = ErrorUtils.getMessageWithService( + "Some requested document was not found.", + service, + code + ); + break; + case ALREADY_EXISTS: + code = ErrorUtils.getCodeWithService(service, "already-exists"); + message = ErrorUtils.getMessageWithService( + "Some document that we attempted to create already exists.", + service, + code + ); + break; + case PERMISSION_DENIED: + code = ErrorUtils.getCodeWithService(service, "permission-denied"); + message = ErrorUtils.getMessageWithService( + "The caller does not have permission to execute the specified operation.", + service, + code + ); + break; + case RESOURCE_EXHAUSTED: + code = ErrorUtils.getCodeWithService(service, "resource-exhausted"); + message = ErrorUtils.getMessageWithService( + "Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.", + service, + code + ); + break; + case FAILED_PRECONDITION: + code = ErrorUtils.getCodeWithService(service, "failed-precondition"); + message = ErrorUtils.getMessageWithService( + "Operation was rejected because the system is not in a state required for the operation`s execution.", + service, + code + ); + break; + case ABORTED: + code = ErrorUtils.getCodeWithService(service, "aborted"); + message = ErrorUtils.getMessageWithService( + "The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.", + service, + code + ); + break; + case OUT_OF_RANGE: + code = ErrorUtils.getCodeWithService(service, "out-of-range"); + message = ErrorUtils.getMessageWithService( + "Operation was attempted past the valid range.", + service, + code + ); + break; + case UNIMPLEMENTED: + code = ErrorUtils.getCodeWithService(service, "unimplemented"); + message = ErrorUtils.getMessageWithService( + "Operation is not implemented or not supported/enabled.", + service, + code + ); + break; + case INTERNAL: + code = ErrorUtils.getCodeWithService(service, "internal"); + message = ErrorUtils.getMessageWithService("Internal errors.", service, code); + break; + case UNAVAILABLE: + code = ErrorUtils.getCodeWithService(service, "unavailable"); + message = ErrorUtils.getMessageWithService( + "The service is currently unavailable.", + service, + code + ); + break; + case DATA_LOSS: + code = ErrorUtils.getCodeWithService(service, "data-loss"); + message = ErrorUtils.getMessageWithService( + "Unrecoverable data loss or corruption.", + service, + code + ); + break; + case UNAUTHENTICATED: + code = ErrorUtils.getCodeWithService(service, "unauthenticated"); + message = ErrorUtils.getMessageWithService( + "The request does not have valid authentication credentials for the operation.", + service, + code + ); + break; + default: + code = ErrorUtils.getCodeWithService(service, "unknown"); + message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code); + } + + errorMap.putString("code", code); + errorMap.putString("message", message); + return errorMap; + } + @ReactMethod public void disableNetwork(String appName, final Promise promise) { getFirestoreForApp(appName).disableNetwork().addOnCompleteListener(new OnCompleteListener() { @@ -58,7 +236,10 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { promise.resolve(null); } else { Log.e(TAG, "disableNetwork:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); @@ -83,35 +264,55 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { promise.resolve(null); } else { Log.e(TAG, "enableNetwork:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); } @ReactMethod - public void collectionGet(String appName, String path, ReadableArray filters, - ReadableArray orders, ReadableMap options, ReadableMap getOptions, - final Promise promise) { - RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); + public void collectionGet( + String appName, String path, ReadableArray filters, + ReadableArray orders, ReadableMap options, ReadableMap getOptions, + final Promise promise + ) { + RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath( + appName, + path, + filters, + orders, + options + ); ref.get(getOptions, promise); } @ReactMethod - public void collectionOffSnapshot(String appName, String path, ReadableArray filters, - ReadableArray orders, ReadableMap options, String listenerId) { + public void collectionOffSnapshot( + String appName, String path, ReadableArray filters, + ReadableArray orders, ReadableMap options, String listenerId + ) { RNFirebaseFirestoreCollectionReference.offSnapshot(listenerId); } @ReactMethod - public void collectionOnSnapshot(String appName, String path, ReadableArray filters, - ReadableArray orders, ReadableMap options, String listenerId, - ReadableMap queryListenOptions) { - RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); + public void collectionOnSnapshot( + String appName, String path, ReadableArray filters, + ReadableArray orders, ReadableMap options, String listenerId, + ReadableMap queryListenOptions + ) { + RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath( + appName, + path, + filters, + orders, + options + ); ref.onSnapshot(listenerId, queryListenOptions); } - @ReactMethod public void documentBatch(final String appName, final ReadableArray writes, final Promise promise) { @@ -166,7 +367,12 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { } @ReactMethod - public void documentGet(String appName, String path, ReadableMap getOptions, final Promise promise) { + public void documentGet( + String appName, + String path, + ReadableMap getOptions, + final Promise promise + ) { RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); ref.get(getOptions, promise); } @@ -177,18 +383,31 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { } @ReactMethod - public void documentOnSnapshot(String appName, String path, String listenerId, - ReadableMap docListenOptions) { + public void documentOnSnapshot( + String appName, String path, String listenerId, + ReadableMap docListenOptions + ) { RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); ref.onSnapshot(listenerId, docListenOptions); } @ReactMethod - public void documentSet(String appName, String path, ReadableMap data, ReadableMap options, final Promise promise) { + public void documentSet( + String appName, + String path, + ReadableMap data, + ReadableMap options, + final Promise promise + ) { RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); ref.set(data, options, promise); } + + /* + * Transaction Methods + */ + @ReactMethod public void documentUpdate(String appName, String path, ReadableMap data, final Promise promise) { RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); @@ -214,18 +433,17 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { } else { firestoreSettings.setSslEnabled(firestore.getFirestoreSettings().isSslEnabled()); } - if (settings.hasKey("timestampsInSnapshots")) { - // TODO: Not supported on Android yet - } + +// if (settings.hasKey("timestampsInSnapshots")) { +// // TODO: Not supported on Android yet +// } firestore.setFirestoreSettings(firestoreSettings.build()); promise.resolve(null); } - /** * Try clean up previous transactions on reload - * */ @Override public void onCatalystInstanceDestroy() { @@ -239,23 +457,22 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { transactionHandlers.clear(); } - - /* - * Transaction Methods - */ - - /** * Calls the internal Firestore Transaction classes instance .get(ref) method and resolves with * the DocumentSnapshot. * - * @param appName - * @param transactionId - * @param path - * @param promise + * @param appName appName + * @param transactionId transactionId + * @param path path + * @param promise promise */ @ReactMethod - public void transactionGetDocument(String appName, int transactionId, String path, final Promise promise) { + public void transactionGetDocument( + String appName, + int transactionId, + String path, + final Promise promise + ) { RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId); if (handler == null) { @@ -269,11 +486,16 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { } } + + /* + * INTERNALS/UTILS + */ + /** * Aborts any pending signals and deletes the transaction handler. * - * @param appName - * @param transactionId + * @param appName appName + * @param transactionId transactionId */ @ReactMethod public void transactionDispose(String appName, int transactionId) { @@ -288,12 +510,16 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { /** * Signals to transactionHandler that the command buffer is ready. * - * @param appName - * @param transactionId - * @param commandBuffer + * @param appName appName + * @param transactionId transactionId + * @param commandBuffer commandBuffer */ @ReactMethod - public void transactionApplyBuffer(String appName, int transactionId, ReadableArray commandBuffer) { + public void transactionApplyBuffer( + String appName, + int transactionId, + ReadableArray commandBuffer + ) { RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId); if (handler != null) { @@ -304,12 +530,16 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { /** * Begin a new transaction via AsyncTask 's * - * @param appName - * @param transactionId + * @param appName appName + * @param transactionId transactionId */ @ReactMethod public void transactionBegin(final String appName, int transactionId) { - final RNFirebaseFirestoreTransactionHandler transactionHandler = new RNFirebaseFirestoreTransactionHandler(appName, transactionId); + final RNFirebaseFirestoreTransactionHandler transactionHandler = new RNFirebaseFirestoreTransactionHandler( + appName, + transactionId + ); + transactionHandlers.put(transactionId, transactionHandler); AsyncTask.execute(new Runnable() { @@ -327,7 +557,11 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { @Override public void run() { WritableMap eventMap = transactionHandler.createEventMap(null, "update"); - Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap); + Utils.sendEvent( + getReactApplicationContext(), + "firestore_transaction_event", + eventMap + ); } }); @@ -336,12 +570,18 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { // exit early if aborted - has to throw an exception otherwise will just keep trying ... if (transactionHandler.aborted) { - throw new FirebaseFirestoreException("abort", FirebaseFirestoreException.Code.ABORTED); + throw new FirebaseFirestoreException( + "abort", + FirebaseFirestoreException.Code.ABORTED + ); } // exit early if timeout from bridge - has to throw an exception otherwise will just keep trying ... if (transactionHandler.timeout) { - throw new FirebaseFirestoreException("timeout", FirebaseFirestoreException.Code.DEADLINE_EXCEEDED); + throw new FirebaseFirestoreException( + "timeout", + FirebaseFirestoreException.Code.DEADLINE_EXCEEDED + ); } // process any buffered commands from JS land @@ -357,15 +597,19 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { ReadableMap command = buffer.getMap(i); String path = command.getString("path"); String type = command.getString("type"); - RNFirebaseFirestoreDocumentReference documentReference = getDocumentForAppPath(appName, path); - + RNFirebaseFirestoreDocumentReference documentReference = getDocumentForAppPath( + appName, + path + ); switch (type) { case "set": data = command.getMap("data"); - ReadableMap options = command.getMap("options"); - Map setData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); + Map setData = FirestoreSerialize.parseReadableMap( + RNFirebaseFirestore.getFirestoreForApp(appName), + data + ); if (options != null && options.hasKey("merge") && options.getBoolean("merge")) { transaction.set(documentReference.getRef(), setData, SetOptions.merge()); @@ -376,7 +620,11 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { case "update": data = command.getMap("data"); - Map updateData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); + Map updateData = FirestoreSerialize.parseReadableMap( + RNFirebaseFirestore.getFirestoreForApp(appName), + data + ); + transaction.update(documentReference.getRef(), updateData); break; case "delete": @@ -396,7 +644,11 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { if (!transactionHandler.aborted) { Log.d(TAG, "Transaction onSuccess!"); WritableMap eventMap = transactionHandler.createEventMap(null, "complete"); - Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap); + Utils.sendEvent( + getReactApplicationContext(), + "firestore_transaction_event", + eventMap + ); } } }) @@ -405,8 +657,15 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { public void onFailure(@NonNull Exception e) { if (!transactionHandler.aborted) { Log.w(TAG, "Transaction onFailure.", e); - WritableMap eventMap = transactionHandler.createEventMap((FirebaseFirestoreException) e, "error"); - Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap); + WritableMap eventMap = transactionHandler.createEventMap( + (FirebaseFirestoreException) e, + "error" + ); + Utils.sendEvent( + getReactApplicationContext(), + "firestore_transaction_event", + eventMap + ); } } }); @@ -414,158 +673,44 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { }); } - - /* - * INTERNALS/UTILS - */ - - /** - * Generates a js-like error from an exception and rejects the provided promise with it. - * - * @param exception Exception Exception normally from a task result. - * @param promise Promise react native promise - */ - static void promiseRejectException(Promise promise, FirebaseFirestoreException exception) { - WritableMap jsError = getJSError(exception); - promise.reject( - jsError.getString("code"), - jsError.getString("message"), - exception - ); - } - - /** - * Get a database instance for a specific firebase app instance - * - * @param appName - * @return - */ - static FirebaseFirestore getFirestoreForApp(String appName) { - FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); - return FirebaseFirestore.getInstance(firebaseApp); - } - /** * Get a collection reference for a specific app and path * - * @param appName - * @param filters - * @param orders - * @param options + * @param appName appName + * @param filters filters + * @param orders orders + * @param options options * @param path @return */ - private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path, - ReadableArray filters, - ReadableArray orders, - ReadableMap options) { - return new RNFirebaseFirestoreCollectionReference(this.getReactApplicationContext(), appName, path, filters, orders, options); + private RNFirebaseFirestoreCollectionReference getCollectionForAppPath( + String appName, String path, + ReadableArray filters, + ReadableArray orders, + ReadableMap options + ) { + return new RNFirebaseFirestoreCollectionReference( + this.getReactApplicationContext(), + appName, + path, + filters, + orders, + options + ); } /** * Get a document reference for a specific app and path * - * @param appName - * @param path - * @return + * @param appName appName + * @param path path + * @return RNFirebaseFirestoreDocumentReference */ private RNFirebaseFirestoreDocumentReference getDocumentForAppPath(String appName, String path) { - return new RNFirebaseFirestoreDocumentReference(this.getReactApplicationContext(), appName, path); - } - - /** - * Convert as firebase DatabaseError instance into a writable map - * with the correct web-like error codes. - * - * @param nativeException - * @return - */ - static WritableMap getJSError(FirebaseFirestoreException nativeException) { - WritableMap errorMap = Arguments.createMap(); - errorMap.putInt("nativeErrorCode", nativeException.getCode().value()); - errorMap.putString("nativeErrorMessage", nativeException.getMessage()); - - String code; - String message; - String service = "Firestore"; - - // TODO: Proper error mappings - switch (nativeException.getCode()) { - case OK: - code = ErrorUtils.getCodeWithService(service, "ok"); - message = ErrorUtils.getMessageWithService("Ok.", service, code); - break; - case CANCELLED: - code = ErrorUtils.getCodeWithService(service, "cancelled"); - message = ErrorUtils.getMessageWithService("The operation was cancelled.", service, code); - break; - case UNKNOWN: - code = ErrorUtils.getCodeWithService(service, "unknown"); - message = ErrorUtils.getMessageWithService("Unknown error or an error from a different error domain.", service, code); - break; - case INVALID_ARGUMENT: - code = ErrorUtils.getCodeWithService(service, "invalid-argument"); - message = ErrorUtils.getMessageWithService("Client specified an invalid argument.", service, code); - break; - case DEADLINE_EXCEEDED: - code = ErrorUtils.getCodeWithService(service, "deadline-exceeded"); - message = ErrorUtils.getMessageWithService("Deadline expired before operation could complete.", service, code); - break; - case NOT_FOUND: - code = ErrorUtils.getCodeWithService(service, "not-found"); - message = ErrorUtils.getMessageWithService("Some requested document was not found.", service, code); - break; - case ALREADY_EXISTS: - code = ErrorUtils.getCodeWithService(service, "already-exists"); - message = ErrorUtils.getMessageWithService("Some document that we attempted to create already exists.", service, code); - break; - case PERMISSION_DENIED: - code = ErrorUtils.getCodeWithService(service, "permission-denied"); - message = ErrorUtils.getMessageWithService("The caller does not have permission to execute the specified operation.", service, code); - break; - case RESOURCE_EXHAUSTED: - code = ErrorUtils.getCodeWithService(service, "resource-exhausted"); - message = ErrorUtils.getMessageWithService("Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.", service, code); - break; - case FAILED_PRECONDITION: - code = ErrorUtils.getCodeWithService(service, "failed-precondition"); - message = ErrorUtils.getMessageWithService("Operation was rejected because the system is not in a state required for the operation`s execution.", service, code); - break; - case ABORTED: - code = ErrorUtils.getCodeWithService(service, "aborted"); - message = ErrorUtils.getMessageWithService("The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.", service, code); - break; - case OUT_OF_RANGE: - code = ErrorUtils.getCodeWithService(service, "out-of-range"); - message = ErrorUtils.getMessageWithService("Operation was attempted past the valid range.", service, code); - break; - case UNIMPLEMENTED: - code = ErrorUtils.getCodeWithService(service, "unimplemented"); - message = ErrorUtils.getMessageWithService("Operation is not implemented or not supported/enabled.", service, code); - break; - case INTERNAL: - code = ErrorUtils.getCodeWithService(service, "internal"); - message = ErrorUtils.getMessageWithService("Internal errors.", service, code); - break; - case UNAVAILABLE: - code = ErrorUtils.getCodeWithService(service, "unavailable"); - message = ErrorUtils.getMessageWithService("The service is currently unavailable.", service, code); - break; - case DATA_LOSS: - code = ErrorUtils.getCodeWithService(service, "data-loss"); - message = ErrorUtils.getMessageWithService("Unrecoverable data loss or corruption.", service, code); - break; - case UNAUTHENTICATED: - code = ErrorUtils.getCodeWithService(service, "unauthenticated"); - message = ErrorUtils.getMessageWithService("The request does not have valid authentication credentials for the operation.", service, code); - break; - default: - code = ErrorUtils.getCodeWithService(service, "unknown"); - message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code); - } - - errorMap.putString("code", code); - errorMap.putString("message", message); - return errorMap; + return new RNFirebaseFirestoreDocumentReference( + this.getReactApplicationContext(), + appName, + path + ); } /** 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 548f942f..b251ec5f 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java @@ -1,6 +1,7 @@ package io.invertase.firebase.firestore; +import android.annotation.SuppressLint; import android.support.annotation.NonNull; import android.util.Log; @@ -22,28 +23,32 @@ import com.google.firebase.firestore.Query; import com.google.firebase.firestore.QuerySnapshot; import com.google.firebase.firestore.Source; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import io.invertase.firebase.Utils; -public class RNFirebaseFirestoreCollectionReference { +class RNFirebaseFirestoreCollectionReference { private static final String TAG = "RNFSCollectionReference"; private static Map collectionSnapshotListeners = new HashMap<>(); - private final String appName; private final String path; - private final ReadableArray filters; - private final ReadableArray orders; - private final ReadableMap options; private final Query query; + private final String appName; + private final ReadableMap options; + private final ReadableArray orders; + private final ReadableArray filters; private ReactContext reactContext; - RNFirebaseFirestoreCollectionReference(ReactContext reactContext, String appName, String path, - ReadableArray filters, ReadableArray orders, - ReadableMap options) { + RNFirebaseFirestoreCollectionReference( + ReactContext reactContext, + String appName, + String path, + ReadableArray filters, + ReadableArray orders, + ReadableMap options + ) { this.appName = appName; this.path = path; this.filters = filters; @@ -53,6 +58,13 @@ public class RNFirebaseFirestoreCollectionReference { this.reactContext = reactContext; } + static void offSnapshot(final String listenerId) { + ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { + listenerRegistration.remove(); + } + } + void get(ReadableMap getOptions, final Promise promise) { Source source; if (getOptions != null && getOptions.hasKey("source")) { @@ -67,29 +79,34 @@ public class RNFirebaseFirestoreCollectionReference { } else { source = Source.DEFAULT; } + + @SuppressLint("StaticFieldLeak") final QuerySnapshotSerializeAsyncTask serializeAsyncTask = new QuerySnapshotSerializeAsyncTask( + reactContext, this + ) { + @Override + protected void onPostExecute(WritableMap writableMap) { + promise.resolve(writableMap); + } + }; + query.get(source).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { Log.d(TAG, "get:onComplete:success"); - WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult()); - promise.resolve(data); + serializeAsyncTask.execute(task.getResult()); } else { Log.e(TAG, "get:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); } - public static void offSnapshot(final String listenerId) { - ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId); - if (listenerRegistration != null) { - listenerRegistration.remove(); - } - } - - public void onSnapshot(final String listenerId, final ReadableMap queryListenOptions) { + void onSnapshot(final String listenerId, final ReadableMap queryListenOptions) { if (!collectionSnapshotListeners.containsKey(listenerId)) { final EventListener listener = new EventListener() { @Override @@ -97,7 +114,8 @@ public class RNFirebaseFirestoreCollectionReference { if (exception == null) { handleQuerySnapshotEvent(listenerId, querySnapshot); } else { - ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId); + ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove( + listenerId); if (listenerRegistration != null) { listenerRegistration.remove(); } @@ -115,7 +133,10 @@ public class RNFirebaseFirestoreCollectionReference { metadataChanges = MetadataChanges.EXCLUDE; } - ListenerRegistration listenerRegistration = this.query.addSnapshotListener(metadataChanges, listener); + ListenerRegistration listenerRegistration = this.query.addSnapshotListener( + metadataChanges, + listener + ); collectionSnapshotListeners.put(listenerId, listenerRegistration); } } @@ -170,7 +191,7 @@ public class RNFirebaseFirestoreCollectionReference { } else { ReadableArray fieldPathElements = fieldPathMap.getArray("elements"); String[] fieldPathArray = new String[fieldPathElements.size()]; - for (int j=0; j order = (Map) o; String direction = (String) order.get("direction"); Map fieldPathMap = (Map) order.get("fieldPath"); - String fieldPathType = (String)fieldPathMap.get("type"); + String fieldPathType = (String) fieldPathMap.get("type"); if (fieldPathType.equals("string")) { - String fieldPath = (String)fieldPathMap.get("string"); + String fieldPath = (String) fieldPathMap.get("string"); query = query.orderBy(fieldPath, Query.Direction.valueOf(direction)); } else { - List fieldPathElements = (List)fieldPathMap.get("elements"); + List fieldPathElements = (List) fieldPathMap.get("elements"); FieldPath fieldPath = FieldPath.of(fieldPathElements.toArray(new String[fieldPathElements.size()])); query = query.orderBy(fieldPath, Query.Direction.valueOf(direction)); } @@ -218,57 +239,79 @@ public class RNFirebaseFirestoreCollectionReference { private Query applyOptions(FirebaseFirestore firestore, Query query) { if (options.hasKey("endAt")) { - List endAtList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endAt")); + List endAtList = FirestoreSerialize.parseReadableArray( + firestore, + options.getArray("endAt") + ); query = query.endAt(endAtList.toArray()); } + if (options.hasKey("endBefore")) { - List endBeforeList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endBefore")); + List endBeforeList = FirestoreSerialize.parseReadableArray( + firestore, + options.getArray("endBefore") + ); query = query.endBefore(endBeforeList.toArray()); } + if (options.hasKey("limit")) { int limit = options.getInt("limit"); query = query.limit(limit); } - if (options.hasKey("offset")) { - // Android doesn't support offset - } - if (options.hasKey("selectFields")) { - // Android doesn't support selectFields - } +// if (options.hasKey("offset")) { + // Android doesn't support offset +// } +// if (options.hasKey("selectFields")) { + // Android doesn't support selectFields +// } if (options.hasKey("startAfter")) { - List startAfterList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAfter")); + List startAfterList = FirestoreSerialize.parseReadableArray( + firestore, + options.getArray("startAfter") + ); query = query.startAfter(startAfterList.toArray()); } + if (options.hasKey("startAt")) { - List startAtList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAt")); + List startAtList = FirestoreSerialize.parseReadableArray( + firestore, + options.getArray("startAt") + ); query = query.startAt(startAtList.toArray()); } + return query; } /** * Handles documentSnapshot events. * - * @param listenerId - * @param querySnapshot + * @param listenerId id + * @param querySnapshot snapshot */ - private void handleQuerySnapshotEvent(String listenerId, QuerySnapshot querySnapshot) { - WritableMap event = Arguments.createMap(); - WritableMap data = FirestoreSerialize.snapshotToWritableMap(querySnapshot); + private void handleQuerySnapshotEvent(final String listenerId, QuerySnapshot querySnapshot) { + @SuppressLint("StaticFieldLeak") final QuerySnapshotSerializeAsyncTask serializeAsyncTask = new QuerySnapshotSerializeAsyncTask( + reactContext, this + ) { + @Override + protected void onPostExecute(WritableMap writableMap) { + WritableMap event = Arguments.createMap(); + event.putString("path", path); + event.putString("appName", appName); + event.putString("listenerId", listenerId); + event.putMap("querySnapshot", writableMap); + Utils.sendEvent(reactContext, "firestore_collection_sync_event", event); + } + }; - event.putString("appName", appName); - event.putString("path", path); - event.putString("listenerId", listenerId); - event.putMap("querySnapshot", data); - - Utils.sendEvent(reactContext, "firestore_collection_sync_event", event); + serializeAsyncTask.execute(querySnapshot); } /** * Handles a documentSnapshot error event * - * @param listenerId - * @param exception + * @param listenerId id + * @param exception exception */ private void handleQuerySnapshotError(String listenerId, FirebaseFirestoreException exception) { WritableMap event = Arguments.createMap(); 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 ebb87a8d..2690afa1 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java @@ -1,5 +1,6 @@ package io.invertase.firebase.firestore; +import android.annotation.SuppressLint; import android.support.annotation.NonNull; import android.util.Log; @@ -31,17 +32,24 @@ public class RNFirebaseFirestoreDocumentReference { private final String appName; private final String path; - private ReactContext reactContext; private final DocumentReference ref; + private ReactContext reactContext; RNFirebaseFirestoreDocumentReference(ReactContext reactContext, String appName, String path) { - this.appName = appName; this.path = path; + this.appName = appName; this.reactContext = reactContext; this.ref = RNFirebaseFirestore.getFirestoreForApp(appName).document(path); } - public void delete(final Promise promise) { + static void offSnapshot(final String listenerId) { + ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { + listenerRegistration.remove(); + } + } + + void delete(final Promise promise) { this.ref.delete().addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { @@ -50,7 +58,10 @@ public class RNFirebaseFirestoreDocumentReference { promise.resolve(null); } else { Log.e(TAG, "delete:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); @@ -58,6 +69,7 @@ public class RNFirebaseFirestoreDocumentReference { void get(final ReadableMap getOptions, final Promise promise) { Source source; + if (getOptions != null && getOptions.hasKey("source")) { String optionsSource = getOptions.getString("source"); if ("server".equals(optionsSource)) { @@ -70,45 +82,57 @@ public class RNFirebaseFirestoreDocumentReference { } else { source = Source.DEFAULT; } + + @SuppressLint("StaticFieldLeak") final DocumentSnapshotSerializeAsyncTask serializeAsyncTask = new DocumentSnapshotSerializeAsyncTask( + reactContext, this + ) { + @Override + protected void onPostExecute(WritableMap writableMap) { + promise.resolve(writableMap); + } + }; + this.ref.get(source).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { Log.d(TAG, "get:onComplete:success"); - WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult()); - promise.resolve(data); + serializeAsyncTask.execute(task.getResult()); } else { Log.e(TAG, "get:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); } - public static void offSnapshot(final String listenerId) { - ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId); - if (listenerRegistration != null) { - listenerRegistration.remove(); - } - } - - public void onSnapshot(final String listenerId, final ReadableMap docListenOptions) { + void onSnapshot(final String listenerId, final ReadableMap docListenOptions) { if (!documentSnapshotListeners.containsKey(listenerId)) { final EventListener listener = new EventListener() { @Override - public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException exception) { + public void onEvent( + DocumentSnapshot documentSnapshot, + FirebaseFirestoreException exception + ) { if (exception == null) { handleDocumentSnapshotEvent(listenerId, documentSnapshot); } else { ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId); + if (listenerRegistration != null) { listenerRegistration.remove(); } + handleDocumentSnapshotError(listenerId, exception); } } }; + MetadataChanges metadataChanges; + if (docListenOptions != null && docListenOptions.hasKey("includeMetadataChanges") && docListenOptions.getBoolean("includeMetadataChanges")) { @@ -116,19 +140,30 @@ public class RNFirebaseFirestoreDocumentReference { } else { metadataChanges = MetadataChanges.EXCLUDE; } - ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(metadataChanges, listener); + + ListenerRegistration listenerRegistration = this.ref.addSnapshotListener( + metadataChanges, + listener + ); + documentSnapshotListeners.put(listenerId, listenerRegistration); } } public void set(final ReadableMap data, final ReadableMap options, final Promise promise) { - Map map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); Task task; + + Map map = FirestoreSerialize.parseReadableMap( + RNFirebaseFirestore.getFirestoreForApp(appName), + data + ); + if (options != null && options.hasKey("merge") && options.getBoolean("merge")) { task = this.ref.set(map, SetOptions.merge()); } else { task = this.ref.set(map); } + task.addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { @@ -137,14 +172,21 @@ public class RNFirebaseFirestoreDocumentReference { promise.resolve(null); } else { Log.e(TAG, "set:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); } - public void update(final ReadableMap data, final Promise promise) { - Map map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); + void update(final ReadableMap data, final Promise promise) { + Map map = FirestoreSerialize.parseReadableMap( + RNFirebaseFirestore.getFirestoreForApp(appName), + data + ); + this.ref.update(map).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { @@ -153,7 +195,10 @@ public class RNFirebaseFirestoreDocumentReference { promise.resolve(null); } else { Log.e(TAG, "update:onComplete:failure", task.getException()); - RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); + RNFirebaseFirestore.promiseRejectException( + promise, + (FirebaseFirestoreException) task.getException() + ); } } }); @@ -163,7 +208,7 @@ public class RNFirebaseFirestoreDocumentReference { * INTERNALS/UTILS */ - public DocumentReference getRef() { + DocumentReference getRef() { return ref; } @@ -174,32 +219,44 @@ public class RNFirebaseFirestoreDocumentReference { /** * Handles documentSnapshot events. * - * @param listenerId - * @param documentSnapshot + * @param listenerId id + * @param documentSnapshot snapshot */ - private void handleDocumentSnapshotEvent(String listenerId, DocumentSnapshot documentSnapshot) { - WritableMap event = Arguments.createMap(); - WritableMap data = FirestoreSerialize.snapshotToWritableMap(documentSnapshot); + private void handleDocumentSnapshotEvent( + final String listenerId, + DocumentSnapshot documentSnapshot + ) { + @SuppressLint("StaticFieldLeak") final DocumentSnapshotSerializeAsyncTask serializeAsyncTask = new DocumentSnapshotSerializeAsyncTask( + reactContext, this + ) { + @Override + protected void onPostExecute(WritableMap writableMap) { + WritableMap event = Arguments.createMap(); + event.putString("path", path); + event.putString("appName", appName); + event.putString("listenerId", listenerId); + event.putMap("documentSnapshot", writableMap); + Utils.sendEvent(reactContext, "firestore_document_sync_event", event); + } + }; - event.putString("appName", appName); - event.putString("path", path); - event.putString("listenerId", listenerId); - event.putMap("documentSnapshot", data); - - Utils.sendEvent(reactContext, "firestore_document_sync_event", event); + serializeAsyncTask.execute(documentSnapshot); } /** * Handles a documentSnapshot error event * - * @param listenerId - * @param exception + * @param listenerId id + * @param exception exception */ - private void handleDocumentSnapshotError(String listenerId, FirebaseFirestoreException exception) { + private void handleDocumentSnapshotError( + String listenerId, + FirebaseFirestoreException exception + ) { WritableMap event = Arguments.createMap(); - event.putString("appName", appName); event.putString("path", path); + event.putString("appName", appName); event.putString("listenerId", listenerId); event.putMap("error", RNFirebaseFirestore.getJSError(exception)); diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java index f971daea..22629c35 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestorePackage.java @@ -1,7 +1,6 @@ package io.invertase.firebase.firestore; import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.UIManagerModule; @@ -29,7 +28,7 @@ public class RNFirebaseFirestorePackage implements ReactPackage { } /** - * @param reactContext + * @param reactContext reactContext * @return a list of view managers that should be registered with {@link UIManagerModule} */ @Override diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreTransactionHandler.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreTransactionHandler.java index ef136ed7..dd908eb8 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreTransactionHandler.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreTransactionHandler.java @@ -17,17 +17,16 @@ import javax.annotation.Nullable; class RNFirebaseFirestoreTransactionHandler { + private final ReentrantLock lock; + private final Condition condition; + boolean aborted = false; + boolean timeout = false; private String appName; private long timeoutAt; private int transactionId; - private final ReentrantLock lock; - private final Condition condition; private ReadableArray commandBuffer; private Transaction firestoreTransaction; - boolean aborted = false; - boolean timeout = false; - RNFirebaseFirestoreTransactionHandler(String app, int id) { appName = app; transactionId = id; From 69fa5639c5741d975575102cb3c08f7e39e95332 Mon Sep 17 00:00:00 2001 From: Salakar Date: Wed, 1 Aug 2018 23:01:34 +0100 Subject: [PATCH 34/35] [android][firestore] implement Document and Query snapshot serialize async tasks --- .../DocumentSnapshotSerializeAsyncTask.java | 47 +++++++++++++++++++ .../QuerySnapshotSerializeAsyncTask.java | 47 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 android/src/main/java/io/invertase/firebase/firestore/DocumentSnapshotSerializeAsyncTask.java create mode 100644 android/src/main/java/io/invertase/firebase/firestore/QuerySnapshotSerializeAsyncTask.java diff --git a/android/src/main/java/io/invertase/firebase/firestore/DocumentSnapshotSerializeAsyncTask.java b/android/src/main/java/io/invertase/firebase/firestore/DocumentSnapshotSerializeAsyncTask.java new file mode 100644 index 00000000..ebf77162 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/DocumentSnapshotSerializeAsyncTask.java @@ -0,0 +1,47 @@ +package io.invertase.firebase.firestore; + +import android.os.AsyncTask; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; +import com.google.firebase.firestore.DocumentSnapshot; + +import java.lang.ref.WeakReference; + +class DocumentSnapshotSerializeAsyncTask extends AsyncTask { + private WeakReference reactContextWeakReference; + private WeakReference referenceWeakReference; + + DocumentSnapshotSerializeAsyncTask( + ReactContext context, + RNFirebaseFirestoreDocumentReference reference + ) { + referenceWeakReference = new WeakReference<>(reference); + reactContextWeakReference = new WeakReference<>(context); + } + + @Override + protected final WritableMap doInBackground(Object... params) { + DocumentSnapshot querySnapshot = (DocumentSnapshot) params[0]; + + try { + return FirestoreSerialize.snapshotToWritableMap(querySnapshot); + } catch (RuntimeException e) { + if (isAvailable()) { + reactContextWeakReference.get().handleException(e); + } else { + throw e; + } + return null; + } + } + + @Override + protected void onPostExecute(WritableMap writableMap) { + // do nothing as overridden on usage + } + + private Boolean isAvailable() { + return reactContextWeakReference.get() != null && referenceWeakReference.get() != null; + } +} diff --git a/android/src/main/java/io/invertase/firebase/firestore/QuerySnapshotSerializeAsyncTask.java b/android/src/main/java/io/invertase/firebase/firestore/QuerySnapshotSerializeAsyncTask.java new file mode 100644 index 00000000..4ddeab5f --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/firestore/QuerySnapshotSerializeAsyncTask.java @@ -0,0 +1,47 @@ +package io.invertase.firebase.firestore; + +import android.os.AsyncTask; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; +import com.google.firebase.firestore.QuerySnapshot; + +import java.lang.ref.WeakReference; + +class QuerySnapshotSerializeAsyncTask extends AsyncTask { + private WeakReference reactContextWeakReference; + private WeakReference referenceWeakReference; + + QuerySnapshotSerializeAsyncTask( + ReactContext context, + RNFirebaseFirestoreCollectionReference reference + ) { + referenceWeakReference = new WeakReference<>(reference); + reactContextWeakReference = new WeakReference<>(context); + } + + @Override + protected final WritableMap doInBackground(Object... params) { + QuerySnapshot querySnapshot = (QuerySnapshot) params[0]; + + try { + return FirestoreSerialize.snapshotToWritableMap(querySnapshot); + } catch (RuntimeException e) { + if (isAvailable()) { + reactContextWeakReference.get().handleException(e); + } else { + throw e; + } + return null; + } + } + + @Override + protected void onPostExecute(WritableMap writableMap) { + // do nothing as overridden on usage + } + + private Boolean isAvailable() { + return reactContextWeakReference.get() != null && referenceWeakReference.get() != null; + } +} From fc5c8f1af88b248a1145f68b9dd5d9c34ce95d3c Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Thu, 2 Aug 2018 17:09:36 +0100 Subject: [PATCH 35/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1945275b..21503692 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a ## Documentation -To check out our latest docs, visit [rnfirebase.io](https://rnfirebase.io) +To check out our latest docs, visit [https://invertase.io/oss/react-native-firebase](https://invertase.io/oss/react-native-firebase) ## Questions