Merge pull request #1369 from invertase/master
Update v4.3.x branch from master
This commit is contained in:
commit
e54f951e21
|
@ -2,7 +2,6 @@
|
|||
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.
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
|
@ -10,7 +9,7 @@ BEFORE YOU MAKE AN ISSUE
|
|||
|
||||
The issue list of this repo is exclusively for bug reports.
|
||||
|
||||
1) For feature requests, please use our Canny board: https://react-native-firebase.canny.io/feature-requests
|
||||
1) For feature requests please visit our [Feature Request Board](https://boards.invertase.io/react-native-firebase).
|
||||
|
||||
2) For questions and support please use our Discord chat: https://discord.gg/C9aK28N or Stack Overflow: https://stackoverflow.com/questions/tagged/react-native-firebase
|
||||
|
||||
|
@ -23,7 +22,7 @@ The issue list of this repo is exclusively for bug reports.
|
|||
|
||||
### Environment
|
||||
|
||||
1. Application Target Platform:
|
||||
1. Application Target Platform:
|
||||
|
||||
<!--- (e.g. iOS, Android, Both) --->
|
||||
|
||||
|
@ -47,14 +46,14 @@ The issue list of this repo is exclusively for bug reports.
|
|||
|
||||
<!--- (e.g. database, auth, messaging, analytics etc - or N/A if not applicable) --->
|
||||
|
||||
7. Are you using `typescript`?
|
||||
7. Are you using `typescript`?
|
||||
|
||||
<!--- yes/no --->
|
||||
|
||||
|
||||
---
|
||||
|
||||
Loving `react-native-firebase`? Please consider supporting them with any of the below:
|
||||
|
||||
- 👉 Back financially via [Open Collective](https://opencollective.com/react-native-firebase/donate)
|
||||
- 👉 Follow [`React Native Firebase`](https://twitter.com/rnfirebase) and [`Invertase`](https://twitter.com/invertaseio) on Twitter
|
||||
- 👉 Star this repo on GitHub ⭐️
|
||||
* 👉 Back financially via [Open Collective](https://opencollective.com/react-native-firebase/donate)
|
||||
* 👉 Follow [`React Native Firebase`](https://twitter.com/rnfirebase) and [`Invertase`](https://twitter.com/invertaseio) on Twitter
|
||||
* 👉 Star this repo on GitHub ⭐️
|
||||
|
|
|
@ -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).
|
||||
|
|
16
README.md
16
README.md
|
@ -49,7 +49,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
|
|||
| **Crashlytics** | ❌ | ✅ | ✅ | ❌ |
|
||||
| **Crash Reporting** | ✅ | ✅ | ✅ | ❌ |
|
||||
| **Dynamic Links** | ❌ | ✅ | ✅ | ❌ |
|
||||
| **[Functions Callable](https://firebase.googleblog.com/2018/04/launching-cloud-functions-for-firebase-1-0.html?m=1)** | ❌ | ❌ | ✅ | ✅ |
|
||||
| **[Functions Callable](https://firebase.googleblog.com/2018/04/launching-cloud-functions-for-firebase-1-0.html?m=1)** | ❌ | ❌ | ✅ | ✅ |
|
||||
| **Invites** | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Instance ID** | ❌ | ❌ | **?** | ❌ |
|
||||
| **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ |
|
||||
|
@ -64,17 +64,17 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
|
|||
|
||||
> The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase`.
|
||||
|
||||
| | 2.2.x | 3.3.x | 4.0.x | 4.1.x | 4.2.x |
|
||||
| -------------------- | -------- | -------- | -------- | -------- | -------- |
|
||||
| React Native | 0.47 + | 0.50 + | 0.52 + | 0.52 + | 0.52-55.x |
|
||||
| Firebase Android SDK | 11.0.0 + | 11.8.0 + | 12.0.0 + | 15.0.0 + | 15.0.0 + |
|
||||
| Firebase iOS SDK | 4.0.0 + | 4.7.0 + | 4.11.0 + | 4.13.0 + | 5.0.0 + |
|
||||
| | 2.2.x | 3.3.x | 4.0.x | 4.1.x | 4.2.x |
|
||||
| -------------------- | -------- | -------- | -------- | -------- | --------- |
|
||||
| React Native | 0.47 + | 0.50 + | 0.52 + | 0.52 + | 0.52-55.x |
|
||||
| Firebase Android SDK | 11.0.0 + | 11.8.0 + | 12.0.0 + | 15.0.0 + | 15.0.0 + |
|
||||
| Firebase iOS SDK | 4.0.0 + | 4.7.0 + | 4.11.0 + | 4.13.0 + | 5.0.0 + |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -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 [Feature Request Board](https://boards.invertase.io/react-native-firebase).
|
||||
|
||||
## Changelog
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
@ -156,4 +157,12 @@ public class Utils {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
return resourceId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -119,9 +120,13 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule {
|
|||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
promise.resolve("remoteConfigFetchStatusSuccess");
|
||||
promise.resolve(null);
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -154,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");
|
||||
|
|
|
@ -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<String, ChildEventListener> childEventListeners = new HashMap<>();
|
||||
private HashMap<String, ValueEventListener> 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<Object, Void, WritableMap> {
|
||||
|
||||
private WeakReference<ReactContext> reactContextWeakReference;
|
||||
private WeakReference<RNFirebaseDatabaseReference> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<Object, Void, WritableMap> {
|
||||
private WeakReference<ReactContext> reactContextWeakReference;
|
||||
private WeakReference<RNFirebaseFirestoreDocumentReference> 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;
|
||||
}
|
||||
}
|
|
@ -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<DocumentChange> 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<DocumentChange>
|
||||
* @return WritableArray
|
||||
*/
|
||||
static WritableArray documentChangesToWritableArray(List<DocumentChange> documentChanges) {
|
||||
private static WritableArray documentChangesToWritableArray(List<DocumentChange> 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<String, Object>
|
||||
* @return WritableMap
|
||||
*/
|
||||
static WritableMap objectMapToWritable(Map<String, Object> map) {
|
||||
private static WritableMap objectMapToWritable(Map<String, Object> map) {
|
||||
WritableMap writableMap = Arguments.createMap();
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
WritableMap typeMap = buildTypeMap(entry.getValue());
|
||||
|
@ -224,7 +222,10 @@ public class FirestoreSerialize {
|
|||
return typeMap;
|
||||
}
|
||||
|
||||
static Map<String, Object> parseReadableMap(FirebaseFirestore firestore, ReadableMap readableMap) {
|
||||
static Map<String, Object> parseReadableMap(
|
||||
FirebaseFirestore firestore,
|
||||
ReadableMap readableMap
|
||||
) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
if (readableMap != null) {
|
||||
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
|
||||
|
@ -290,7 +291,10 @@ public class FirestoreSerialize {
|
|||
}
|
||||
}
|
||||
|
||||
public static List<Object> parseDocumentBatches(FirebaseFirestore firestore, ReadableArray readableArray) {
|
||||
static List<Object> parseDocumentBatches(
|
||||
FirebaseFirestore firestore,
|
||||
ReadableArray readableArray
|
||||
) {
|
||||
List<Object> writes = new ArrayList<>(readableArray.size());
|
||||
for (int i = 0; i < readableArray.size(); i++) {
|
||||
Map<String, Object> write = new HashMap<>();
|
||||
|
|
|
@ -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<Object, Void, WritableMap> {
|
||||
private WeakReference<ReactContext> reactContextWeakReference;
|
||||
private WeakReference<RNFirebaseFirestoreCollectionReference> 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;
|
||||
}
|
||||
}
|
|
@ -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<Void>() {
|
||||
|
@ -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<String, Object> setData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||
Map<String, Object> 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<String, Object> updateData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||
Map<String, Object> 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<String, ListenerRegistration> 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<QuerySnapshot>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<QuerySnapshot> 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<QuerySnapshot> listener = new EventListener<QuerySnapshot>() {
|
||||
@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<fieldPathElements.size(); j++) {
|
||||
for (int j = 0; j < fieldPathElements.size(); j++) {
|
||||
fieldPathArray[j] = fieldPathElements.getString(j);
|
||||
}
|
||||
FieldPath fieldPath = FieldPath.of(fieldPathArray);
|
||||
|
@ -202,13 +223,13 @@ public class RNFirebaseFirestoreCollectionReference {
|
|||
Map<String, Object> order = (Map) o;
|
||||
String direction = (String) order.get("direction");
|
||||
Map<String, Object> 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<String> fieldPathElements = (List)fieldPathMap.get("elements");
|
||||
List<String> 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<Object> endAtList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endAt"));
|
||||
List<Object> endAtList = FirestoreSerialize.parseReadableArray(
|
||||
firestore,
|
||||
options.getArray("endAt")
|
||||
);
|
||||
query = query.endAt(endAtList.toArray());
|
||||
}
|
||||
|
||||
if (options.hasKey("endBefore")) {
|
||||
List<Object> endBeforeList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endBefore"));
|
||||
List<Object> 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<Object> startAfterList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAfter"));
|
||||
List<Object> startAfterList = FirestoreSerialize.parseReadableArray(
|
||||
firestore,
|
||||
options.getArray("startAfter")
|
||||
);
|
||||
query = query.startAfter(startAfterList.toArray());
|
||||
}
|
||||
|
||||
if (options.hasKey("startAt")) {
|
||||
List<Object> startAtList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAt"));
|
||||
List<Object> 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();
|
||||
|
|
|
@ -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<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> 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<DocumentSnapshot>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<DocumentSnapshot> 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<DocumentSnapshot> listener = new EventListener<DocumentSnapshot>() {
|
||||
@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<String, Object> map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||
Task<Void> task;
|
||||
|
||||
Map<String, Object> 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<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> 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<String, Object> map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||
void update(final ReadableMap data, final Promise promise) {
|
||||
Map<String, Object> map = FirestoreSerialize.parseReadableMap(
|
||||
RNFirebaseFirestore.getFirestoreForApp(appName),
|
||||
data
|
||||
);
|
||||
|
||||
this.ref.update(map).addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> 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));
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -192,7 +192,7 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
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<String> people = android.getStringArrayList("people");
|
||||
|
@ -279,13 +279,18 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
|
|
@ -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;
|
||||
|
@ -153,7 +154,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)) {
|
||||
|
@ -195,6 +196,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<Bundle> bundles = getScheduledNotifications();
|
||||
|
@ -320,15 +330,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");
|
||||
|
@ -361,14 +375,33 @@ 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);
|
||||
Calendar pastFireDate = Calendar.getInstance();
|
||||
pastFireDate.setTimeInMillis(fireDate);
|
||||
|
||||
newFireDate.add(Calendar.DATE, 1);
|
||||
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;
|
||||
}
|
||||
|
||||
fireDate = newFireDate.getTimeInMillis();
|
||||
}
|
||||
|
@ -401,14 +434,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
@ -112,6 +115,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
|
||||
|
@ -264,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<String, String> e : message.getData().entrySet()) {
|
||||
dataMap.putString(e.getKey(), e.getValue());
|
||||
|
@ -277,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
|
||||
|
@ -296,12 +308,39 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
|||
}
|
||||
if (notification.getTag() != null) {
|
||||
androidMap.putString("group", notification.getTag());
|
||||
androidMap.putString("tag", notification.getTag());
|
||||
}
|
||||
notificationMap.putMap("android", androidMap);
|
||||
|
||||
return notificationMap;
|
||||
}
|
||||
|
||||
private @Nullable String getNotificationBody(RemoteMessage.Notification notification) {
|
||||
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 {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoteNotificationReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -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,9 +59,9 @@ 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));
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
@ -63,9 +73,9 @@ 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));
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -372,7 +372,7 @@ RCT_EXPORT_METHOD(setBadge:(NSInteger) number
|
|||
resolve(nil);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
jsReady = TRUE;
|
||||
resolve(nil);
|
||||
|
@ -487,11 +487,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];
|
||||
|
|
|
@ -1089,8 +1089,8 @@ declare module 'react-native-firebase' {
|
|||
interface InstanceId {
|
||||
delete(): Promise<void>;
|
||||
get(): Promise<string>;
|
||||
getToken(authorizedEntity: string, scope: string): Promise<string>;
|
||||
deleteToken(authorizedEntity: string, scope: string): Promise<void>;
|
||||
getToken(authorizedEntity?: string, scope?: string): Promise<string>;
|
||||
deleteToken(authorizedEntity?: string, scope?: string): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1213,6 +1213,7 @@ declare module 'react-native-firebase' {
|
|||
showWhen?: boolean;
|
||||
smallIcon?: any;
|
||||
sortKey?: string;
|
||||
tag?: string;
|
||||
ticker?: string;
|
||||
timeoutAfter?: number;
|
||||
usesChronometer?: boolean;
|
||||
|
@ -1265,6 +1266,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;
|
||||
|
|
|
@ -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<void> {
|
||||
return getNativeModule(this).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Instance ID.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
get(): Promise<string> {
|
||||
return getNativeModule(this).get();
|
||||
}
|
||||
|
||||
getToken(authorizedEntity: string, scope: string): Promise<string> {
|
||||
return getNativeModule(this).getToken(authorizedEntity, scope);
|
||||
/**
|
||||
* Delete the current Instance ID.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
delete(): Promise<void> {
|
||||
return getNativeModule(this).delete();
|
||||
}
|
||||
|
||||
deleteToken(authorizedEntity: string, scope: string): Promise<void> {
|
||||
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<string>}
|
||||
*/
|
||||
getToken(authorizedEntity?: string, scope?: string): Promise<string> {
|
||||
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<void>}
|
||||
*/
|
||||
deleteToken(authorizedEntity?: string, scope?: string): Promise<void> {
|
||||
return getNativeModule(this).deleteToken(
|
||||
authorizedEntity || this.app.options.messagingSenderId,
|
||||
scope || '*'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -719,6 +735,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,
|
||||
|
|
|
@ -92,6 +92,20 @@ export default class AndroidNotifications {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
removeDeliveredNotificationsByTag(tag: string): Promise<void> {
|
||||
if (Platform.OS === 'android') {
|
||||
if (typeof tag !== 'string') {
|
||||
throw new Error(
|
||||
`AndroidNotifications:removeDeliveredNotificationsByTag expects an 'string' but got type ${typeof tag}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(
|
||||
this._notifications
|
||||
).removeDeliveredNotificationsByTag(tag);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
deleteChannelGroup(groupId: string): Promise<void> {
|
||||
if (Platform.OS === 'android') {
|
||||
if (typeof groupId !== 'string') {
|
||||
|
@ -99,9 +113,7 @@ export default class AndroidNotifications {
|
|||
`AndroidNotifications:deleteChannelGroup expects an 'string' but got type ${typeof groupId}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this._notifications).deleteChannelGroup(
|
||||
groupId
|
||||
);
|
||||
return getNativeModule(this._notifications).deleteChannelGroup(groupId);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -113,9 +125,7 @@ export default class AndroidNotifications {
|
|||
`AndroidNotifications:deleteChannel expects an 'string' but got type ${typeof channelId}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this._notifications).deleteChannel(
|
||||
channelId
|
||||
);
|
||||
return getNativeModule(this._notifications).deleteChannel(channelId);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -170,6 +170,7 @@ export type NativeAndroidNotification = {|
|
|||
smallIcon: SmallIcon,
|
||||
sortKey?: string,
|
||||
// TODO: style: Style,
|
||||
tag?: string,
|
||||
ticker?: string,
|
||||
timeoutAfter?: number,
|
||||
usesChronometer?: boolean,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-native-firebase",
|
||||
"version": "4.3.7",
|
||||
"version": "4.3.8",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-native-firebase",
|
||||
"version": "4.3.7",
|
||||
"version": "4.3.8",
|
||||
"author": "Invertase <contact@invertase.io> (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",
|
||||
|
|
Loading…
Reference in New Issue