Merge branch 'master' into background-notification-fix-ios
2
.babelrc
|
@ -7,7 +7,7 @@
|
|||
]
|
||||
},
|
||||
"publish": {
|
||||
"presets": ["react-native-syntax"],
|
||||
"presets": ["@invertase/react-native-syntax"],
|
||||
"plugins": [
|
||||
"transform-flow-strip-types"
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
src/version.js
|
|
@ -30,7 +30,8 @@
|
|||
"no-plusplus": 0,
|
||||
"no-undef": 0,
|
||||
"no-underscore-dangle": "off",
|
||||
"no-use-before-define": 0
|
||||
"no-use-before-define": 0,
|
||||
"import/no-cycle": 0
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": true,
|
||||
|
|
20
.flowconfig
|
@ -22,9 +22,6 @@
|
|||
# Ignore dist folder
|
||||
.*/dist/.*
|
||||
|
||||
# Ignore tests project
|
||||
.*/tests/.*
|
||||
|
||||
# Ignore bridge project
|
||||
.*/bridge/.*
|
||||
|
||||
|
@ -38,7 +35,22 @@ node_modules/react-native/flow-github/
|
|||
[options]
|
||||
emoji=true
|
||||
|
||||
# Taken from: https://github.com/facebook/react-native/issues/19766
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# keep the following in sync with server/haste/hasteImpl.js
|
||||
# get basename
|
||||
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
|
||||
# strip .js or .js.flow suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
|
||||
# strip .ios suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
|
||||
module.system.haste.paths.blacklist=.*/__tests__/.*
|
||||
module.system.haste.paths.blacklist=.*/__mocks__/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
|
@ -60,4 +72,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
|||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[version]
|
||||
^0.65.0
|
||||
^0.78.0
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -15,7 +15,7 @@ project.xcworkspace/
|
|||
xcuserdata/
|
||||
|
||||
# Config files
|
||||
.babelrc
|
||||
# .babelrc
|
||||
.editorconfig
|
||||
.eslintrc
|
||||
.flowconfig
|
||||
|
@ -87,3 +87,4 @@ CONTRIBUTING.md
|
|||
CODE_OF_CONDUCT.md
|
||||
android/.settings
|
||||
README.md
|
||||
src
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"ignore_dirs": [
|
||||
".git",
|
||||
"node_modules",
|
||||
"android/build",
|
||||
"android/.idea",
|
||||
"ios/.idea",
|
||||
"android/.gradle",
|
||||
"android/gradle",
|
||||
".idea"
|
||||
]
|
||||
}
|
35
README.md
|
@ -17,15 +17,11 @@
|
|||
|
||||
## Introduction
|
||||
|
||||
**RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple. It is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the Firebase Web SDK as closely as possible.
|
||||
**React Native Firebase** is a _light-weight_ javascript layer connecting you to the native Firebase SDKs for both iOS and Android which aimes to mirror the offical Firebase Web SDK as closely as possible.
|
||||
|
||||
Although the [Firebase Web SDK](https://www.npmjs.com/package/firebase) library will work with React Native, it is mainly built for the web.
|
||||
Although the official [Firebase JS SDK](https://www.npmjs.com/package/firebase) will work with React Native; it is mainly built for the web and has a limited feature-set compared to native.
|
||||
|
||||
RNFirebase provides a JavaScript bridge to the native Firebase SDKs for both iOS and Android therefore Firebase will run on the native thread, allowing the rest of your app to run on the [JS thread](https://facebook.github.io/react-native/docs/performance.html#javascript-frame-rate). The Firebase Web SDK also runs on the JS thread, therefore potentially affecting the frame rate causing jank with animations, touch events etc.
|
||||
|
||||
The native SDKs also allow us to hook into device sdk's which are not possible with the web SDK, for example crash reporting, offline realtime database support, analytics and more!
|
||||
|
||||
All in all, RNFirebase provides much faster performance (~2x) over the web SDK and provides device sdk's not found in the web sdk (see the feature table below).
|
||||
Using the native Firebase SDKs with **React Native Firebase** allows you to consume device SDKs which don't exist on the Firebase JS SDK - for example; Remote Config, Performance Monitoring, Dynamic Links, Analytics and more (see the feature table below for comparison).
|
||||
|
||||
---
|
||||
|
||||
|
@ -35,7 +31,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
|
|||
|
||||
> '**?**' indicates partial support
|
||||
|
||||
| Firebase Features | v2.2.x | v3.3.x | v4.2.x | Web SDK |
|
||||
| Firebase Features | v2.2.x | v3.3.x | v4.3.x | Web SDK |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------- | :----: | :----: | :----: | :-----: |
|
||||
| **AdMob** | ✅ | ✅ | ✅ | ❌ |
|
||||
| **Analytics** | ✅ | ✅ | ✅ | ❌ |
|
||||
|
@ -47,9 +43,8 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
|
|||
| **Cloud Firestore** | ❌ | ✅ | ✅ | **?** |
|
||||
| **Cloud Messaging (FCM)** | **?** | **?** | ✅ | ❌ |
|
||||
| **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 +59,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 + |
|
||||
| | 3.3.x | 4.3.x | 5.0.x |
|
||||
| ------------------------- | :------: | :-----: | :---: |
|
||||
| React Native | 0.50 + | 0.52-55 | 0.56+ |
|
||||
| Play Services Android SDK | 11.8.0 + | 15.0.1 | TBC |
|
||||
| Firebase iOS SDK | 4.7.0 + | 5.3.0 | TBC |
|
||||
|
||||
---
|
||||
|
||||
## 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,19 +81,19 @@ 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
|
||||
|
||||
Detailed changes for each release are documented in the [releases notes](https://github.com/invertase/react-native-firebase/releases).
|
||||
|
||||
<hr>
|
||||
----
|
||||
|
||||
## Supporting RNFirebase
|
||||
|
||||
RNFirebase is an Apache-2.0 licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [sponsors](#sponsors) and [backers](#backers). If you'd like to join them, please consider:
|
||||
|
||||
* [Become a backer or sponsor on Open Collective](https://opencollective.com/react-native-firebase).
|
||||
- [Become a backer or sponsor on Open Collective](https://opencollective.com/react-native-firebase).
|
||||
|
||||
### Sponsors
|
||||
|
||||
|
@ -133,4 +128,4 @@ Thank you to all the people who have already contributed to RNFirebase!
|
|||
|
||||
## License
|
||||
|
||||
* See [LICENSE](/LICENSE)
|
||||
- See [LICENSE](/LICENSE)
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 183 197" style="enable-background:new 0 0 183 197;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#E88634;}
|
||||
.st1{fill:#F9CB52;}
|
||||
.st2{fill:#F4A73E;}
|
||||
</style>
|
||||
<title>Slice 1</title>
|
||||
<g>
|
||||
<path class="st0" d="M1,98c0.6,20.7,40.7,37,90,37v-8c-39.7-0.2-81.3-12.4-82-29C9,98,1,98,1,98z"/>
|
||||
<path class="st1" d="M135.5,21.8c-18.3-9.8-52.4,16.7-77,59.4l6.9,4c20-34.3,51.4-64.3,66.1-56.5L135.5,21.8L135.5,21.8z"/>
|
||||
<path class="st0" d="M1,98c0.6-20.7,40.7-37,90-37v8C51.3,69.2,9.7,81.4,9,98C9,98,1,98,1,98z"/>
|
||||
<path class="st2" d="M46,20c-17.6,10.9-11.7,53.7,13,96.4l6.9-4C46.3,78,36,35.8,50,26.9L46,20L46,20z"/>
|
||||
<path class="st1" d="M46,176.4C28.4,165.5,34.3,122.7,59,80l6.9,4c-19.7,34.5-30,76.6-15.9,85.5L46,176.4L46,176.4z"/>
|
||||
<path class="st2" d="M45,20.5c18.3-9.8,52.4,16.7,77,59.4l-6.9,4C95.1,49.7,63.7,19.7,49,27.4L45,20.5z"/>
|
||||
<path class="st0" d="M181,98c-0.6-20.7-40.7-37-90-37v8c39.7,0.2,81.3,12.4,82,29H181z"/>
|
||||
<path class="st1" d="M45.2,176.5c0.1,0.1,0.2,0.1,0.3,0.2c18.1,10.5,52.6-16.1,77.5-59.2l-6.9-4.1C96,148,64.2,178.2,49.6,169.8
|
||||
c-0.1-0.1-0.3-0.2-0.4-0.3L45.2,176.5L45.2,176.5z"/>
|
||||
<path class="st2" d="M136,175.9c-18.3,9.8-52.4-16.7-77-59.4l-0.3-0.4l6.8-4.2c20,34.5,51.6,64.9,66.4,57.1L136,175.9L136,175.9z"
|
||||
/>
|
||||
<path class="st0" d="M181,97c0,0.2,0,0.3,0,0.5c0,20.9-40.3,37.5-90,37.5v-8c40-0.2,82-12.6,82-29.4c0-0.2,0-0.4,0-0.6H181z"/>
|
||||
<path class="st2" d="M135,176.4c17.6-10.9,11.7-53.7-13-96.4l-0.4-0.8l-7,3.8l0,0.1c20,34.6,30.5,77.2,16.5,86.3L135,176.4z"/>
|
||||
<path class="st1" d="M135.5,21.3c17.6,10.9,11.7,53.7-13,96.4l-6.9-4c19.7-34.5,30-76.6,15.9-85.5
|
||||
C131.5,28.2,135.5,21.3,135.5,21.3z"/>
|
||||
<path class="st2" d="M81,103.1c0,5.5,4.5,10,10,10l0,0c5.5,0,10-4.5,10-10c0-5.6-2.8-10-0.9-13.9c0,0-3,0.6-4.7,5
|
||||
c0,0-5.6-5.6-3.2-13.4c0,0-5.7,2.2-5.1,9.5c0.5,6.2-0.6,7.3-0.6,7.3c0-5-3.9-7.3-3.9-7.3C83.7,96.4,81,98.1,81,103.1z"/>
|
||||
<path class="st1" d="M98.2,105.5c0,4.2-3.2,7.7-7.4,7.7c-3.9-0.1-6.9-3.1-6.5-7c0.1-1.7,1.5-4.6,3.6-7.4l1.4,0.9
|
||||
c0-3.5,1.6-8.2,1.6-8.2C92.1,96.4,98.2,97.7,98.2,105.5z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB |
|
@ -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,30 +1,30 @@
|
|||
package io.invertase.firebase;
|
||||
|
||||
import android.util.Log;
|
||||
import android.app.Activity;
|
||||
import android.content.IntentSender;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
|
||||
// react
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
// play services
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.FirebaseOptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// react
|
||||
// play services
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class RNFirebaseModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebase";
|
||||
|
@ -103,7 +103,9 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule {
|
|||
if (status != ConnectionResult.SUCCESS && gapi.isUserResolvableError(status)) {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity != null) {
|
||||
gapi.getErrorDialog(activity, status, status).show();
|
||||
gapi
|
||||
.getErrorDialog(activity, status, status)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +115,9 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule {
|
|||
*/
|
||||
@ReactMethod
|
||||
public void resolutionForPlayServices() {
|
||||
int status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(getReactApplicationContext());
|
||||
int status = GoogleApiAvailability
|
||||
.getInstance()
|
||||
.isGooglePlayServicesAvailable(getReactApplicationContext());
|
||||
ConnectionResult connectionResult = new ConnectionResult(status);
|
||||
|
||||
if (!connectionResult.isSuccess() && connectionResult.hasResolution()) {
|
||||
|
|
|
@ -8,9 +8,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
|||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RNFirebasePackage implements ReactPackage {
|
||||
|
|
|
@ -4,16 +4,15 @@ import android.app.ActivityManager;
|
|||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
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.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -47,7 +46,9 @@ public class Utils {
|
|||
if (value == null) {
|
||||
map.putNull(key);
|
||||
} else {
|
||||
String type = value.getClass().getName();
|
||||
String type = value
|
||||
.getClass()
|
||||
.getName();
|
||||
switch (type) {
|
||||
case "java.lang.Boolean":
|
||||
map.putBoolean(key, (Boolean) value);
|
||||
|
@ -156,4 +157,14 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,13 @@ import java.util.Map;
|
|||
public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebaseAdMob";
|
||||
private HashMap<String, RNFirebaseAdmobInterstitial> interstitials = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseAdMobRewardedVideo> rewardedVideos = new HashMap<>();
|
||||
|
||||
public RNFirebaseAdMob(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
ReactApplicationContext getContext() {
|
||||
return getReactApplicationContext();
|
||||
|
@ -27,14 +34,6 @@ public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
|||
return getCurrentActivity();
|
||||
}
|
||||
|
||||
private HashMap<String, RNFirebaseAdmobInterstitial> interstitials = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseAdMobRewardedVideo> rewardedVideos = new HashMap<>();
|
||||
|
||||
public RNFirebaseAdMob(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
|
@ -53,7 +52,9 @@ public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
|||
@ReactMethod
|
||||
public void interstitialLoadAd(String adUnit, ReadableMap request) {
|
||||
RNFirebaseAdmobInterstitial interstitial = getOrCreateInterstitial(adUnit);
|
||||
interstitial.loadAd(RNFirebaseAdMobUtils.buildRequest(request).build());
|
||||
interstitial.loadAd(RNFirebaseAdMobUtils
|
||||
.buildRequest(request)
|
||||
.build());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -65,7 +66,9 @@ public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
|||
@ReactMethod
|
||||
public void rewardedVideoLoadAd(String adUnit, ReadableMap request) {
|
||||
RNFirebaseAdMobRewardedVideo rewardedVideo = getOrCreateRewardedVideo(adUnit);
|
||||
rewardedVideo.loadAd(RNFirebaseAdMobUtils.buildRequest(request).build());
|
||||
rewardedVideo.loadAd(RNFirebaseAdMobUtils
|
||||
.buildRequest(request)
|
||||
.build());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -75,7 +78,6 @@ public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param adUnit
|
||||
* @return
|
||||
*/
|
||||
|
@ -89,7 +91,6 @@ public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param adUnit
|
||||
* @return
|
||||
*/
|
||||
|
|
|
@ -23,38 +23,15 @@ public class RNFirebaseAdMobBanner extends SimpleViewManager<ReactViewGroup> {
|
|||
|
||||
public static final String REACT_CLASS = "RNFirebaseAdMobBanner";
|
||||
public static final String BANNER_EVENT = "onBannerEvent";
|
||||
|
||||
public enum Events {
|
||||
EVENT_AD_SIZE_CHANGE("onSizeChange"),
|
||||
EVENT_AD_LOADED("onAdLoaded"),
|
||||
EVENT_AD_FAILED_TO_LOAD("onAdFailedToLoad"),
|
||||
EVENT_AD_OPENED("onAdOpened"),
|
||||
EVENT_AD_CLOSED("onAdClosed"),
|
||||
EVENT_AD_LEFT_APPLICATION("onAdLeftApplication");
|
||||
|
||||
private final String event;
|
||||
|
||||
Events(final String name) {
|
||||
event = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
private ThemedReactContext context;
|
||||
private ReactViewGroup viewGroup;
|
||||
private RCTEventEmitter emitter;
|
||||
private Boolean requested = false;
|
||||
|
||||
// Internal prop values
|
||||
private AdRequest.Builder request;
|
||||
private AdSize size;
|
||||
private String unitId;
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
|
@ -197,15 +174,22 @@ public class RNFirebaseAdMobBanner extends SimpleViewManager<ReactViewGroup> {
|
|||
int left = adView.getLeft();
|
||||
int top = adView.getTop();
|
||||
|
||||
int width = adView.getAdSize().getWidthInPixels(context);
|
||||
int height = adView.getAdSize().getHeightInPixels(context);
|
||||
int width = adView
|
||||
.getAdSize()
|
||||
.getWidthInPixels(context);
|
||||
int height = adView
|
||||
.getAdSize()
|
||||
.getHeightInPixels(context);
|
||||
|
||||
adView.measure(width, height);
|
||||
adView.layout(left, top, left + width, top + height);
|
||||
|
||||
WritableMap payload = Arguments.createMap();
|
||||
|
||||
payload.putBoolean(RNFirebaseAdMobNativeExpress.Events.EVENT_AD_VIDEO_CONTENT.toString(), false);
|
||||
payload.putBoolean(
|
||||
RNFirebaseAdMobNativeExpress.Events.EVENT_AD_VIDEO_CONTENT.toString(),
|
||||
false
|
||||
);
|
||||
payload.putInt("width", width);
|
||||
payload.putInt("height", height);
|
||||
|
||||
|
@ -251,4 +235,24 @@ public class RNFirebaseAdMobBanner extends SimpleViewManager<ReactViewGroup> {
|
|||
|
||||
emitter.receiveEvent(viewGroup.getId(), BANNER_EVENT, event);
|
||||
}
|
||||
|
||||
public enum Events {
|
||||
EVENT_AD_SIZE_CHANGE("onSizeChange"),
|
||||
EVENT_AD_LOADED("onAdLoaded"),
|
||||
EVENT_AD_FAILED_TO_LOAD("onAdFailedToLoad"),
|
||||
EVENT_AD_OPENED("onAdOpened"),
|
||||
EVENT_AD_CLOSED("onAdClosed"),
|
||||
EVENT_AD_LEFT_APPLICATION("onAdLeftApplication");
|
||||
|
||||
private final String event;
|
||||
|
||||
Events(final String name) {
|
||||
event = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,38 +25,10 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
public static final String REACT_CLASS = "RNFirebaseAdMobNativeExpress";
|
||||
public static final String BANNER_EVENT = "onBannerEvent";
|
||||
|
||||
public enum Events {
|
||||
EVENT_AD_SIZE_CHANGE("onSizeChange"),
|
||||
EVENT_AD_LOADED("onAdLoaded"),
|
||||
EVENT_AD_FAILED_TO_LOAD("onAdFailedToLoad"),
|
||||
EVENT_AD_OPENED("onAdOpened"),
|
||||
EVENT_AD_CLOSED("onAdClosed"),
|
||||
EVENT_AD_LEFT_APPLICATION("onAdLeftApplication"),
|
||||
EVENT_AD_VIDEO_END("onVideoEnd"),
|
||||
EVENT_AD_VIDEO_MUTE("onVideoMute"),
|
||||
EVENT_AD_VIDEO_PAUSE("onVideoPause"),
|
||||
EVENT_AD_VIDEO_PLAY("onVideoPlay"),
|
||||
EVENT_AD_VIDEO_START("onVideoStart"),
|
||||
EVENT_AD_VIDEO_CONTENT("hasVideoContent");
|
||||
|
||||
private final String event;
|
||||
|
||||
Events(final String name) {
|
||||
event = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
private ThemedReactContext context;
|
||||
private ReactViewGroup viewGroup;
|
||||
private RCTEventEmitter emitter;
|
||||
private Boolean requested = false;
|
||||
|
||||
// Internal prop values
|
||||
private AdRequest.Builder request;
|
||||
private VideoOptions.Builder videoOptions;
|
||||
|
@ -70,6 +42,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Create & return view instance
|
||||
*
|
||||
* @param themedReactContext
|
||||
* @return
|
||||
*/
|
||||
|
@ -105,6 +78,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Declare custom events
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
|
@ -116,6 +90,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Handle unitId prop
|
||||
*
|
||||
* @param view
|
||||
* @param value
|
||||
*/
|
||||
|
@ -127,6 +102,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Handle request prop
|
||||
*
|
||||
* @param view
|
||||
* @param map
|
||||
*/
|
||||
|
@ -138,6 +114,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Handle video prop
|
||||
*
|
||||
* @param view
|
||||
* @param map
|
||||
*/
|
||||
|
@ -149,6 +126,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Handle size prop
|
||||
*
|
||||
* @param view
|
||||
* @param value
|
||||
*/
|
||||
|
@ -210,8 +188,12 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
int left = adView.getLeft();
|
||||
int top = adView.getTop();
|
||||
|
||||
int width = adView.getAdSize().getWidthInPixels(context);
|
||||
int height = adView.getAdSize().getHeightInPixels(context);
|
||||
int width = adView
|
||||
.getAdSize()
|
||||
.getWidthInPixels(context);
|
||||
int height = adView
|
||||
.getAdSize()
|
||||
.getHeightInPixels(context);
|
||||
|
||||
adView.measure(width, height);
|
||||
adView.layout(left, top, left + width, top + height);
|
||||
|
@ -230,17 +212,21 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
public void onVideoEnd() {
|
||||
sendEvent(Events.EVENT_AD_VIDEO_END.toString(), null);
|
||||
}
|
||||
|
||||
public void onVideoMute(boolean isMuted) {
|
||||
WritableMap videoMutePayload = Arguments.createMap();
|
||||
videoMutePayload.putBoolean("isMuted", isMuted);
|
||||
sendEvent(Events.EVENT_AD_VIDEO_MUTE.toString(), videoMutePayload);
|
||||
}
|
||||
|
||||
public void onVideoPause() {
|
||||
sendEvent(Events.EVENT_AD_VIDEO_PAUSE.toString(), null);
|
||||
}
|
||||
|
||||
public void onVideoPlay() {
|
||||
sendEvent(Events.EVENT_AD_VIDEO_PLAY.toString(), null);
|
||||
}
|
||||
|
||||
public void onVideoStart() {
|
||||
sendEvent(Events.EVENT_AD_VIDEO_START.toString(), null);
|
||||
}
|
||||
|
@ -273,6 +259,7 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
|
||||
/**
|
||||
* Sends an event back to the JS component to handle
|
||||
*
|
||||
* @param type
|
||||
* @param payload
|
||||
*/
|
||||
|
@ -287,4 +274,30 @@ public class RNFirebaseAdMobNativeExpress extends SimpleViewManager<ReactViewGro
|
|||
int id = viewGroup.getId();
|
||||
emitter.receiveEvent(viewGroup.getId(), BANNER_EVENT, event);
|
||||
}
|
||||
|
||||
public enum Events {
|
||||
EVENT_AD_SIZE_CHANGE("onSizeChange"),
|
||||
EVENT_AD_LOADED("onAdLoaded"),
|
||||
EVENT_AD_FAILED_TO_LOAD("onAdFailedToLoad"),
|
||||
EVENT_AD_OPENED("onAdOpened"),
|
||||
EVENT_AD_CLOSED("onAdClosed"),
|
||||
EVENT_AD_LEFT_APPLICATION("onAdLeftApplication"),
|
||||
EVENT_AD_VIDEO_END("onVideoEnd"),
|
||||
EVENT_AD_VIDEO_MUTE("onVideoMute"),
|
||||
EVENT_AD_VIDEO_PAUSE("onVideoPause"),
|
||||
EVENT_AD_VIDEO_PLAY("onVideoPlay"),
|
||||
EVENT_AD_VIDEO_START("onVideoStart"),
|
||||
EVENT_AD_VIDEO_CONTENT("hasVideoContent");
|
||||
|
||||
private final String event;
|
||||
|
||||
Events(final String name) {
|
||||
event = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.invertase.firebase.admob;
|
||||
|
||||
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;
|
||||
|
@ -9,7 +8,6 @@ import com.facebook.react.uimanager.ViewManager;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
|
|
@ -18,6 +18,7 @@ class RNFirebaseAdMobUtils {
|
|||
|
||||
/**
|
||||
* Convert common AdMob errors into a standard format
|
||||
*
|
||||
* @param errorCode
|
||||
* @return
|
||||
*/
|
||||
|
@ -27,11 +28,17 @@ class RNFirebaseAdMobUtils {
|
|||
switch (errorCode) {
|
||||
case AdRequest.ERROR_CODE_INTERNAL_ERROR:
|
||||
map.putString("code", "admob/error-code-internal-error");
|
||||
map.putString("message", "Something happened internally; for instance, an invalid response was received from the ad server.");
|
||||
map.putString(
|
||||
"message",
|
||||
"Something happened internally; for instance, an invalid response was received from the ad server."
|
||||
);
|
||||
break;
|
||||
case AdRequest.ERROR_CODE_INVALID_REQUEST:
|
||||
map.putString("code", "admob/error-code-invalid-request");
|
||||
map.putString("message", "The ad request was invalid; for instance, the ad unit ID was incorrect.");
|
||||
map.putString(
|
||||
"message",
|
||||
"The ad request was invalid; for instance, the ad unit ID was incorrect."
|
||||
);
|
||||
break;
|
||||
case AdRequest.ERROR_CODE_NETWORK_ERROR:
|
||||
map.putString("code", "admob/error-code-network-error");
|
||||
|
@ -39,7 +46,10 @@ class RNFirebaseAdMobUtils {
|
|||
break;
|
||||
case AdRequest.ERROR_CODE_NO_FILL:
|
||||
map.putString("code", "admob/error-code-no-fill");
|
||||
map.putString("message", "The ad request was successful, but no ad was returned due to lack of ad inventory.");
|
||||
map.putString(
|
||||
"message",
|
||||
"The ad request was successful, but no ad was returned due to lack of ad inventory."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -114,6 +124,7 @@ class RNFirebaseAdMobUtils {
|
|||
|
||||
/**
|
||||
* Map the size prop to the AdSize
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
|
|
|
@ -3,10 +3,8 @@ package io.invertase.firebase.admob;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.ads.AdListener;
|
||||
import com.google.android.gms.ads.AdRequest;
|
||||
|
@ -60,6 +58,7 @@ class RNFirebaseAdmobInterstitial {
|
|||
|
||||
/**
|
||||
* Load an Ad with a AdRequest instance
|
||||
*
|
||||
* @param adRequest
|
||||
*/
|
||||
void loadAd(final AdRequest adRequest) {
|
||||
|
@ -93,6 +92,7 @@ class RNFirebaseAdmobInterstitial {
|
|||
|
||||
/**
|
||||
* Send a native event over the bridge with a type and optional payload
|
||||
*
|
||||
* @param type
|
||||
* @param payload
|
||||
*/
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package io.invertase.firebase.analytics;
|
||||
|
||||
import android.util.Log;
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
|
||||
public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
||||
|
@ -31,7 +31,9 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void logEvent(final String name, @Nullable final ReadableMap params) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).logEvent(name, Arguments.toBundle(params));
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.logEvent(name, Arguments.toBundle(params));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,7 +41,9 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
*/
|
||||
@ReactMethod
|
||||
public void setAnalyticsCollectionEnabled(final Boolean enabled) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setAnalyticsCollectionEnabled(enabled);
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.setAnalyticsCollectionEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +59,9 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setCurrentScreen(activity, screenName, screenClassOverride);
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.setCurrentScreen(activity, screenName, screenClassOverride);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -66,7 +72,9 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
*/
|
||||
@ReactMethod
|
||||
public void setMinimumSessionDuration(final double milliseconds) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setMinimumSessionDuration((long) milliseconds);
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.setMinimumSessionDuration((long) milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,7 +82,9 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
*/
|
||||
@ReactMethod
|
||||
public void setSessionTimeoutDuration(final double milliseconds) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setSessionTimeoutDuration((long) milliseconds);
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.setSessionTimeoutDuration((long) milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +92,9 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(final String id) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserId(id);
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.setUserId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,6 +103,8 @@ public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
|||
*/
|
||||
@ReactMethod
|
||||
public void setUserProperty(final String name, final String value) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserProperty(name, value);
|
||||
FirebaseAnalytics
|
||||
.getInstance(getReactApplicationContext())
|
||||
.setUserProperty(name, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package io.invertase.firebase.analytics;
|
|||
import android.support.annotation.RequiresPermission;
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.invertase.firebase.auth;
|
||||
|
||||
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;
|
||||
|
|
|
@ -3,25 +3,25 @@ package io.invertase.firebase.config;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException;
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
@ -48,11 +48,14 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule {
|
|||
return TAG;
|
||||
}
|
||||
|
||||
@ReactMethod public void enableDeveloperMode() {
|
||||
@ReactMethod
|
||||
public void enableDeveloperMode() {
|
||||
FirebaseRemoteConfigSettings.Builder settings = new FirebaseRemoteConfigSettings.Builder();
|
||||
settings.setDeveloperModeEnabled(true);
|
||||
|
||||
FirebaseRemoteConfig.getInstance().setConfigSettings(settings.build());
|
||||
FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.setConfigSettings(settings.build());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -67,13 +70,19 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void activateFetched(final Promise promise) {
|
||||
Boolean status = FirebaseRemoteConfig.getInstance().activateFetched();
|
||||
Boolean status = FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.activateFetched();
|
||||
|
||||
promise.resolve(status);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getValue(String key, final Promise promise) {
|
||||
FirebaseRemoteConfigValue value = FirebaseRemoteConfig.getInstance().getValue(key);
|
||||
FirebaseRemoteConfigValue value = FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.getValue(key);
|
||||
|
||||
promise.resolve(convertRemoteConfigValue(value));
|
||||
}
|
||||
|
||||
|
@ -83,7 +92,10 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule {
|
|||
List<Object> keysList = Utils.recursivelyDeconstructReadableArray(keys);
|
||||
|
||||
for (Object key : keysList) {
|
||||
FirebaseRemoteConfigValue value = FirebaseRemoteConfig.getInstance().getValue((String) key);
|
||||
FirebaseRemoteConfigValue value = FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.getValue((String) key);
|
||||
|
||||
array.pushMap(convertRemoteConfigValue(value));
|
||||
}
|
||||
|
||||
|
@ -92,7 +104,10 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void getKeysByPrefix(String prefix, final Promise promise) {
|
||||
Set<String> keys = FirebaseRemoteConfig.getInstance().getKeysByPrefix(prefix);
|
||||
Set<String> keys = FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.getKeysByPrefix(prefix);
|
||||
|
||||
WritableArray array = Arguments.createArray();
|
||||
|
||||
for (String key : keys) {
|
||||
|
@ -105,23 +120,45 @@ class RNFirebaseRemoteConfig extends ReactContextBaseJavaModule {
|
|||
@ReactMethod
|
||||
public void setDefaults(ReadableMap map) {
|
||||
Map<String, Object> convertedMap = Utils.recursivelyDeconstructReadableMap(map);
|
||||
FirebaseRemoteConfig.getInstance().setDefaults(convertedMap);
|
||||
FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.setDefaults(convertedMap);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setDefaultsFromResource(int resourceId) {
|
||||
FirebaseRemoteConfig.getInstance().setDefaults(resourceId);
|
||||
FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.setDefaults(resourceId);
|
||||
}
|
||||
|
||||
private void fetchInternal(final Promise promise, Boolean withExpiration, long expirationDuration) {
|
||||
FirebaseRemoteConfig.getInstance().fetch(withExpiration ? expirationDuration : 43200) // 12 hours default
|
||||
private void fetchInternal(
|
||||
final Promise promise,
|
||||
Boolean withExpiration,
|
||||
long expirationDuration
|
||||
) {
|
||||
FirebaseRemoteConfig
|
||||
.getInstance()
|
||||
.fetch(withExpiration ? expirationDuration : 43200) // 12 hours default
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@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 +191,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");
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.invertase.firebase.config;
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package io.invertase.firebase.crash;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import com.google.firebase.crash.FirebaseCrash;
|
||||
|
||||
|
||||
public class RNFirebaseCrash extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebaseCrash";
|
||||
|
||||
public RNFirebaseCrash(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void log(final String message) {
|
||||
FirebaseCrash.log(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void logcat(final int level, final String tag, final String message) {
|
||||
FirebaseCrash.logcat(level, tag, message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void report(String message) {
|
||||
FirebaseCrash.report(new Exception(message));
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setCrashCollectionEnabled(Boolean enabled) {
|
||||
FirebaseCrash.setCrashCollectionEnabled(enabled);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void isCrashCollectionEnabled(Promise promise) {
|
||||
Boolean isEnabled = FirebaseCrash.isCrashCollectionEnabled();
|
||||
promise.resolve(isEnabled);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package io.invertase.firebase.crash;
|
||||
|
||||
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;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RNFirebaseCrashPackage implements ReactPackage {
|
||||
public RNFirebaseCrashPackage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reactContext react application context that can be used to create modules
|
||||
* @return list of native modules to register with the newly created catalyst instance
|
||||
*/
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
modules.add(new RNFirebaseCrash(reactContext));
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reactContext
|
||||
* @return a list of view managers that should be registered with {@link UIManagerModule}
|
||||
*/
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
|
@ -4,26 +4,25 @@ import android.os.AsyncTask;
|
|||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.database.DataSnapshot;
|
||||
import com.google.firebase.database.DatabaseError;
|
||||
import com.google.firebase.database.DatabaseException;
|
||||
import com.google.firebase.database.DatabaseReference;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
import com.google.firebase.database.Logger;
|
||||
import com.google.firebase.database.MutableData;
|
||||
import com.google.firebase.database.OnDisconnect;
|
||||
import com.google.firebase.database.ServerValue;
|
||||
import com.google.firebase.database.Transaction;
|
||||
import com.google.firebase.database.DataSnapshot;
|
||||
import com.google.firebase.database.DatabaseError;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
import com.google.firebase.database.DatabaseReference;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -32,12 +31,11 @@ import java.util.Map;
|
|||
import io.invertase.firebase.ErrorUtils;
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
||||
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseDatabase";
|
||||
private static boolean enableLogging = false;
|
||||
private HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<>();
|
||||
private static HashMap<String, Boolean> loggingLevelSet = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<>();
|
||||
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
|
||||
RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
||||
|
@ -49,6 +47,210 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* REACT NATIVE METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolve null or reject with a js like error if databaseError exists
|
||||
*
|
||||
* @param promise
|
||||
* @param databaseError
|
||||
*/
|
||||
static void handlePromise(Promise promise, DatabaseError databaseError) {
|
||||
if (databaseError != null) {
|
||||
WritableMap jsError = getJSError(databaseError);
|
||||
promise.reject(
|
||||
jsError.getString("code"),
|
||||
jsError.getString("message"),
|
||||
databaseError.toException()
|
||||
);
|
||||
} else {
|
||||
promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database instance for a specific firebase app instance
|
||||
*
|
||||
* @param appName
|
||||
* @param dbURL
|
||||
* @return
|
||||
*/
|
||||
public static FirebaseDatabase getDatabaseForApp(String appName, String dbURL) {
|
||||
FirebaseDatabase firebaseDatabase;
|
||||
if (dbURL != null && dbURL.length() > 0) {
|
||||
if (appName != null && appName.length() > 0) {
|
||||
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||
firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp, dbURL);
|
||||
} else {
|
||||
firebaseDatabase = FirebaseDatabase.getInstance(dbURL);
|
||||
}
|
||||
} else {
|
||||
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||
firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp);
|
||||
}
|
||||
|
||||
Boolean logLevel = loggingLevelSet.get(firebaseDatabase
|
||||
.getApp()
|
||||
.getName());
|
||||
|
||||
if (enableLogging && (logLevel == null || !logLevel)) {
|
||||
try {
|
||||
loggingLevelSet.put(firebaseDatabase
|
||||
.getApp()
|
||||
.getName(), enableLogging);
|
||||
firebaseDatabase.setLogLevel(Logger.Level.DEBUG);
|
||||
} catch (DatabaseException dex) {
|
||||
// do nothing - to catch 'calls to setLogLevel must be made for use of database' errors
|
||||
// only occurs in dev after reloading or if user has actually incorrectly called it.
|
||||
Log.w(
|
||||
TAG,
|
||||
"WARNING: enableLogging(bool) must be called before any other use of database(). \n" +
|
||||
"If you are sure you've done this then this message can be ignored during development as \n" +
|
||||
"RN reloads can cause false positives. APP: " + firebaseDatabase
|
||||
.getApp()
|
||||
.getName()
|
||||
);
|
||||
}
|
||||
} else if (!enableLogging && (logLevel != null && logLevel)) {
|
||||
try {
|
||||
loggingLevelSet.put(firebaseDatabase
|
||||
.getApp()
|
||||
.getName(), enableLogging);
|
||||
firebaseDatabase.setLogLevel(Logger.Level.WARN);
|
||||
} catch (DatabaseException dex) {
|
||||
// do nothing - to catch 'calls to setLogLevel must be made for use of database' errors
|
||||
// only occurs in dev after reloading or if user has actually incorrectly called it.
|
||||
Log.w(
|
||||
TAG,
|
||||
"WARNING: enableLogging(bool) must be called before any other use of database(). \n" +
|
||||
"If you are sure you've done this then this message can be ignored during development as \n" +
|
||||
"RN reloads can cause false positives. APP: " + firebaseDatabase
|
||||
.getApp()
|
||||
.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return firebaseDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert as firebase DatabaseError instance into a writable map
|
||||
* with the correct web-like error codes.
|
||||
*
|
||||
* @param nativeError
|
||||
* @return
|
||||
*/
|
||||
static WritableMap getJSError(DatabaseError nativeError) {
|
||||
WritableMap errorMap = Arguments.createMap();
|
||||
errorMap.putInt("nativeErrorCode", nativeError.getCode());
|
||||
errorMap.putString("nativeErrorMessage", nativeError.getMessage());
|
||||
|
||||
String code;
|
||||
String message;
|
||||
String service = "Database";
|
||||
|
||||
switch (nativeError.getCode()) {
|
||||
case DatabaseError.DATA_STALE:
|
||||
code = ErrorUtils.getCodeWithService(service, "data-stale");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The transaction needs to be run again with current data.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.OPERATION_FAILED:
|
||||
code = ErrorUtils.getCodeWithService(service, "failure");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The server indicated that this operation failed.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.PERMISSION_DENIED:
|
||||
code = ErrorUtils.getCodeWithService(service, "permission-denied");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"Client doesn't have permission to access the desired data.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.DISCONNECTED:
|
||||
code = ErrorUtils.getCodeWithService(service, "disconnected");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The operation had to be aborted due to a network disconnect.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.EXPIRED_TOKEN:
|
||||
code = ErrorUtils.getCodeWithService(service, "expired-token");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The supplied auth token has expired.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.INVALID_TOKEN:
|
||||
code = ErrorUtils.getCodeWithService(service, "invalid-token");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The supplied auth token was invalid.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.MAX_RETRIES:
|
||||
code = ErrorUtils.getCodeWithService(service, "max-retries");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The transaction had too many retries.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.OVERRIDDEN_BY_SET:
|
||||
code = ErrorUtils.getCodeWithService(service, "overridden-by-set");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The transaction was overridden by a subsequent set.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.UNAVAILABLE:
|
||||
code = ErrorUtils.getCodeWithService(service, "unavailable");
|
||||
message = ErrorUtils.getMessageWithService("The service is unavailable.", service, code);
|
||||
break;
|
||||
case DatabaseError.USER_CODE_EXCEPTION:
|
||||
code = ErrorUtils.getCodeWithService(service, "user-code-exception");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"User code called from the Firebase Database runloop threw an exception.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.NETWORK_ERROR:
|
||||
code = ErrorUtils.getCodeWithService(service, "network-error");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The operation could not be performed due to a network error.",
|
||||
service,
|
||||
code
|
||||
);
|
||||
break;
|
||||
case DatabaseError.WRITE_CANCELED:
|
||||
code = ErrorUtils.getCodeWithService(service, "write-cancelled");
|
||||
message = ErrorUtils.getMessageWithService(
|
||||
"The write was canceled by the user.",
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param appName
|
||||
*/
|
||||
|
@ -74,6 +276,11 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
getDatabaseForApp(appName, dbURL).setPersistenceEnabled(state);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TRANSACTIONS
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param appName
|
||||
* @param size
|
||||
|
@ -83,7 +290,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
getDatabaseForApp(appName, dbURL).setPersistenceCacheSizeBytes((long) size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param enabled
|
||||
*/
|
||||
|
@ -95,42 +301,62 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
loggingLevelSet.put(app.getName(), enabled);
|
||||
try {
|
||||
if (enableLogging) {
|
||||
FirebaseDatabase.getInstance(app).setLogLevel(Logger.Level.DEBUG);
|
||||
FirebaseDatabase
|
||||
.getInstance(app)
|
||||
.setLogLevel(Logger.Level.DEBUG);
|
||||
} else {
|
||||
FirebaseDatabase.getInstance(app).setLogLevel(Logger.Level.WARN);
|
||||
FirebaseDatabase
|
||||
.getInstance(app)
|
||||
.setLogLevel(Logger.Level.WARN);
|
||||
}
|
||||
} catch (DatabaseException dex) {
|
||||
// do nothing - to catch 'calls to setLogLevel must be made for use of database' errors
|
||||
// only occurs in dev after reloading or if user has actually incorrectly called it.
|
||||
Log.w(TAG, "WARNING: enableLogging(bool) must be called before any other use of database(). \n" +
|
||||
"If you are sure you've done this then this message can be ignored during development as \n" +
|
||||
"RN reloads can cause false positives. APP: " + app.getName());
|
||||
Log.w(
|
||||
TAG,
|
||||
"WARNING: enableLogging(bool) must be called before any other use of database(). \n" +
|
||||
"If you are sure you've done this then this message can be ignored during development as \n" +
|
||||
"RN reloads can cause false positives. APP: " + app.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ON DISCONNECT
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param appName
|
||||
* @param path
|
||||
* @param state
|
||||
*/
|
||||
@ReactMethod
|
||||
public void keepSynced(String appName, String dbURL, String key, String path, ReadableArray modifiers, Boolean state) {
|
||||
getInternalReferenceForApp(appName, dbURL, key, path, modifiers).getQuery().keepSynced(state);
|
||||
public void keepSynced(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String key,
|
||||
String path,
|
||||
ReadableArray modifiers,
|
||||
Boolean state
|
||||
) {
|
||||
getInternalReferenceForApp(appName, dbURL, key, path, modifiers)
|
||||
.getQuery()
|
||||
.keepSynced(state);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TRANSACTIONS
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param transactionId
|
||||
* @param updates
|
||||
*/
|
||||
@ReactMethod
|
||||
public void transactionTryCommit(String appName, String dbURL, int transactionId, ReadableMap updates) {
|
||||
public void transactionTryCommit(
|
||||
String appName,
|
||||
String dbURL,
|
||||
int transactionId,
|
||||
ReadableMap updates
|
||||
) {
|
||||
RNFirebaseTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||
|
||||
if (handler != null) {
|
||||
|
@ -147,7 +373,13 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param applyLocally
|
||||
*/
|
||||
@ReactMethod
|
||||
public void transactionStart(final String appName, final String dbURL, final String path, final int transactionId, final Boolean applyLocally) {
|
||||
public void transactionStart(
|
||||
final String appName,
|
||||
final String dbURL,
|
||||
final String path,
|
||||
final int transactionId,
|
||||
final Boolean applyLocally
|
||||
) {
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -156,7 +388,11 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
reference.runTransaction(new Transaction.Handler() {
|
||||
@Override
|
||||
public Transaction.Result doTransaction(MutableData mutableData) {
|
||||
final RNFirebaseTransactionHandler transactionHandler = new RNFirebaseTransactionHandler(transactionId, appName, dbURL);
|
||||
final RNFirebaseTransactionHandler transactionHandler = new RNFirebaseTransactionHandler(
|
||||
transactionId,
|
||||
appName,
|
||||
dbURL
|
||||
);
|
||||
transactionHandlers.put(transactionId, transactionHandler);
|
||||
final WritableMap updatesMap = transactionHandler.createUpdateMap(mutableData);
|
||||
|
||||
|
@ -165,7 +401,11 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", updatesMap);
|
||||
Utils.sendEvent(
|
||||
getReactApplicationContext(),
|
||||
"database_transaction_event",
|
||||
updatesMap
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -198,11 +438,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ON DISCONNECT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set a value on a ref when the client disconnects from the firebase server.
|
||||
*
|
||||
|
@ -212,7 +447,13 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void onDisconnectSet(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
|
||||
public void onDisconnectSet(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String path,
|
||||
ReadableMap props,
|
||||
final Promise promise
|
||||
) {
|
||||
String type = props.getString("type");
|
||||
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
|
||||
|
||||
|
@ -257,7 +498,13 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void onDisconnectUpdate(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
|
||||
public void onDisconnectUpdate(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String path,
|
||||
ReadableMap props,
|
||||
final Promise promise
|
||||
) {
|
||||
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
|
||||
OnDisconnect ondDisconnect = ref.onDisconnect();
|
||||
|
||||
|
@ -318,9 +565,17 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void set(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
|
||||
public void set(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String path,
|
||||
ReadableMap props,
|
||||
final Promise promise
|
||||
) {
|
||||
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
|
||||
Object value = Utils.recursivelyDeconstructReadableMap(props).get("value");
|
||||
Object value = Utils
|
||||
.recursivelyDeconstructReadableMap(props)
|
||||
.get("value");
|
||||
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
|
@ -339,9 +594,17 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setPriority(String appName, String dbURL, String path, ReadableMap priority, final Promise promise) {
|
||||
public void setPriority(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String path,
|
||||
ReadableMap priority,
|
||||
final Promise promise
|
||||
) {
|
||||
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
|
||||
Object priorityValue = Utils.recursivelyDeconstructReadableMap(priority).get("value");
|
||||
Object priorityValue = Utils
|
||||
.recursivelyDeconstructReadableMap(priority)
|
||||
.get("value");
|
||||
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
|
@ -361,10 +624,21 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setWithPriority(String appName, String dbURL, String path, ReadableMap data, ReadableMap priority, final Promise promise) {
|
||||
public void setWithPriority(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String path,
|
||||
ReadableMap data,
|
||||
ReadableMap priority,
|
||||
final Promise promise
|
||||
) {
|
||||
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
|
||||
Object dataValue = Utils.recursivelyDeconstructReadableMap(data).get("value");
|
||||
Object priorityValue = Utils.recursivelyDeconstructReadableMap(priority).get("value");
|
||||
Object dataValue = Utils
|
||||
.recursivelyDeconstructReadableMap(data)
|
||||
.get("value");
|
||||
Object priorityValue = Utils
|
||||
.recursivelyDeconstructReadableMap(priority)
|
||||
.get("value");
|
||||
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
|
@ -383,7 +657,13 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void update(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
|
||||
public void update(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String path,
|
||||
ReadableMap props,
|
||||
final Promise promise
|
||||
) {
|
||||
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
|
||||
Map<String, Object> updates = Utils.recursivelyDeconstructReadableMap(props);
|
||||
|
||||
|
@ -416,6 +696,9 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
ref.removeValue(listener);
|
||||
}
|
||||
|
||||
/*
|
||||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Subscribe once to a firebase reference.
|
||||
|
@ -428,7 +711,15 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void once(String appName, String dbURL, String key, String path, ReadableArray modifiers, String eventType, Promise promise) {
|
||||
public void once(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String key,
|
||||
String path,
|
||||
ReadableArray modifiers,
|
||||
String eventType,
|
||||
Promise promise
|
||||
) {
|
||||
getInternalReferenceForApp(appName, dbURL, key, path, modifiers).once(eventType, promise);
|
||||
}
|
||||
|
||||
|
@ -466,80 +757,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolve null or reject with a js like error if databaseError exists
|
||||
*
|
||||
* @param promise
|
||||
* @param databaseError
|
||||
*/
|
||||
static void handlePromise(Promise promise, DatabaseError databaseError) {
|
||||
if (databaseError != null) {
|
||||
WritableMap jsError = getJSError(databaseError);
|
||||
promise.reject(
|
||||
jsError.getString("code"),
|
||||
jsError.getString("message"),
|
||||
databaseError.toException()
|
||||
);
|
||||
} else {
|
||||
promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a database instance for a specific firebase app instance
|
||||
*
|
||||
* @param appName
|
||||
* @param dbURL
|
||||
* @return
|
||||
*/
|
||||
public static FirebaseDatabase getDatabaseForApp(String appName, String dbURL) {
|
||||
FirebaseDatabase firebaseDatabase;
|
||||
if(dbURL != null && dbURL.length() > 0) {
|
||||
if (appName != null && appName.length() > 0) {
|
||||
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||
firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp, dbURL);
|
||||
} else {
|
||||
firebaseDatabase = FirebaseDatabase.getInstance(dbURL);
|
||||
}
|
||||
} else {
|
||||
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||
firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp);
|
||||
}
|
||||
|
||||
Boolean logLevel = loggingLevelSet.get(firebaseDatabase.getApp().getName());
|
||||
|
||||
if (enableLogging && (logLevel == null || !logLevel)) {
|
||||
try {
|
||||
loggingLevelSet.put(firebaseDatabase.getApp().getName(), enableLogging);
|
||||
firebaseDatabase.setLogLevel(Logger.Level.DEBUG);
|
||||
} catch (DatabaseException dex) {
|
||||
// do nothing - to catch 'calls to setLogLevel must be made for use of database' errors
|
||||
// only occurs in dev after reloading or if user has actually incorrectly called it.
|
||||
Log.w(TAG, "WARNING: enableLogging(bool) must be called before any other use of database(). \n" +
|
||||
"If you are sure you've done this then this message can be ignored during development as \n" +
|
||||
"RN reloads can cause false positives. APP: " + firebaseDatabase.getApp().getName());
|
||||
}
|
||||
} else if (!enableLogging && (logLevel != null && logLevel)) {
|
||||
try {
|
||||
loggingLevelSet.put(firebaseDatabase.getApp().getName(), enableLogging);
|
||||
firebaseDatabase.setLogLevel(Logger.Level.WARN);
|
||||
} catch (DatabaseException dex) {
|
||||
// do nothing - to catch 'calls to setLogLevel must be made for use of database' errors
|
||||
// only occurs in dev after reloading or if user has actually incorrectly called it.
|
||||
Log.w(TAG, "WARNING: enableLogging(bool) must be called before any other use of database(). \n" +
|
||||
"If you are sure you've done this then this message can be ignored during development as \n" +
|
||||
"RN reloads can cause false positives. APP: " + firebaseDatabase.getApp().getName());
|
||||
}
|
||||
}
|
||||
|
||||
return firebaseDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database reference for a specific app and path
|
||||
*
|
||||
|
@ -560,7 +777,13 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param modifiers
|
||||
* @return
|
||||
*/
|
||||
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, String dbURL, String key, String path, ReadableArray modifiers) {
|
||||
private RNFirebaseDatabaseReference getInternalReferenceForApp(
|
||||
String appName,
|
||||
String dbURL,
|
||||
String key,
|
||||
String path,
|
||||
ReadableArray modifiers
|
||||
) {
|
||||
return new RNFirebaseDatabaseReference(
|
||||
getReactApplicationContext(),
|
||||
appName,
|
||||
|
@ -578,7 +801,11 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
* @param props
|
||||
* @return
|
||||
*/
|
||||
private RNFirebaseDatabaseReference getCachedInternalReferenceForApp(String appName, String dbURL, ReadableMap props) {
|
||||
private RNFirebaseDatabaseReference getCachedInternalReferenceForApp(
|
||||
String appName,
|
||||
String dbURL,
|
||||
ReadableMap props
|
||||
) {
|
||||
String key = props.getString("key");
|
||||
String path = props.getString("path");
|
||||
ReadableArray modifiers = props.getArray("modifiers");
|
||||
|
@ -593,81 +820,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
return existingRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert as firebase DatabaseError instance into a writable map
|
||||
* with the correct web-like error codes.
|
||||
*
|
||||
* @param nativeError
|
||||
* @return
|
||||
*/
|
||||
static WritableMap getJSError(DatabaseError nativeError) {
|
||||
WritableMap errorMap = Arguments.createMap();
|
||||
errorMap.putInt("nativeErrorCode", nativeError.getCode());
|
||||
errorMap.putString("nativeErrorMessage", nativeError.getMessage());
|
||||
|
||||
String code;
|
||||
String message;
|
||||
String service = "Database";
|
||||
|
||||
switch (nativeError.getCode()) {
|
||||
case DatabaseError.DATA_STALE:
|
||||
code = ErrorUtils.getCodeWithService(service, "data-stale");
|
||||
message = ErrorUtils.getMessageWithService("The transaction needs to be run again with current data.", service, code);
|
||||
break;
|
||||
case DatabaseError.OPERATION_FAILED:
|
||||
code = ErrorUtils.getCodeWithService(service, "failure");
|
||||
message = ErrorUtils.getMessageWithService("The server indicated that this operation failed.", service, code);
|
||||
break;
|
||||
case DatabaseError.PERMISSION_DENIED:
|
||||
code = ErrorUtils.getCodeWithService(service, "permission-denied");
|
||||
message = ErrorUtils.getMessageWithService("Client doesn't have permission to access the desired data.", service, code);
|
||||
break;
|
||||
case DatabaseError.DISCONNECTED:
|
||||
code = ErrorUtils.getCodeWithService(service, "disconnected");
|
||||
message = ErrorUtils.getMessageWithService("The operation had to be aborted due to a network disconnect.", service, code);
|
||||
break;
|
||||
case DatabaseError.EXPIRED_TOKEN:
|
||||
code = ErrorUtils.getCodeWithService(service, "expired-token");
|
||||
message = ErrorUtils.getMessageWithService("The supplied auth token has expired.", service, code);
|
||||
break;
|
||||
case DatabaseError.INVALID_TOKEN:
|
||||
code = ErrorUtils.getCodeWithService(service, "invalid-token");
|
||||
message = ErrorUtils.getMessageWithService("The supplied auth token was invalid.", service, code);
|
||||
break;
|
||||
case DatabaseError.MAX_RETRIES:
|
||||
code = ErrorUtils.getCodeWithService(service, "max-retries");
|
||||
message = ErrorUtils.getMessageWithService("The transaction had too many retries.", service, code);
|
||||
break;
|
||||
case DatabaseError.OVERRIDDEN_BY_SET:
|
||||
code = ErrorUtils.getCodeWithService(service, "overridden-by-set");
|
||||
message = ErrorUtils.getMessageWithService("The transaction was overridden by a subsequent set.", service, code);
|
||||
break;
|
||||
case DatabaseError.UNAVAILABLE:
|
||||
code = ErrorUtils.getCodeWithService(service, "unavailable");
|
||||
message = ErrorUtils.getMessageWithService("The service is unavailable.", service, code);
|
||||
break;
|
||||
case DatabaseError.USER_CODE_EXCEPTION:
|
||||
code = ErrorUtils.getCodeWithService(service, "user-code-exception");
|
||||
message = ErrorUtils.getMessageWithService("User code called from the Firebase Database runloop threw an exception.", service, code);
|
||||
break;
|
||||
case DatabaseError.NETWORK_ERROR:
|
||||
code = ErrorUtils.getCodeWithService(service, "network-error");
|
||||
message = ErrorUtils.getMessageWithService("The operation could not be performed due to a network error.", service, code);
|
||||
break;
|
||||
case DatabaseError.WRITE_CANCELED:
|
||||
code = ErrorUtils.getCodeWithService(service, "write-cancelled");
|
||||
message = ErrorUtils.getMessageWithService("The write was canceled by the user.", 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* React Method - returns this module name
|
||||
*
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.invertase.firebase.database;
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,35 +1,38 @@
|
|||
package io.invertase.firebase.database;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.util.Log;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
|
||||
import com.google.firebase.database.Query;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.firebase.database.ChildEventListener;
|
||||
import com.google.firebase.database.DataSnapshot;
|
||||
import com.google.firebase.database.DatabaseError;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
import com.google.firebase.database.ChildEventListener;
|
||||
import com.google.firebase.database.Query;
|
||||
import com.google.firebase.database.ValueEventListener;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
class RNFirebaseDatabaseReference {
|
||||
private static final String TAG = "RNFirebaseDBReference";
|
||||
private String key;
|
||||
private Query query;
|
||||
private String appName;
|
||||
private String dbURL;
|
||||
private ReactContext reactContext;
|
||||
private static final String TAG = "RNFirebaseDBReference";
|
||||
private HashMap<String, ChildEventListener> childEventListeners = new HashMap<>();
|
||||
private HashMap<String, ValueEventListener> valueEventListeners = new HashMap<>();
|
||||
|
||||
|
@ -43,7 +46,14 @@ class RNFirebaseDatabaseReference {
|
|||
* @param refPath
|
||||
* @param modifiersArray
|
||||
*/
|
||||
RNFirebaseDatabaseReference(ReactContext context, String app, String url, String refKey, String refPath, ReadableArray modifiersArray) {
|
||||
RNFirebaseDatabaseReference(
|
||||
ReactContext context,
|
||||
String app,
|
||||
String url,
|
||||
String refKey,
|
||||
String refPath,
|
||||
ReadableArray modifiersArray
|
||||
) {
|
||||
key = refKey;
|
||||
query = null;
|
||||
appName = app;
|
||||
|
@ -52,7 +62,6 @@ class RNFirebaseDatabaseReference {
|
|||
buildDatabaseQueryAtPathAndModifiers(refPath, modifiersArray);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used outside of class for keepSynced etc.
|
||||
*
|
||||
|
@ -69,7 +78,8 @@ class RNFirebaseDatabaseReference {
|
|||
* @return
|
||||
*/
|
||||
private Boolean hasEventListener(String eventRegistrationKey) {
|
||||
return valueEventListeners.containsKey(eventRegistrationKey) || childEventListeners.containsKey(eventRegistrationKey);
|
||||
return valueEventListeners.containsKey(eventRegistrationKey) || childEventListeners.containsKey(
|
||||
eventRegistrationKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,7 +121,6 @@ class RNFirebaseDatabaseReference {
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a ChildEventListener to the query and internally keep a reference to it.
|
||||
*
|
||||
|
@ -130,15 +139,24 @@ 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 +174,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 +183,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 +192,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 +201,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 +210,7 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
public void onCancelled(@NonNull DatabaseError error) {
|
||||
query.removeEventListener(this);
|
||||
RNFirebaseDatabase.handlePromise(promise, error);
|
||||
}
|
||||
|
@ -201,7 +219,6 @@ class RNFirebaseDatabaseReference {
|
|||
query.addChildEventListener(childEventListener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles a React Native JS '.on(..)' request and initializes listeners.
|
||||
*
|
||||
|
@ -229,7 +246,6 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a native .on('child_X',.. ) event listener.
|
||||
*
|
||||
|
@ -243,35 +259,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 +308,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 +330,28 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -366,11 +394,6 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
}
|
||||
|
||||
/* =================
|
||||
* QUERY MODIFIERS
|
||||
* =================
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @param type
|
||||
|
@ -393,6 +416,11 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
}
|
||||
|
||||
/* =================
|
||||
* QUERY MODIFIERS
|
||||
* =================
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @param type
|
||||
|
@ -423,12 +451,6 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===============
|
||||
* QUERY FILTERS
|
||||
* ===============
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param valueType
|
||||
|
@ -459,6 +481,12 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===============
|
||||
* QUERY FILTERS
|
||||
* ===============
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param valueType
|
||||
|
@ -518,4 +546,46 @@ class RNFirebaseDatabaseReference {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AsyncTask to convert DataSnapshot instances to WritableMap instances.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@ public class RNFirebaseDatabaseUtils {
|
|||
* @param previousChildName
|
||||
* @return
|
||||
*/
|
||||
public static WritableMap snapshotToMap(DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
||||
public static WritableMap snapshotToMap(
|
||||
DataSnapshot dataSnapshot,
|
||||
@Nullable String previousChildName
|
||||
) {
|
||||
WritableMap result = Arguments.createMap();
|
||||
WritableMap snapshot = snapshotToMap(dataSnapshot);
|
||||
|
||||
|
@ -71,7 +74,10 @@ public class RNFirebaseDatabaseUtils {
|
|||
}
|
||||
} else {
|
||||
if (snapshot.getValue() != null) {
|
||||
String type = snapshot.getValue().getClass().getName();
|
||||
String type = snapshot
|
||||
.getValue()
|
||||
.getClass()
|
||||
.getName();
|
||||
switch (type) {
|
||||
case "java.lang.Boolean":
|
||||
case "java.lang.Long":
|
||||
|
@ -101,7 +107,10 @@ public class RNFirebaseDatabaseUtils {
|
|||
}
|
||||
} else {
|
||||
if (mutableData.getValue() != null) {
|
||||
String type = mutableData.getValue().getClass().getName();
|
||||
String type = mutableData
|
||||
.getValue()
|
||||
.getClass()
|
||||
.getName();
|
||||
switch (type) {
|
||||
case "java.lang.Boolean":
|
||||
case "java.lang.Long":
|
||||
|
@ -190,7 +199,9 @@ public class RNFirebaseDatabaseUtils {
|
|||
expectedKey = key;
|
||||
}
|
||||
Any castedChild = castValue(child);
|
||||
switch (castedChild.getClass().getName()) {
|
||||
switch (castedChild
|
||||
.getClass()
|
||||
.getName()) {
|
||||
case "java.lang.Boolean":
|
||||
array.pushBoolean((Boolean) castedChild);
|
||||
break;
|
||||
|
@ -211,7 +222,12 @@ public class RNFirebaseDatabaseUtils {
|
|||
array.pushArray((WritableArray) castedChild);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
|
||||
Log.w(
|
||||
TAG,
|
||||
"Invalid type: " + castedChild
|
||||
.getClass()
|
||||
.getName()
|
||||
);
|
||||
break;
|
||||
}
|
||||
expectedKey++;
|
||||
|
@ -236,7 +252,9 @@ public class RNFirebaseDatabaseUtils {
|
|||
expectedKey = key;
|
||||
}
|
||||
Any castedChild = castValue(child);
|
||||
switch (castedChild.getClass().getName()) {
|
||||
switch (castedChild
|
||||
.getClass()
|
||||
.getName()) {
|
||||
case "java.lang.Boolean":
|
||||
array.pushBoolean((Boolean) castedChild);
|
||||
break;
|
||||
|
@ -257,7 +275,12 @@ public class RNFirebaseDatabaseUtils {
|
|||
array.pushArray((WritableArray) castedChild);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
|
||||
Log.w(
|
||||
TAG,
|
||||
"Invalid type: " + castedChild
|
||||
.getClass()
|
||||
.getName()
|
||||
);
|
||||
break;
|
||||
}
|
||||
expectedKey++;
|
||||
|
@ -275,7 +298,9 @@ public class RNFirebaseDatabaseUtils {
|
|||
for (DataSnapshot child : snapshot.getChildren()) {
|
||||
Any castedChild = castValue(child);
|
||||
|
||||
switch (castedChild.getClass().getName()) {
|
||||
switch (castedChild
|
||||
.getClass()
|
||||
.getName()) {
|
||||
case "java.lang.Boolean":
|
||||
map.putBoolean(child.getKey(), (Boolean) castedChild);
|
||||
break;
|
||||
|
@ -295,7 +320,12 @@ public class RNFirebaseDatabaseUtils {
|
|||
map.putArray(child.getKey(), (WritableArray) castedChild);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
|
||||
Log.w(
|
||||
TAG,
|
||||
"Invalid type: " + castedChild
|
||||
.getClass()
|
||||
.getName()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -312,7 +342,9 @@ public class RNFirebaseDatabaseUtils {
|
|||
for (MutableData child : mutableData.getChildren()) {
|
||||
Any castedChild = castValue(child);
|
||||
|
||||
switch (castedChild.getClass().getName()) {
|
||||
switch (castedChild
|
||||
.getClass()
|
||||
.getName()) {
|
||||
case "java.lang.Boolean":
|
||||
map.putBoolean(child.getKey(), (Boolean) castedChild);
|
||||
break;
|
||||
|
@ -332,7 +364,12 @@ public class RNFirebaseDatabaseUtils {
|
|||
map.putArray(child.getKey(), (WritableArray) castedChild);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
|
||||
Log.w(
|
||||
TAG,
|
||||
"Invalid type: " + castedChild
|
||||
.getClass()
|
||||
.getName()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,18 +19,17 @@ import javax.annotation.Nullable;
|
|||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseTransactionHandler {
|
||||
private int transactionId;
|
||||
private String appName;
|
||||
private String dbURL;
|
||||
private final ReentrantLock lock;
|
||||
private final Condition condition;
|
||||
private Map<String, Object> data;
|
||||
private boolean signalled;
|
||||
|
||||
public Object value;
|
||||
boolean interrupted;
|
||||
boolean abort = false;
|
||||
boolean timeout = false;
|
||||
private int transactionId;
|
||||
private String appName;
|
||||
private String dbURL;
|
||||
private Map<String, Object> data;
|
||||
private boolean signalled;
|
||||
|
||||
RNFirebaseTransactionHandler(int id, String app, String url) {
|
||||
appName = app;
|
||||
|
@ -127,7 +126,11 @@ public class RNFirebaseTransactionHandler {
|
|||
}
|
||||
|
||||
|
||||
WritableMap createResultMap(@Nullable DatabaseError error, boolean committed, DataSnapshot snapshot) {
|
||||
WritableMap createResultMap(
|
||||
@Nullable DatabaseError error,
|
||||
boolean committed,
|
||||
DataSnapshot snapshot
|
||||
) {
|
||||
WritableMap resultMap = Arguments.createMap();
|
||||
|
||||
resultMap.putInt("id", transactionId);
|
||||
|
@ -144,7 +147,10 @@ public class RNFirebaseTransactionHandler {
|
|||
if (error == null && timeout) {
|
||||
WritableMap timeoutError = Arguments.createMap();
|
||||
timeoutError.putString("code", "DATABASE/INTERNAL-TIMEOUT");
|
||||
timeoutError.putString("message", "A timeout occurred whilst waiting for RN JS thread to send transaction updates.");
|
||||
timeoutError.putString(
|
||||
"message",
|
||||
"A timeout occurred whilst waiting for RN JS thread to send transaction updates."
|
||||
);
|
||||
resultMap.putMap("error", timeoutError);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -2,11 +2,10 @@ package io.invertase.firebase.fabric.crashlytics;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
|
||||
public class RNFirebaseCrashlytics extends ReactContextBaseJavaModule {
|
||||
|
@ -25,7 +24,9 @@ public class RNFirebaseCrashlytics extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void crash() {
|
||||
Crashlytics.getInstance().crash();
|
||||
Crashlytics
|
||||
.getInstance()
|
||||
.crash();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.invertase.firebase.fabric.crashlytics;
|
||||
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -48,22 +48,35 @@ public class FirestoreSerialize {
|
|||
static WritableMap snapshotToWritableMap(DocumentSnapshot documentSnapshot) {
|
||||
WritableMap documentMap = Arguments.createMap();
|
||||
|
||||
documentMap.putString(KEY_PATH, documentSnapshot.getReference().getPath());
|
||||
documentMap.putString(
|
||||
KEY_PATH,
|
||||
documentSnapshot
|
||||
.getReference()
|
||||
.getPath()
|
||||
);
|
||||
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 +91,20 @@ 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 +115,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 +129,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 +143,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 +159,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 +247,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 +316,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,49 @@
|
|||
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;
|
||||
|
@ -34,7 +34,6 @@ import java.util.Map;
|
|||
import io.invertase.firebase.ErrorUtils;
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
||||
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseFirestore";
|
||||
private SparseArray<RNFirebaseFirestoreTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
|
@ -48,20 +47,208 @@ 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>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "disableNetwork:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "disableNetwork:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
getFirestoreForApp(appName)
|
||||
.disableNetwork()
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "disableNetwork:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "disableNetwork:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(
|
||||
promise,
|
||||
(FirebaseFirestoreException) task.getException()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -75,46 +262,70 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void enableNetwork(String appName, final Promise promise) {
|
||||
getFirestoreForApp(appName).enableNetwork().addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "enableNetwork:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "enableNetwork:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
getFirestoreForApp(appName)
|
||||
.enableNetwork()
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "enableNetwork:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "enableNetwork:onComplete:failure", 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) {
|
||||
public void documentBatch(
|
||||
final String appName, final ReadableArray writes,
|
||||
final Promise promise
|
||||
) {
|
||||
FirebaseFirestore firestore = getFirestoreForApp(appName);
|
||||
WriteBatch batch = firestore.batch();
|
||||
final List<Object> writesArray = FirestoreSerialize.parseDocumentBatches(firestore, writes);
|
||||
|
@ -145,18 +356,23 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
}
|
||||
}
|
||||
|
||||
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "documentBatch:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "documentBatch:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException) task.getException());
|
||||
batch
|
||||
.commit()
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "documentBatch:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "documentBatch:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(
|
||||
promise,
|
||||
(FirebaseFirestoreException) task.getException()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -166,7 +382,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 +398,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);
|
||||
|
@ -202,30 +436,35 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
if (settings.hasKey("host")) {
|
||||
firestoreSettings.setHost(settings.getString("host"));
|
||||
} else {
|
||||
firestoreSettings.setHost(firestore.getFirestoreSettings().getHost());
|
||||
firestoreSettings.setHost(firestore
|
||||
.getFirestoreSettings()
|
||||
.getHost());
|
||||
}
|
||||
if (settings.hasKey("persistence")) {
|
||||
firestoreSettings.setPersistenceEnabled(settings.getBoolean("persistence"));
|
||||
} else {
|
||||
firestoreSettings.setPersistenceEnabled(firestore.getFirestoreSettings().isPersistenceEnabled());
|
||||
firestoreSettings.setPersistenceEnabled(firestore
|
||||
.getFirestoreSettings()
|
||||
.isPersistenceEnabled());
|
||||
}
|
||||
if (settings.hasKey("ssl")) {
|
||||
firestoreSettings.setSslEnabled(settings.getBoolean("ssl"));
|
||||
} else {
|
||||
firestoreSettings.setSslEnabled(firestore.getFirestoreSettings().isSslEnabled());
|
||||
}
|
||||
if (settings.hasKey("timestampsInSnapshots")) {
|
||||
// TODO: Not supported on Android yet
|
||||
firestoreSettings.setSslEnabled(firestore
|
||||
.getFirestoreSettings()
|
||||
.isSslEnabled());
|
||||
}
|
||||
|
||||
// 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 +478,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 +507,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 +531,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 +551,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 +578,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 +591,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 +618,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 +641,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 +665,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 +678,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 +694,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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -586,8 +752,18 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
constants.put("deleteFieldValue", FieldValue.delete().toString());
|
||||
constants.put("serverTimestampFieldValue", FieldValue.serverTimestamp().toString());
|
||||
constants.put(
|
||||
"deleteFieldValue",
|
||||
FieldValue
|
||||
.delete()
|
||||
.toString()
|
||||
);
|
||||
constants.put(
|
||||
"serverTimestampFieldValue",
|
||||
FieldValue
|
||||
.serverTimestamp()
|
||||
.toString()
|
||||
);
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,36 @@ public class RNFirebaseFirestoreCollectionReference {
|
|||
} else {
|
||||
source = Source.DEFAULT;
|
||||
}
|
||||
query.get(source).addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
|
||||
|
||||
@SuppressLint("StaticFieldLeak") final QuerySnapshotSerializeAsyncTask serializeAsyncTask = new QuerySnapshotSerializeAsyncTask(
|
||||
reactContext, this
|
||||
) {
|
||||
@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);
|
||||
} else {
|
||||
Log.e(TAG, "get:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
}
|
||||
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");
|
||||
serializeAsyncTask.execute(task.getResult());
|
||||
} else {
|
||||
Log.e(TAG, "get:onComplete:failure", 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 +116,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 +135,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 +193,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 +225,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 +241,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,33 +32,48 @@ 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);
|
||||
this.ref = RNFirebaseFirestore
|
||||
.getFirestoreForApp(appName)
|
||||
.document(path);
|
||||
}
|
||||
|
||||
public void delete(final Promise promise) {
|
||||
this.ref.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "delete:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "delete:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
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) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "delete:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "delete:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(
|
||||
promise,
|
||||
(FirebaseFirestoreException) task.getException()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 +86,59 @@ public class RNFirebaseFirestoreDocumentReference {
|
|||
} else {
|
||||
source = Source.DEFAULT;
|
||||
}
|
||||
this.ref.get(source).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
|
||||
|
||||
@SuppressLint("StaticFieldLeak") final DocumentSnapshotSerializeAsyncTask serializeAsyncTask = new DocumentSnapshotSerializeAsyncTask(
|
||||
reactContext, this
|
||||
) {
|
||||
@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);
|
||||
} else {
|
||||
Log.e(TAG, "get:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
}
|
||||
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");
|
||||
serializeAsyncTask.execute(task.getResult());
|
||||
} else {
|
||||
Log.e(TAG, "get:onComplete:failure", 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 +146,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,33 +178,45 @@ 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);
|
||||
this.ref.update(map).addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "update:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "update:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
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) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "update:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "update:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(
|
||||
promise,
|
||||
(FirebaseFirestoreException) task.getException()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
||||
public DocumentReference getRef() {
|
||||
DocumentReference getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
@ -174,32 +227,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;
|
||||
|
|
|
@ -41,7 +41,10 @@ public class RNFirebaseFunctions extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void httpsCallable(final String name, ReadableMap wrapper, final Promise promise) {
|
||||
Object input = wrapper.toHashMap().get(DATA_KEY);
|
||||
Object input = wrapper
|
||||
.toHashMap()
|
||||
.get(DATA_KEY);
|
||||
|
||||
Log.d(TAG, "function:call:input:" + name + ":" + (input != null ? input.toString() : "null"));
|
||||
|
||||
HttpsCallableReference httpsCallableReference = FirebaseFunctions
|
||||
|
@ -60,10 +63,14 @@ public class RNFirebaseFunctions extends ReactContextBaseJavaModule {
|
|||
TAG,
|
||||
"function:call:onSuccess:" + name
|
||||
);
|
||||
|
||||
Log.d(
|
||||
TAG,
|
||||
"function:call:onSuccess:result:type:" + name + ":" + (result != null ? result.getClass().getName() : "null")
|
||||
"function:call:onSuccess:result:type:" + name + ":" + (result != null ? result
|
||||
.getClass()
|
||||
.getName() : "null")
|
||||
);
|
||||
|
||||
Log.d(
|
||||
TAG,
|
||||
"function:call:onSuccess:result:data:" + name + ":" + (result != null ? result.toString() : "null")
|
||||
|
@ -71,7 +78,6 @@ public class RNFirebaseFunctions extends ReactContextBaseJavaModule {
|
|||
|
||||
Utils.mapPutValue(DATA_KEY, result, map);
|
||||
promise.resolve(map);
|
||||
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
|
@ -87,7 +93,9 @@ public class RNFirebaseFunctions extends ReactContextBaseJavaModule {
|
|||
if (exception instanceof FirebaseFunctionsException) {
|
||||
FirebaseFunctionsException ffe = (FirebaseFunctionsException) exception;
|
||||
details = ffe.getDetails();
|
||||
code = ffe.getCode().name();
|
||||
code = ffe
|
||||
.getCode()
|
||||
.name();
|
||||
message = ffe.getLocalizedMessage();
|
||||
} else {
|
||||
message = exception.getLocalizedMessage();
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package io.invertase.firebase.instanceid;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
@ -28,10 +26,12 @@ public class RNFirebaseInstanceId extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void delete(Promise promise){
|
||||
public void delete(Promise promise) {
|
||||
try {
|
||||
Log.d(TAG, "Deleting instance id");
|
||||
FirebaseInstanceId.getInstance().deleteInstanceId();
|
||||
FirebaseInstanceId
|
||||
.getInstance()
|
||||
.deleteInstanceId();
|
||||
promise.resolve(null);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
|
@ -40,15 +40,19 @@ public class RNFirebaseInstanceId extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void get(Promise promise){
|
||||
String id = FirebaseInstanceId.getInstance().getId();
|
||||
public void get(Promise promise) {
|
||||
String id = FirebaseInstanceId
|
||||
.getInstance()
|
||||
.getId();
|
||||
promise.resolve(id);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getToken(String authorizedEntity, String scope, Promise promise) {
|
||||
try {
|
||||
String token = FirebaseInstanceId.getInstance().getToken(authorizedEntity, scope);
|
||||
String token = FirebaseInstanceId
|
||||
.getInstance()
|
||||
.getToken(authorizedEntity, scope);
|
||||
Log.d(TAG, "Firebase token for " + authorizedEntity + ": " + token);
|
||||
promise.resolve(token);
|
||||
} catch (IOException e) {
|
||||
|
@ -59,7 +63,9 @@ public class RNFirebaseInstanceId extends ReactContextBaseJavaModule {
|
|||
@ReactMethod
|
||||
public void deleteToken(String authorizedEntity, String scope, Promise promise) {
|
||||
try {
|
||||
FirebaseInstanceId.getInstance().deleteToken(authorizedEntity, scope);
|
||||
FirebaseInstanceId
|
||||
.getInstance()
|
||||
.deleteToken(authorizedEntity, scope);
|
||||
Log.d(TAG, "Firebase token deleted for " + authorizedEntity);
|
||||
promise.resolve(null);
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -28,7 +28,6 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
import io.invertase.firebase.links.RNFirebaseLinks;
|
||||
|
||||
public class RNFirebaseInvites extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {
|
||||
private static final String TAG = "RNFirebaseInvites";
|
||||
|
@ -58,21 +57,28 @@ public class RNFirebaseInvites extends ReactContextBaseJavaModule implements Act
|
|||
}
|
||||
} else {
|
||||
if (getCurrentActivity() != null) {
|
||||
FirebaseDynamicLinks.getInstance()
|
||||
FirebaseDynamicLinks
|
||||
.getInstance()
|
||||
.getDynamicLink(getCurrentActivity().getIntent())
|
||||
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
|
||||
@Override
|
||||
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
|
||||
if (pendingDynamicLinkData != null) {
|
||||
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(pendingDynamicLinkData);
|
||||
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(
|
||||
pendingDynamicLinkData);
|
||||
if (invite == null) {
|
||||
promise.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
mInitialDeepLink = pendingDynamicLinkData.getLink().toString();
|
||||
mInitialDeepLink = pendingDynamicLinkData
|
||||
.getLink()
|
||||
.toString();
|
||||
mInitialInvitationId = invite.getInvitationId();
|
||||
promise.resolve(buildInvitationMap(mInitialDeepLink, mInitialInvitationId));
|
||||
promise.resolve(buildInvitationMap(
|
||||
mInitialDeepLink,
|
||||
mInitialInvitationId
|
||||
));
|
||||
} else {
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
@ -83,7 +89,11 @@ public class RNFirebaseInvites extends ReactContextBaseJavaModule implements Act
|
|||
@Override
|
||||
public void onFailure(@NonNull Exception e) {
|
||||
Log.e(TAG, "getInitialInvitation: failed to resolve invitation", e);
|
||||
promise.reject("invites/initial-invitation-error", e.getMessage(), e);
|
||||
promise.reject(
|
||||
"invites/initial-invitation-error",
|
||||
e.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -96,15 +106,22 @@ public class RNFirebaseInvites extends ReactContextBaseJavaModule implements Act
|
|||
@ReactMethod
|
||||
public void sendInvitation(ReadableMap invitationMap, Promise promise) {
|
||||
if (!invitationMap.hasKey("message")) {
|
||||
promise.reject("invites/invalid-invitation", "The supplied invitation is missing a 'message' field");
|
||||
promise.reject(
|
||||
"invites/invalid-invitation",
|
||||
"The supplied invitation is missing a 'message' field"
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!invitationMap.hasKey("title")) {
|
||||
promise.reject("invites/invalid-invitation", "The supplied invitation is missing a 'title' field");
|
||||
promise.reject(
|
||||
"invites/invalid-invitation",
|
||||
"The supplied invitation is missing a 'title' field"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
AppInviteInvitation.IntentBuilder ib = new AppInviteInvitation.IntentBuilder(invitationMap.getString("title"));
|
||||
AppInviteInvitation.IntentBuilder ib = new AppInviteInvitation.IntentBuilder(invitationMap.getString(
|
||||
"title"));
|
||||
if (invitationMap.hasKey("androidMinimumVersionCode")) {
|
||||
Double androidMinimumVersionCode = invitationMap.getDouble("androidMinimumVersionCode");
|
||||
ib = ib.setAndroidMinimumVersionCode(androidMinimumVersionCode.intValue());
|
||||
|
@ -121,7 +138,8 @@ public class RNFirebaseInvites extends ReactContextBaseJavaModule implements Act
|
|||
if (invitationMap.hasKey("iosClientId")) {
|
||||
ib = ib.setOtherPlatformsTargetApplication(
|
||||
AppInviteInvitation.IntentBuilder.PlatformMode.PROJECT_PLATFORM_IOS,
|
||||
invitationMap.getString("iosClientId"));
|
||||
invitationMap.getString("iosClientId")
|
||||
);
|
||||
}
|
||||
ib = ib.setMessage(invitationMap.getString("message"));
|
||||
|
||||
|
@ -155,7 +173,9 @@ public class RNFirebaseInvites extends ReactContextBaseJavaModule implements Act
|
|||
this.mPromise = promise;
|
||||
|
||||
// Start the intent
|
||||
this.getCurrentActivity().startActivityForResult(invitationIntent, REQUEST_INVITE);
|
||||
this
|
||||
.getCurrentActivity()
|
||||
.startActivityForResult(invitationIntent, REQUEST_INVITE);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
@ -179,22 +199,33 @@ public class RNFirebaseInvites extends ReactContextBaseJavaModule implements Act
|
|||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
FirebaseDynamicLinks.getInstance()
|
||||
FirebaseDynamicLinks
|
||||
.getInstance()
|
||||
.getDynamicLink(intent)
|
||||
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
|
||||
@Override
|
||||
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
|
||||
if (pendingDynamicLinkData != null) {
|
||||
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(pendingDynamicLinkData);
|
||||
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(
|
||||
pendingDynamicLinkData);
|
||||
if (invite == null) {
|
||||
// this is a dynamic link, not an invitation
|
||||
return;
|
||||
}
|
||||
|
||||
String deepLink = pendingDynamicLinkData.getLink().toString();
|
||||
String deepLink = pendingDynamicLinkData
|
||||
.getLink()
|
||||
.toString();
|
||||
String invitationId = invite.getInvitationId();
|
||||
WritableMap invitationMap = buildInvitationMap(deepLink, invitationId);
|
||||
Utils.sendEvent(getReactApplicationContext(), "invites_invitation_received", invitationMap);
|
||||
WritableMap invitationMap = buildInvitationMap(
|
||||
deepLink,
|
||||
invitationId
|
||||
);
|
||||
Utils.sendEvent(
|
||||
getReactApplicationContext(),
|
||||
"invites_invitation_received",
|
||||
invitationMap
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,17 +13,15 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
|||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.appinvite.FirebaseAppInvite;
|
||||
import com.google.firebase.dynamiclinks.DynamicLink;
|
||||
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
|
||||
import com.google.firebase.dynamiclinks.ShortDynamicLink;
|
||||
import com.google.firebase.dynamiclinks.PendingDynamicLinkData;
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.firebase.dynamiclinks.ShortDynamicLink;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
@ -47,7 +45,10 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
public void createDynamicLink(final ReadableMap linkData, final Promise promise) {
|
||||
try {
|
||||
DynamicLink.Builder builder = getDynamicLinkBuilder(linkData);
|
||||
String link = builder.buildDynamicLink().getUri().toString();
|
||||
String link = builder
|
||||
.buildDynamicLink()
|
||||
.getUri()
|
||||
.toString();
|
||||
Log.d(TAG, "created dynamic link: " + link);
|
||||
promise.resolve(link);
|
||||
} catch (Exception ex) {
|
||||
|
@ -57,7 +58,11 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void createShortDynamicLink(final ReadableMap linkData, final String type, final Promise promise) {
|
||||
public void createShortDynamicLink(
|
||||
final ReadableMap linkData,
|
||||
final String type,
|
||||
final Promise promise
|
||||
) {
|
||||
try {
|
||||
DynamicLink.Builder builder = getDynamicLinkBuilder(linkData);
|
||||
Task<ShortDynamicLink> shortLinkTask;
|
||||
|
@ -70,16 +75,30 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
}
|
||||
|
||||
shortLinkTask.addOnCompleteListener(new OnCompleteListener<ShortDynamicLink>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<ShortDynamicLink> task) {
|
||||
if (task.isSuccessful()) {
|
||||
String shortLink = task.getResult().getShortLink().toString();
|
||||
Log.d(TAG, "created short dynamic link: " + shortLink);
|
||||
promise.resolve(shortLink);
|
||||
} else {
|
||||
Log.e(TAG, "create short dynamic link failure " + task.getException().getMessage());
|
||||
promise.reject("links/failure", task.getException().getMessage(), task.getException());
|
||||
}
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<ShortDynamicLink> task) {
|
||||
if (task.isSuccessful()) {
|
||||
String shortLink = task
|
||||
.getResult()
|
||||
.getShortLink()
|
||||
.toString();
|
||||
Log.d(TAG, "created short dynamic link: " + shortLink);
|
||||
promise.resolve(shortLink);
|
||||
} else {
|
||||
Log.e(
|
||||
TAG,
|
||||
"create short dynamic link failure " + task
|
||||
.getException()
|
||||
.getMessage()
|
||||
);
|
||||
promise.reject(
|
||||
"links/failure",
|
||||
task
|
||||
.getException()
|
||||
.getMessage(),
|
||||
task.getException()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
|
@ -94,7 +113,8 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
promise.resolve(mInitialLink);
|
||||
} else {
|
||||
if (getCurrentActivity() != null) {
|
||||
FirebaseDynamicLinks.getInstance()
|
||||
FirebaseDynamicLinks
|
||||
.getInstance()
|
||||
.getDynamicLink(getCurrentActivity().getIntent())
|
||||
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
|
||||
@Override
|
||||
|
@ -102,7 +122,9 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
if (pendingDynamicLinkData != null
|
||||
&& !isInvitation(pendingDynamicLinkData)) {
|
||||
|
||||
mInitialLink = pendingDynamicLinkData.getLink().toString();
|
||||
mInitialLink = pendingDynamicLinkData
|
||||
.getLink()
|
||||
.toString();
|
||||
}
|
||||
Log.d(TAG, "getInitialLink: link is: " + mInitialLink);
|
||||
mInitialLinkInitialized = true;
|
||||
|
@ -133,15 +155,22 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
FirebaseDynamicLinks.getInstance()
|
||||
FirebaseDynamicLinks
|
||||
.getInstance()
|
||||
.getDynamicLink(intent)
|
||||
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
|
||||
@Override
|
||||
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
|
||||
if (pendingDynamicLinkData != null
|
||||
&& !isInvitation(pendingDynamicLinkData)) {
|
||||
String link = pendingDynamicLinkData.getLink().toString();
|
||||
Utils.sendEvent(getReactApplicationContext(), "links_link_received", link);
|
||||
String link = pendingDynamicLinkData
|
||||
.getLink()
|
||||
.toString();
|
||||
Utils.sendEvent(
|
||||
getReactApplicationContext(),
|
||||
"links_link_received",
|
||||
link
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -175,14 +204,18 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
// Looks at the internals of the link data to detect whether it's an invitation or not
|
||||
private boolean isInvitation(PendingDynamicLinkData pendingDynamicLinkData) {
|
||||
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(pendingDynamicLinkData);
|
||||
if (invite != null && invite.getInvitationId() != null && !invite.getInvitationId().isEmpty()) {
|
||||
if (invite != null && invite.getInvitationId() != null && !invite
|
||||
.getInvitationId()
|
||||
.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private DynamicLink.Builder getDynamicLinkBuilder(final ReadableMap linkData) {
|
||||
DynamicLink.Builder builder = FirebaseDynamicLinks.getInstance().createDynamicLink();
|
||||
DynamicLink.Builder builder = FirebaseDynamicLinks
|
||||
.getInstance()
|
||||
.createDynamicLink();
|
||||
try {
|
||||
builder.setLink(Uri.parse(linkData.getString("link")));
|
||||
builder.setDynamicLinkDomain(linkData.getString("dynamicLinkDomain"));
|
||||
|
@ -199,7 +232,10 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
return builder;
|
||||
}
|
||||
|
||||
private void setAnalyticsParameters(final ReadableMap analyticsData, final DynamicLink.Builder builder) {
|
||||
private void setAnalyticsParameters(
|
||||
final ReadableMap analyticsData,
|
||||
final DynamicLink.Builder builder
|
||||
) {
|
||||
DynamicLink.GoogleAnalyticsParameters.Builder analyticsParameters = new DynamicLink.GoogleAnalyticsParameters.Builder();
|
||||
|
||||
if (analyticsData.hasKey("campaign")) {
|
||||
|
@ -220,9 +256,13 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
builder.setGoogleAnalyticsParameters(analyticsParameters.build());
|
||||
}
|
||||
|
||||
private void setAndroidParameters(final ReadableMap androidData, final DynamicLink.Builder builder) {
|
||||
private void setAndroidParameters(
|
||||
final ReadableMap androidData,
|
||||
final DynamicLink.Builder builder
|
||||
) {
|
||||
if (androidData.hasKey("packageName")) {
|
||||
DynamicLink.AndroidParameters.Builder androidParameters = new DynamicLink.AndroidParameters.Builder(androidData.getString("packageName"));
|
||||
DynamicLink.AndroidParameters.Builder androidParameters = new DynamicLink.AndroidParameters.Builder(
|
||||
androidData.getString("packageName"));
|
||||
|
||||
if (androidData.hasKey("fallbackUrl")) {
|
||||
androidParameters.setFallbackUrl(Uri.parse(androidData.getString("fallbackUrl")));
|
||||
|
@ -261,7 +301,10 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
}
|
||||
}
|
||||
|
||||
private void setITunesParameters(final ReadableMap itunesData, final DynamicLink.Builder builder) {
|
||||
private void setITunesParameters(
|
||||
final ReadableMap itunesData,
|
||||
final DynamicLink.Builder builder
|
||||
) {
|
||||
DynamicLink.ItunesConnectAnalyticsParameters.Builder itunesParameters = new DynamicLink.ItunesConnectAnalyticsParameters.Builder();
|
||||
|
||||
if (itunesData.hasKey("affiliateToken")) {
|
||||
|
@ -276,16 +319,23 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
|
|||
builder.setItunesConnectAnalyticsParameters(itunesParameters.build());
|
||||
}
|
||||
|
||||
private void setNavigationParameters(final ReadableMap navigationData, final DynamicLink.Builder builder) {
|
||||
private void setNavigationParameters(
|
||||
final ReadableMap navigationData,
|
||||
final DynamicLink.Builder builder
|
||||
) {
|
||||
DynamicLink.NavigationInfoParameters.Builder navigationParameters = new DynamicLink.NavigationInfoParameters.Builder();
|
||||
|
||||
if (navigationData.hasKey("forcedRedirectEnabled")) {
|
||||
navigationParameters.setForcedRedirectEnabled(navigationData.getBoolean("forcedRedirectEnabled"));
|
||||
navigationParameters.setForcedRedirectEnabled(navigationData.getBoolean(
|
||||
"forcedRedirectEnabled"));
|
||||
}
|
||||
builder.setNavigationInfoParameters(navigationParameters.build());
|
||||
}
|
||||
|
||||
private void setSocialParameters(final ReadableMap socialData, final DynamicLink.Builder builder) {
|
||||
private void setSocialParameters(
|
||||
final ReadableMap socialData,
|
||||
final DynamicLink.Builder builder
|
||||
) {
|
||||
DynamicLink.SocialMetaTagParameters.Builder socialParameters = new DynamicLink.SocialMetaTagParameters.Builder();
|
||||
|
||||
if (socialData.hasKey("descriptionText")) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.invertase.firebase.links;
|
||||
|
||||
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;
|
||||
|
|
|
@ -39,9 +39,9 @@ import java.util.Map;
|
|||
* com.facebook.internal is solely for the use of other packages within the Facebook SDK for
|
||||
* Android. Use of any of the classes in this package is unsupported, and they may be modified or
|
||||
* removed without warning at any time.
|
||||
*
|
||||
* <p>
|
||||
* A helper class that can round trip between JSON and Bundle objects that contains the types:
|
||||
* Boolean, Integer, Long, Double, String
|
||||
* Boolean, Integer, Long, Double, String
|
||||
* If other types are found, an IllegalArgumentException is thrown.
|
||||
*/
|
||||
public class BundleJSONConverter {
|
||||
|
@ -126,13 +126,13 @@ public class BundleJSONConverter {
|
|||
} else if (jsonArray.get(0) instanceof Boolean) {
|
||||
boolean[] booleanArray = new boolean[jsonArray.length()];
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
booleanArray[i] = (Boolean)jsonArray.get(i);
|
||||
booleanArray[i] = (Boolean) jsonArray.get(i);
|
||||
}
|
||||
bundle.putBooleanArray(key, booleanArray);
|
||||
} else if (jsonArray.get(0) instanceof Double) {
|
||||
double[] doubleArray = new double[jsonArray.length()];
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
doubleArray[i] = (Double)jsonArray.get(i);
|
||||
doubleArray[i] = (Double) jsonArray.get(i);
|
||||
}
|
||||
bundle.putDoubleArray(key, doubleArray);
|
||||
} else if (jsonArray.get(0) instanceof Long) {
|
||||
|
@ -143,12 +143,14 @@ public class BundleJSONConverter {
|
|||
bundle.putLongArray(key, longArray);
|
||||
} else if (jsonArray.get(0) instanceof JSONObject) {
|
||||
ArrayList<Bundle> bundleArrayList = new ArrayList<>();
|
||||
for (int i =0; i < jsonArray.length(); i++) {
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
bundleArrayList.add(convertToBundle((JSONObject) jsonArray.get(i)));
|
||||
}
|
||||
bundle.putSerializable(key, bundleArrayList);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected type in an array: " + jsonArray.get(0).getClass());
|
||||
throw new IllegalArgumentException("Unexpected type in an array: " + jsonArray
|
||||
.get(0)
|
||||
.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,12 +161,6 @@ public class BundleJSONConverter {
|
|||
});
|
||||
}
|
||||
|
||||
public interface Setter {
|
||||
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException;
|
||||
|
||||
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException;
|
||||
}
|
||||
|
||||
public static JSONObject convertToJSON(Bundle bundle) throws JSONException {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
|
@ -239,4 +235,10 @@ public class BundleJSONConverter {
|
|||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public interface Setter {
|
||||
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException;
|
||||
|
||||
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.google.firebase.messaging.RemoteMessage;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class MessagingSerializer {
|
||||
public static WritableMap parseRemoteMessage(RemoteMessage message) {
|
||||
WritableMap messageMap = Arguments.createMap();
|
||||
|
@ -17,7 +16,9 @@ public class MessagingSerializer {
|
|||
}
|
||||
|
||||
if (message.getData() != null) {
|
||||
for (Map.Entry<String, String> e : message.getData().entrySet()) {
|
||||
for (Map.Entry<String, String> e : message
|
||||
.getData()
|
||||
.entrySet()) {
|
||||
dataMap.putString(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Intent;
|
|||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.HeadlessJsTaskService;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
@ -13,7 +12,8 @@ import javax.annotation.Nullable;
|
|||
|
||||
public class RNFirebaseBackgroundMessagingService extends HeadlessJsTaskService {
|
||||
@Override
|
||||
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
|
||||
protected @Nullable
|
||||
HeadlessJsTaskConfig getTaskConfig(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras != null) {
|
||||
RemoteMessage message = intent.getParcelableExtra("message");
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.invertase.firebase.messaging;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
@ -8,8 +7,8 @@ import android.util.Log;
|
|||
import com.google.firebase.iid.FirebaseInstanceIdService;
|
||||
|
||||
public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
||||
private static final String TAG = "RNFInstanceIdService";
|
||||
public static final String TOKEN_REFRESH_EVENT = "messaging-token-refresh";
|
||||
private static final String TAG = "RNFInstanceIdService";
|
||||
|
||||
@Override
|
||||
public void onTokenRefresh() {
|
||||
|
@ -19,6 +18,8 @@ public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
|||
Intent tokenRefreshEvent = new Intent(TOKEN_REFRESH_EVENT);
|
||||
|
||||
// Broadcast it so it is only available to the RN Application
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(tokenRefreshEvent);
|
||||
LocalBroadcastManager
|
||||
.getInstance(this)
|
||||
.sendBroadcast(tokenRefreshEvent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
@ -32,12 +32,16 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
|||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
|
||||
// Subscribe to message events
|
||||
localBroadcastManager.registerReceiver(new MessageReceiver(),
|
||||
new IntentFilter(RNFirebaseMessagingService.MESSAGE_EVENT));
|
||||
localBroadcastManager.registerReceiver(
|
||||
new MessageReceiver(),
|
||||
new IntentFilter(RNFirebaseMessagingService.MESSAGE_EVENT)
|
||||
);
|
||||
|
||||
// Subscribe to token refresh events
|
||||
localBroadcastManager.registerReceiver(new RefreshTokenReceiver(),
|
||||
new IntentFilter(RNFirebaseInstanceIdService.TOKEN_REFRESH_EVENT));
|
||||
localBroadcastManager.registerReceiver(
|
||||
new RefreshTokenReceiver(),
|
||||
new IntentFilter(RNFirebaseInstanceIdService.TOKEN_REFRESH_EVENT)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,7 +51,9 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void getToken(Promise promise) {
|
||||
String token = FirebaseInstanceId.getInstance().getToken();
|
||||
String token = FirebaseInstanceId
|
||||
.getInstance()
|
||||
.getToken();
|
||||
Log.d(TAG, "Firebase token: " + token);
|
||||
promise.resolve(token);
|
||||
}
|
||||
|
@ -60,7 +66,9 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
|||
// Non Web SDK methods
|
||||
@ReactMethod
|
||||
public void hasPermission(Promise promise) {
|
||||
Boolean enabled = NotificationManagerCompat.from(getReactApplicationContext()).areNotificationsEnabled();
|
||||
Boolean enabled = NotificationManagerCompat
|
||||
.from(getReactApplicationContext())
|
||||
.areNotificationsEnabled();
|
||||
promise.resolve(enabled);
|
||||
}
|
||||
|
||||
|
@ -94,7 +102,9 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
|||
}
|
||||
}
|
||||
|
||||
FirebaseMessaging.getInstance().send(mb.build());
|
||||
FirebaseMessaging
|
||||
.getInstance()
|
||||
.send(mb.build());
|
||||
|
||||
// TODO: Listen to onMessageSent and onSendError for better feedback?
|
||||
promise.resolve(null);
|
||||
|
@ -102,36 +112,42 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void subscribeToTopic(String topic, final Promise promise) {
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(topic).addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "subscribeToTopic:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Exception exception = task.getException();
|
||||
Log.e(TAG, "subscribeToTopic:onComplete:failure", exception);
|
||||
promise.reject(exception);
|
||||
FirebaseMessaging
|
||||
.getInstance()
|
||||
.subscribeToTopic(topic)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "subscribeToTopic:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Exception exception = task.getException();
|
||||
Log.e(TAG, "subscribeToTopic:onComplete:failure", exception);
|
||||
promise.reject(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void unsubscribeFromTopic(String topic, final Promise promise) {
|
||||
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic).addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "unsubscribeFromTopic:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Exception exception = task.getException();
|
||||
Log.e(TAG, "unsubscribeFromTopic:onComplete:failure", exception);
|
||||
promise.reject(exception);
|
||||
FirebaseMessaging
|
||||
.getInstance()
|
||||
.unsubscribeFromTopic(topic)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "unsubscribeFromTopic:onComplete:success");
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
Exception exception = task.getException();
|
||||
Log.e(TAG, "unsubscribeFromTopic:onComplete:failure", exception);
|
||||
promise.reject(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private class MessageReceiver extends BroadcastReceiver {
|
||||
|
@ -152,7 +168,9 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
|||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
String token = FirebaseInstanceId.getInstance().getToken();
|
||||
String token = FirebaseInstanceId
|
||||
.getInstance()
|
||||
.getToken();
|
||||
Log.d(TAG, "Received new FCM token: " + token);
|
||||
|
||||
Utils.sendEvent(getReactApplicationContext(), "messaging_token_refreshed", token);
|
||||
|
|
|
@ -11,9 +11,9 @@ import com.google.firebase.messaging.RemoteMessage;
|
|||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseMessagingService extends FirebaseMessagingService {
|
||||
private static final String TAG = "RNFMessagingService";
|
||||
public static final String MESSAGE_EVENT = "messaging-message";
|
||||
public static final String REMOTE_NOTIFICATION_EVENT = "notifications-remote-notification";
|
||||
private static final String TAG = "RNFMessagingService";
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(RemoteMessage message) {
|
||||
|
@ -25,7 +25,9 @@ public class RNFirebaseMessagingService extends FirebaseMessagingService {
|
|||
notificationEvent.putExtra("notification", message);
|
||||
|
||||
// Broadcast it to the (foreground) RN Application
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(notificationEvent);
|
||||
LocalBroadcastManager
|
||||
.getInstance(this)
|
||||
.sendBroadcast(notificationEvent);
|
||||
} else {
|
||||
// It's a data message
|
||||
// If the app is in the foreground we send it to the Messaging module
|
||||
|
@ -33,16 +35,27 @@ public class RNFirebaseMessagingService extends FirebaseMessagingService {
|
|||
Intent messagingEvent = new Intent(MESSAGE_EVENT);
|
||||
messagingEvent.putExtra("message", message);
|
||||
// Broadcast it so it is only available to the RN Application
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(messagingEvent);
|
||||
LocalBroadcastManager
|
||||
.getInstance(this)
|
||||
.sendBroadcast(messagingEvent);
|
||||
} else {
|
||||
try {
|
||||
// If the app is in the background we send it to the Headless JS Service
|
||||
Intent headlessIntent = new Intent(this.getApplicationContext(), RNFirebaseBackgroundMessagingService.class);
|
||||
Intent headlessIntent = new Intent(
|
||||
this.getApplicationContext(),
|
||||
RNFirebaseBackgroundMessagingService.class
|
||||
);
|
||||
headlessIntent.putExtra("message", message);
|
||||
this.getApplicationContext().startService(headlessIntent);
|
||||
this
|
||||
.getApplicationContext()
|
||||
.startService(headlessIntent);
|
||||
HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext());
|
||||
} catch (IllegalStateException ex) {
|
||||
Log.e(TAG, "Background messages will only work if the message priority is set to 'high'", ex);
|
||||
Log.e(
|
||||
TAG,
|
||||
"Background messages will only work if the message priority is set to 'high'",
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,11 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
private final Promise promise;
|
||||
private ReactApplicationContext reactContext;
|
||||
|
||||
public DisplayNotificationTask(Context context, ReactApplicationContext reactContext,
|
||||
NotificationManager notificationManager,
|
||||
Bundle notification, Promise promise) {
|
||||
public DisplayNotificationTask(
|
||||
Context context, ReactApplicationContext reactContext,
|
||||
NotificationManager notificationManager,
|
||||
Bundle notification, Promise promise
|
||||
) {
|
||||
this.context = context;
|
||||
this.notification = notification;
|
||||
this.notificationManager = notificationManager;
|
||||
|
@ -53,7 +55,10 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
Class intentClass = getMainActivityClass();
|
||||
if (intentClass == null) {
|
||||
if (promise != null) {
|
||||
promise.reject("notification/display_notification_error", "Could not find main activity class");
|
||||
promise.reject(
|
||||
"notification/display_notification_error",
|
||||
"Could not find main activity class"
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -77,7 +82,10 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
nb = nb.setExtras(notification.getBundle("data"));
|
||||
}
|
||||
if (notification.containsKey("sound")) {
|
||||
Uri sound = RNFirebaseNotificationManager.getSound(context, notification.getString("sound"));
|
||||
Uri sound = RNFirebaseNotificationManager.getSound(
|
||||
context,
|
||||
notification.getString("sound")
|
||||
);
|
||||
nb = nb.setSound(sound);
|
||||
}
|
||||
if (notification.containsKey("subtitle")) {
|
||||
|
@ -148,7 +156,7 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
|
||||
if (defaults == 0) {
|
||||
ArrayList<Integer> defaultsArray = android.getIntegerArrayList("defaults");
|
||||
if(defaultsArray != null) {
|
||||
if (defaultsArray != null) {
|
||||
for (Integer defaultValue : defaultsArray) {
|
||||
defaults |= defaultValue;
|
||||
}
|
||||
|
@ -192,7 +200,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");
|
||||
|
@ -210,7 +218,11 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
Bundle progress = android.getBundle("progress");
|
||||
Double max = progress.getDouble("max");
|
||||
Double progressI = progress.getDouble("progress");
|
||||
nb = nb.setProgress(max.intValue(), progressI.intValue(), progress.getBoolean("indeterminate"));
|
||||
nb = nb.setProgress(
|
||||
max.intValue(),
|
||||
progressI.intValue(),
|
||||
progress.getBoolean("indeterminate")
|
||||
);
|
||||
}
|
||||
// TODO: Public version of notification
|
||||
/* if (android.containsKey("publicVersion")) {
|
||||
|
@ -253,10 +265,12 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
}
|
||||
if (android.containsKey("vibrate")) {
|
||||
ArrayList<Integer> vibrate = android.getIntegerArrayList("vibrate");
|
||||
if(vibrate != null) {
|
||||
if (vibrate != null) {
|
||||
long[] vibrateArray = new long[vibrate.size()];
|
||||
for (int i = 0; i < vibrate.size(); i++) {
|
||||
vibrateArray[i] = vibrate.get(i).longValue();
|
||||
vibrateArray[i] = vibrate
|
||||
.get(i)
|
||||
.longValue();
|
||||
}
|
||||
nb = nb.setVibrate(vibrateArray);
|
||||
}
|
||||
|
@ -279,16 +293,29 @@ 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"));
|
||||
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));
|
||||
Utils.sendEvent(
|
||||
reactContext,
|
||||
"notifications_notification_displayed",
|
||||
Arguments.fromBundle(notification)
|
||||
);
|
||||
}
|
||||
|
||||
if (promise != null) {
|
||||
|
@ -305,8 +332,13 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
return null;
|
||||
}
|
||||
|
||||
private NotificationCompat.Action createAction(Bundle action, Class intentClass, Bundle notification) {
|
||||
boolean showUserInterface = action.containsKey("showUserInterface") && action.getBoolean("showUserInterface");
|
||||
private NotificationCompat.Action createAction(
|
||||
Bundle action,
|
||||
Class intentClass,
|
||||
Bundle notification
|
||||
) {
|
||||
boolean showUserInterface = action.containsKey("showUserInterface") && action.getBoolean(
|
||||
"showUserInterface");
|
||||
String actionKey = action.getString("action");
|
||||
PendingIntent actionIntent = showUserInterface ?
|
||||
createIntent(intentClass, notification, actionKey) :
|
||||
|
@ -314,7 +346,11 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
int icon = getIcon(action.getString("icon"));
|
||||
String title = action.getString("title");
|
||||
|
||||
NotificationCompat.Action.Builder ab = new NotificationCompat.Action.Builder(icon, title, actionIntent);
|
||||
NotificationCompat.Action.Builder ab = new NotificationCompat.Action.Builder(
|
||||
icon,
|
||||
title,
|
||||
actionIntent
|
||||
);
|
||||
|
||||
if (action.containsKey("allowGeneratedReplies")) {
|
||||
ab = ab.setAllowGeneratedReplies(action.getBoolean("allowGeneratedReplies"));
|
||||
|
@ -348,7 +384,12 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
}
|
||||
|
||||
String notificationId = notification.getString("notificationId");
|
||||
return PendingIntent.getActivity(context, notificationId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
notificationId.hashCode(),
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
}
|
||||
|
||||
private PendingIntent createBroadcastIntent(Bundle notification, String action) {
|
||||
|
@ -360,7 +401,12 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
intent.setAction("io.invertase.firebase.notifications.BackgroundAction");
|
||||
intent.putExtra("action", action);
|
||||
intent.putExtra("notification", notification);
|
||||
return PendingIntent.getBroadcast(context, notificationId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId.hashCode(),
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
}
|
||||
|
||||
private RemoteInput createRemoteInput(Bundle remoteInput) {
|
||||
|
@ -412,18 +458,22 @@ public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
|||
}
|
||||
|
||||
private int getIcon(String icon) {
|
||||
int resourceId = RNFirebaseNotificationManager.getResourceId(context,"mipmap", icon);
|
||||
int resourceId = RNFirebaseNotificationManager.getResourceId(context, "mipmap", icon);
|
||||
if (resourceId == 0) {
|
||||
resourceId = RNFirebaseNotificationManager.getResourceId(context,"drawable", icon);
|
||||
resourceId = RNFirebaseNotificationManager.getResourceId(context, "drawable", icon);
|
||||
}
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
private Class getMainActivityClass() {
|
||||
String packageName = context.getPackageName();
|
||||
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
|
||||
Intent launchIntent = context
|
||||
.getPackageManager()
|
||||
.getLaunchIntentForPackage(packageName);
|
||||
try {
|
||||
return Class.forName(launchIntent.getComponent().getClassName());
|
||||
return Class.forName(launchIntent
|
||||
.getComponent()
|
||||
.getClassName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG, "Failed to get main activity class", e);
|
||||
return null;
|
||||
|
|
|
@ -36,12 +36,18 @@ public class RNFirebaseBackgroundNotificationActionReceiver extends BroadcastRec
|
|||
if (Utils.isAppInForeground(context)) {
|
||||
WritableMap notificationOpenMap = toNotificationOpenMap(intent);
|
||||
|
||||
ReactApplication reactApplication = (ReactApplication)context.getApplicationContext();
|
||||
ReactContext reactContext = reactApplication.getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
|
||||
ReactApplication reactApplication = (ReactApplication) context.getApplicationContext();
|
||||
ReactContext reactContext = reactApplication
|
||||
.getReactNativeHost()
|
||||
.getReactInstanceManager()
|
||||
.getCurrentReactContext();
|
||||
|
||||
Utils.sendEvent(reactContext, "notifications_notification_opened", notificationOpenMap);
|
||||
} else {
|
||||
Intent serviceIntent = new Intent(context, RNFirebaseBackgroundNotificationActionsService.class);
|
||||
Intent serviceIntent = new Intent(
|
||||
context,
|
||||
RNFirebaseBackgroundNotificationActionsService.class
|
||||
);
|
||||
serviceIntent.putExtras(intent.getExtras());
|
||||
context.startService(serviceIntent);
|
||||
HeadlessJsTaskService.acquireWakeLockNow(context);
|
||||
|
|
|
@ -13,7 +13,8 @@ import static io.invertase.firebase.notifications.RNFirebaseBackgroundNotificati
|
|||
|
||||
public class RNFirebaseBackgroundNotificationActionsService extends HeadlessJsTaskService {
|
||||
@Override
|
||||
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
|
||||
protected @Nullable
|
||||
HeadlessJsTaskConfig getTaskConfig(Intent intent) {
|
||||
if (isBackgroundNotficationIntent(intent)) {
|
||||
WritableMap notificationOpenMap = toNotificationOpenMap(intent);
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.invertase.firebase.notifications;
|
|||
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
|
@ -10,16 +9,13 @@ import android.app.PendingIntent;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
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;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -32,9 +28,6 @@ import com.facebook.react.bridge.ReadableMap;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
@ -44,8 +37,8 @@ import io.invertase.firebase.Utils;
|
|||
import io.invertase.firebase.messaging.BundleJSONConverter;
|
||||
|
||||
public class RNFirebaseNotificationManager {
|
||||
private static final String PREFERENCES_KEY = "RNFNotifications";
|
||||
public static final String SCHEDULED_NOTIFICATION_EVENT = "notifications-scheduled-notification";
|
||||
private static final String PREFERENCES_KEY = "RNFNotifications";
|
||||
private static final String TAG = "RNFNotificationManager";
|
||||
private AlarmManager alarmManager;
|
||||
private Context context;
|
||||
|
@ -61,31 +54,63 @@ public class RNFirebaseNotificationManager {
|
|||
public RNFirebaseNotificationManager(Context context) {
|
||||
this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
this.context = context;
|
||||
this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
this.preferences = context.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static int getResourceId(Context context, String type, String image) {
|
||||
return context
|
||||
.getResources()
|
||||
.getIdentifier(image, type, context.getPackageName());
|
||||
}
|
||||
|
||||
public static Uri getSound(Context context, String sound) {
|
||||
if (sound == null) {
|
||||
return null;
|
||||
} else if (sound.contains("://")) {
|
||||
return Uri.parse(sound);
|
||||
} else if (sound.equalsIgnoreCase("default")) {
|
||||
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
} else {
|
||||
int soundResourceId = getResourceId(context, "raw", sound);
|
||||
if (soundResourceId == 0) {
|
||||
soundResourceId = getResourceId(context, "raw", sound.substring(0, sound.lastIndexOf('.')));
|
||||
}
|
||||
return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelAllNotifications(Promise promise) {
|
||||
try {
|
||||
Map<String, ?> notifications = preferences.getAll();
|
||||
|
||||
for(String notificationId : notifications.keySet()){
|
||||
for (String notificationId : notifications.keySet()) {
|
||||
cancelAlarm(notificationId);
|
||||
}
|
||||
preferences.edit().clear().apply();
|
||||
preferences
|
||||
.edit()
|
||||
.clear()
|
||||
.apply();
|
||||
promise.resolve(null);
|
||||
} catch (SecurityException e) {
|
||||
// TODO: Identify what these situations are
|
||||
// In some devices/situations cancelAllLocalNotifications can throw a SecurityException.
|
||||
Log.e(TAG, e.getMessage());
|
||||
promise.reject("notification/cancel_notifications_error", "Could not cancel notifications", e);
|
||||
promise.reject(
|
||||
"notification/cancel_notifications_error",
|
||||
"Could not cancel notifications",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelNotification(String notificationId, Promise promise) {
|
||||
try {
|
||||
cancelAlarm(notificationId);
|
||||
preferences.edit().remove(notificationId).apply();
|
||||
preferences
|
||||
.edit()
|
||||
.remove(notificationId)
|
||||
.apply();
|
||||
promise.resolve(null);
|
||||
} catch (SecurityException e) {
|
||||
// TODO: Identify what these situations are
|
||||
|
@ -150,10 +175,17 @@ public class RNFirebaseNotificationManager {
|
|||
|
||||
public void displayScheduledNotification(Bundle notification) {
|
||||
// If this isn't a repeated notification, clear it from the scheduled notifications list
|
||||
if (!notification.getBundle("schedule").containsKey("repeated")
|
||||
|| !notification.getBundle("schedule").getBoolean("repeated")) {
|
||||
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)) {
|
||||
|
@ -161,21 +193,23 @@ public class RNFirebaseNotificationManager {
|
|||
// It is up to the JS to decide whether to display the notification
|
||||
Intent scheduledNotificationEvent = new Intent(SCHEDULED_NOTIFICATION_EVENT);
|
||||
scheduledNotificationEvent.putExtra("notification", notification);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(scheduledNotificationEvent);
|
||||
LocalBroadcastManager
|
||||
.getInstance(context)
|
||||
.sendBroadcast(scheduledNotificationEvent);
|
||||
} else {
|
||||
// If the app is in the background, then we display it automatically
|
||||
displayNotification(notification, null);
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<Bundle> getScheduledNotifications(){
|
||||
public ArrayList<Bundle> getScheduledNotifications() {
|
||||
ArrayList<Bundle> array = new ArrayList<>();
|
||||
|
||||
Map<String, ?> notifications = preferences.getAll();
|
||||
|
||||
for(String notificationId : notifications.keySet()){
|
||||
for (String notificationId : notifications.keySet()) {
|
||||
try {
|
||||
JSONObject json = new JSONObject((String)notifications.get(notificationId));
|
||||
JSONObject json = new JSONObject((String) notifications.get(notificationId));
|
||||
Bundle bundle = BundleJSONConverter.convertToBundle(json);
|
||||
array.add(bundle);
|
||||
} catch (JSONException e) {
|
||||
|
@ -195,10 +229,19 @@ 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();
|
||||
for(Bundle bundle: bundles){
|
||||
for (Bundle bundle : bundles) {
|
||||
scheduleNotification(bundle, null);
|
||||
}
|
||||
}
|
||||
|
@ -211,32 +254,23 @@ public class RNFirebaseNotificationManager {
|
|||
|
||||
private void cancelAlarm(String notificationId) {
|
||||
Intent notificationIntent = new Intent(context, RNFirebaseNotificationReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId.hashCode(),
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
alarmManager.cancel(pendingIntent);
|
||||
}
|
||||
|
||||
private void displayNotification(Bundle notification, Promise promise) {
|
||||
new DisplayNotificationTask(context, reactContext, notificationManager, notification, promise).execute();
|
||||
}
|
||||
|
||||
public static int getResourceId(Context context, String type, String image) {
|
||||
return context.getResources().getIdentifier(image, type, context.getPackageName());
|
||||
}
|
||||
|
||||
public static Uri getSound(Context context, String sound) {
|
||||
if (sound == null) {
|
||||
return null;
|
||||
} else if (sound.contains("://")) {
|
||||
return Uri.parse(sound);
|
||||
} else if (sound.equalsIgnoreCase("default")) {
|
||||
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
} else {
|
||||
int soundResourceId = getResourceId(context,"raw", sound);
|
||||
if (soundResourceId == 0) {
|
||||
soundResourceId = getResourceId(context,"raw", sound.substring(0, sound.lastIndexOf('.')));
|
||||
}
|
||||
return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId);
|
||||
}
|
||||
new DisplayNotificationTask(
|
||||
context,
|
||||
reactContext,
|
||||
notificationManager,
|
||||
notification,
|
||||
promise
|
||||
).execute();
|
||||
}
|
||||
|
||||
private NotificationChannelGroup parseChannelGroupMap(ReadableMap channelGroupMap) {
|
||||
|
@ -320,15 +354,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");
|
||||
|
@ -342,33 +380,63 @@ public class RNFirebaseNotificationManager {
|
|||
// We store them so that they can be re-scheduled when the phone restarts in RNFirebaseNotificationsRebootReceiver
|
||||
try {
|
||||
JSONObject json = BundleJSONConverter.convertToJSON(notification);
|
||||
preferences.edit().putString(notificationId, json.toString()).apply();
|
||||
preferences
|
||||
.edit()
|
||||
.putString(notificationId, json.toString())
|
||||
.apply();
|
||||
} catch (JSONException e) {
|
||||
if (promise == null) {
|
||||
Log.e(TAG, "Failed to store notification");
|
||||
} else {
|
||||
promise.reject("notification/schedule_notification_error", "Failed to store notification", e);
|
||||
promise.reject(
|
||||
"notification/schedule_notification_error",
|
||||
"Failed to store notification",
|
||||
e
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Intent notificationIntent = new Intent(context, RNFirebaseNotificationReceiver.class);
|
||||
notificationIntent.putExtras(notification);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(),
|
||||
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId.hashCode(),
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
if (schedule.containsKey("repeatInterval")) {
|
||||
// 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 +469,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";
|
||||
|
@ -38,6 +41,7 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
|||
private SharedPreferences sharedPreferences = null;
|
||||
|
||||
private RNFirebaseNotificationManager notificationManager;
|
||||
|
||||
public RNFirebaseNotifications(ReactApplicationContext context) {
|
||||
super(context);
|
||||
context.addActivityEventListener(this);
|
||||
|
@ -48,12 +52,16 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
|||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
|
||||
// Subscribe to remote notification events
|
||||
localBroadcastManager.registerReceiver(new RemoteNotificationReceiver(),
|
||||
new IntentFilter(RNFirebaseMessagingService.REMOTE_NOTIFICATION_EVENT));
|
||||
localBroadcastManager.registerReceiver(
|
||||
new RemoteNotificationReceiver(),
|
||||
new IntentFilter(RNFirebaseMessagingService.REMOTE_NOTIFICATION_EVENT)
|
||||
);
|
||||
|
||||
// Subscribe to scheduled notification events
|
||||
localBroadcastManager.registerReceiver(new ScheduledNotificationReceiver(),
|
||||
new IntentFilter(RNFirebaseNotificationManager.SCHEDULED_NOTIFICATION_EVENT));
|
||||
localBroadcastManager.registerReceiver(
|
||||
new ScheduledNotificationReceiver(),
|
||||
new IntentFilter(RNFirebaseNotificationManager.SCHEDULED_NOTIFICATION_EVENT)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,10 +120,18 @@ 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
|
||||
sharedPreferences.edit().putInt(BADGE_KEY, badge).apply();
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putInt(BADGE_KEY, badge)
|
||||
.apply();
|
||||
if (badge == 0) {
|
||||
Log.d(TAG, "Remove badge count");
|
||||
ShortcutBadger.removeCount(this.getReactApplicationContext());
|
||||
|
@ -185,7 +201,11 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
|||
public void onNewIntent(Intent intent) {
|
||||
WritableMap notificationOpenMap = parseIntentForNotification(intent);
|
||||
if (notificationOpenMap != null) {
|
||||
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_opened", notificationOpenMap);
|
||||
Utils.sendEvent(
|
||||
getReactApplicationContext(),
|
||||
"notifications_notification_opened",
|
||||
notificationOpenMap
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,9 +284,14 @@ 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()) {
|
||||
for (Map.Entry<String, String> e : message
|
||||
.getData()
|
||||
.entrySet()) {
|
||||
dataMap.putString(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
@ -277,8 +302,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 +322,45 @@ 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) {
|
||||
|
@ -311,7 +370,11 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
|||
RemoteMessage message = intent.getParcelableExtra("notification");
|
||||
WritableMap messageMap = parseRemoteMessage(message);
|
||||
|
||||
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_received", messageMap);
|
||||
Utils.sendEvent(
|
||||
getReactApplicationContext(),
|
||||
"notifications_notification_received",
|
||||
messageMap
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +388,11 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
|||
Bundle notification = intent.getBundleExtra("notification");
|
||||
WritableMap messageMap = parseNotificationBundle(notification);
|
||||
|
||||
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_received", messageMap);
|
||||
Utils.sendEvent(
|
||||
getReactApplicationContext(),
|
||||
"notifications_notification_received",
|
||||
messageMap
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,24 @@ package io.invertase.firebase.perf;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.firebase.perf.FirebasePerformance;
|
||||
import com.google.firebase.perf.metrics.Trace;
|
||||
import com.google.firebase.perf.metrics.HttpMetric;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RNFirebasePerformance extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebasePerformance";
|
||||
private HashMap<String, Trace> traces = new HashMap<>();
|
||||
private HashMap<String, HttpMetric> httpMetrics = new HashMap<>();
|
||||
|
||||
public RNFirebasePerformance(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
@ -30,19 +36,79 @@ public class RNFirebasePerformance extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setPerformanceCollectionEnabled(Boolean enabled) {
|
||||
public void setPerformanceCollectionEnabled(Boolean enabled, Promise promise) {
|
||||
FirebasePerformance.getInstance().setPerformanceCollectionEnabled(enabled);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace
|
||||
*/
|
||||
|
||||
@ReactMethod
|
||||
public void getTraceAttribute(String identifier, String attribute, Promise promise) {
|
||||
promise.resolve(getOrCreateTrace(identifier).getAttribute(attribute));
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void start(String identifier) {
|
||||
public void getTraceAttributes(String identifier, Promise promise) {
|
||||
Map<String, String> attributes = getOrCreateTrace(identifier).getAttributes();
|
||||
WritableMap map = Arguments.createMap();
|
||||
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
map.putString(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
promise.resolve(map);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getTraceLongMetric(String identifier, String metricName, Promise promise) {
|
||||
Integer value = Long.valueOf(getOrCreateTrace(identifier).getLongMetric(metricName)).intValue();
|
||||
promise.resolve(value);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void incrementTraceMetric(String identifier, String metricName, Integer incrementBy, Promise promise) {
|
||||
getOrCreateTrace(identifier).incrementMetric(metricName, incrementBy.longValue());
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void putTraceAttribute(String identifier, String attribute, String value, Promise promise) {
|
||||
getOrCreateTrace(identifier).putAttribute(attribute, value);
|
||||
// Docs say it returns a bool, actually void so we internally check attributes
|
||||
Map<String, String> attributes = getOrCreateTrace(identifier).getAttributes();
|
||||
if (attributes.containsKey(attribute)) {
|
||||
promise.resolve(true);
|
||||
} else {
|
||||
promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void putTraceMetric(String identifier, String metricName, Integer value, Promise promise) {
|
||||
getOrCreateTrace(identifier).putMetric(metricName, value.longValue());
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeTraceAttribute(String identifier, String attribute, Promise promise) {
|
||||
getOrCreateTrace(identifier).removeAttribute(attribute);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void startTrace(String identifier, Promise promise) {
|
||||
getOrCreateTrace(identifier).start();
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stop(String identifier) {
|
||||
public void stopTrace(String identifier, Promise promise) {
|
||||
getOrCreateTrace(identifier).stop();
|
||||
traces.remove(identifier);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -50,6 +116,86 @@ public class RNFirebasePerformance extends ReactContextBaseJavaModule {
|
|||
getOrCreateTrace(identifier).incrementCounter(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Http Metric
|
||||
*/
|
||||
|
||||
@ReactMethod
|
||||
public void getHttpMetricAttribute(String url, String httpMethod, String attribute, Promise promise) {
|
||||
promise.resolve(getOrCreateHttpMetric(url, httpMethod).getAttribute(attribute));
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getHttpMetricAttributes(String url, String httpMethod, Promise promise) {
|
||||
Map<String, String> attributes = getOrCreateHttpMetric(url, httpMethod).getAttributes();
|
||||
WritableMap map = Arguments.createMap();
|
||||
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
map.putString(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
promise.resolve(map);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void putHttpMetricAttribute(String url, String httpMethod, String attribute, String value, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).putAttribute(attribute, value);
|
||||
// Docs say it returns a bool, actually void so we internally check attributes
|
||||
Map<String, String> attributes = getOrCreateHttpMetric(url, httpMethod).getAttributes();
|
||||
if (attributes.containsKey(attribute)) {
|
||||
promise.resolve( true);
|
||||
} else {
|
||||
promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeHttpMetricAttribute(String url, String httpMethod, String attribute, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).removeAttribute(attribute);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setHttpMetricResponseCode(String url, String httpMethod, Integer code, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).setHttpResponseCode(code);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setHttpMetricRequestPayloadSize(String url, String httpMethod, Integer bytes, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).setRequestPayloadSize(bytes.longValue());
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setHttpMetricResponseContentType(String url, String httpMethod, String type, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).setResponseContentType(type);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setHttpMetricResponsePayloadSize(String url, String httpMethod, Integer bytes, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).setResponsePayloadSize(bytes.longValue());
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void startHttpMetric(String url, String httpMethod, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).start();
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stopHttpMetric(String url, String httpMethod, Promise promise) {
|
||||
getOrCreateHttpMetric(url, httpMethod).stop();
|
||||
httpMetrics.remove(url + httpMethod);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private
|
||||
*/
|
||||
|
||||
private Trace getOrCreateTrace(String identifier) {
|
||||
if (traces.containsKey(identifier)) {
|
||||
return traces.get(identifier);
|
||||
|
@ -58,4 +204,39 @@ public class RNFirebasePerformance extends ReactContextBaseJavaModule {
|
|||
traces.put(identifier, trace);
|
||||
return trace;
|
||||
}
|
||||
|
||||
private HttpMetric getOrCreateHttpMetric(String url, String httpMethod) {
|
||||
String identifier = url + httpMethod;
|
||||
if (httpMetrics.containsKey(identifier)) {
|
||||
return httpMetrics.get(identifier);
|
||||
}
|
||||
HttpMetric httpMetric = FirebasePerformance.getInstance().newHttpMetric(url, this.mapStringToMethod(httpMethod));
|
||||
httpMetrics.put(identifier, httpMetric);
|
||||
return httpMetric;
|
||||
}
|
||||
|
||||
private String mapStringToMethod(String value) {
|
||||
switch (value) {
|
||||
case "CONNECT":
|
||||
return FirebasePerformance.HttpMethod.CONNECT;
|
||||
case "DELETE":
|
||||
return FirebasePerformance.HttpMethod.DELETE;
|
||||
case "GET":
|
||||
return FirebasePerformance.HttpMethod.GET;
|
||||
case "HEAD":
|
||||
return FirebasePerformance.HttpMethod.HEAD;
|
||||
case "OPTIONS":
|
||||
return FirebasePerformance.HttpMethod.OPTIONS;
|
||||
case "PATCH":
|
||||
return FirebasePerformance.HttpMethod.PATCH;
|
||||
case "POST":
|
||||
return FirebasePerformance.HttpMethod.POST;
|
||||
case "PUT":
|
||||
return FirebasePerformance.HttpMethod.PUT;
|
||||
case "TRACE":
|
||||
return FirebasePerformance.HttpMethod.TRACE;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import com.google.firebase.storage.OnProgressListener;
|
|||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
||||
|
||||
|
@ -111,17 +110,20 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
@ReactMethod
|
||||
public void delete(String appName, final String path, final Promise promise) {
|
||||
StorageReference reference = this.getReference(path, appName);
|
||||
reference.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void aVoid) {
|
||||
promise.resolve(null);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
reference
|
||||
.delete()
|
||||
.addOnSuccessListener(new OnSuccessListener<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void aVoid) {
|
||||
promise.resolve(null);
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,17 +164,20 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
@ReactMethod
|
||||
public void getMetadata(String appName, final String path, final Promise promise) {
|
||||
StorageReference reference = this.getReference(path, appName);
|
||||
reference.getMetadata().addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
|
||||
@Override
|
||||
public void onSuccess(StorageMetadata storageMetadata) {
|
||||
promise.resolve(getMetadataAsMap(storageMetadata));
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
reference
|
||||
.getMetadata()
|
||||
.addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
|
||||
@Override
|
||||
public void onSuccess(StorageMetadata storageMetadata) {
|
||||
promise.resolve(getMetadataAsMap(storageMetadata));
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -184,22 +189,30 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata
|
||||
*/
|
||||
@ReactMethod
|
||||
public void updateMetadata(String appName, final String path, final ReadableMap metadata, final Promise promise) {
|
||||
public void updateMetadata(
|
||||
String appName,
|
||||
final String path,
|
||||
final ReadableMap metadata,
|
||||
final Promise promise
|
||||
) {
|
||||
StorageReference reference = this.getReference(path, appName);
|
||||
StorageMetadata md = buildMetadataFromMap(metadata, null);
|
||||
|
||||
reference.updateMetadata(md).addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
|
||||
@Override
|
||||
public void onSuccess(StorageMetadata storageMetadata) {
|
||||
WritableMap data = getMetadataAsMap(storageMetadata);
|
||||
promise.resolve(data);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
reference
|
||||
.updateMetadata(md)
|
||||
.addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
|
||||
@Override
|
||||
public void onSuccess(StorageMetadata storageMetadata) {
|
||||
WritableMap data = getMetadataAsMap(storageMetadata);
|
||||
promise.resolve(data);
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -212,7 +225,12 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#downloadFile
|
||||
*/
|
||||
@ReactMethod
|
||||
public void downloadFile(final String appName, final String path, final String localPath, final Promise promise) {
|
||||
public void downloadFile(
|
||||
final String appName,
|
||||
final String path,
|
||||
final String localPath,
|
||||
final Promise promise
|
||||
) {
|
||||
if (!isExternalStorageWritable()) {
|
||||
promise.reject(
|
||||
"storage/invalid-device-file-path",
|
||||
|
@ -225,59 +243,70 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
Log.d(TAG, "downloadFile path: " + path);
|
||||
StorageReference reference = this.getReference(path, appName);
|
||||
|
||||
reference.getStream(new StreamDownloadTask.StreamProcessor() {
|
||||
@Override
|
||||
public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException {
|
||||
int indexOfLastSlash = localPath.lastIndexOf("/");
|
||||
String pathMinusFileName = indexOfLastSlash > 0 ? localPath.substring(0, indexOfLastSlash) + "/" : "/";
|
||||
String filename = indexOfLastSlash > 0 ? localPath.substring(indexOfLastSlash + 1) : localPath;
|
||||
File fileWithJustPath = new File(pathMinusFileName);
|
||||
reference
|
||||
.getStream(new StreamDownloadTask.StreamProcessor() {
|
||||
@Override
|
||||
public void doInBackground(
|
||||
StreamDownloadTask.TaskSnapshot taskSnapshot,
|
||||
InputStream inputStream
|
||||
) throws IOException {
|
||||
int indexOfLastSlash = localPath.lastIndexOf("/");
|
||||
String pathMinusFileName = indexOfLastSlash > 0 ? localPath.substring(
|
||||
0,
|
||||
indexOfLastSlash
|
||||
) + "/" : "/";
|
||||
String filename = indexOfLastSlash > 0 ? localPath.substring(indexOfLastSlash + 1) : localPath;
|
||||
File fileWithJustPath = new File(pathMinusFileName);
|
||||
|
||||
// directoriesCreated assignment for not consumed warning
|
||||
Boolean directoriesCreated = fileWithJustPath.mkdirs();
|
||||
File fileWithFullPath = new File(pathMinusFileName, filename);
|
||||
FileOutputStream output = new FileOutputStream(fileWithFullPath);
|
||||
int bufferSize = 1024;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
// directoriesCreated assignment for not consumed warning
|
||||
Boolean directoriesCreated = fileWithJustPath.mkdirs();
|
||||
File fileWithFullPath = new File(pathMinusFileName, filename);
|
||||
FileOutputStream output = new FileOutputStream(fileWithFullPath);
|
||||
int bufferSize = 1024;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, len);
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, len);
|
||||
}
|
||||
|
||||
output.close();
|
||||
}
|
||||
|
||||
output.close();
|
||||
}
|
||||
}).addOnProgressListener(new OnProgressListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "downloadFile progress " + taskSnapshot);
|
||||
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
|
||||
sendJSEvent(appName, STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
}).addOnPausedListener(new OnPausedListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "downloadFile paused " + taskSnapshot);
|
||||
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
|
||||
sendJSEvent(appName, STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
}).addOnSuccessListener(new OnSuccessListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "downloadFile success" + taskSnapshot);
|
||||
WritableMap resp = getDownloadTaskAsMap(taskSnapshot);
|
||||
sendJSEvent(appName, STORAGE_DOWNLOAD_SUCCESS, path, resp);
|
||||
resp = getDownloadTaskAsMap(taskSnapshot);
|
||||
promise.resolve(resp);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
Log.e(TAG, "downloadFile failure " + exception.getMessage());
|
||||
// TODO sendJS error event
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
})
|
||||
.addOnProgressListener(new OnProgressListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "downloadFile progress " + taskSnapshot);
|
||||
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
|
||||
sendJSEvent(appName, STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
})
|
||||
.addOnPausedListener(new OnPausedListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "downloadFile paused " + taskSnapshot);
|
||||
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
|
||||
sendJSEvent(appName, STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
})
|
||||
.addOnSuccessListener(new OnSuccessListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "downloadFile success" + taskSnapshot);
|
||||
WritableMap resp = getDownloadTaskAsMap(taskSnapshot);
|
||||
sendJSEvent(appName, STORAGE_DOWNLOAD_SUCCESS, path, resp);
|
||||
resp = getDownloadTaskAsMap(taskSnapshot);
|
||||
promise.resolve(resp);
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
Log.e(TAG, "downloadFile failure " + exception.getMessage());
|
||||
// TODO sendJS error event
|
||||
promiseRejectStorageException(promise, exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,7 +361,13 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile
|
||||
*/
|
||||
@ReactMethod
|
||||
public void putFile(final String appName, final String path, final String localPath, final ReadableMap metadata, final Promise promise) {
|
||||
public void putFile(
|
||||
final String appName,
|
||||
final String path,
|
||||
final String localPath,
|
||||
final ReadableMap metadata,
|
||||
final Promise promise
|
||||
) {
|
||||
StorageReference reference = this.getReference(path, appName);
|
||||
|
||||
Log.i(TAG, "putFile: " + localPath + " to " + path);
|
||||
|
@ -436,7 +471,9 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
private Uri getURI(final String uri) {
|
||||
Uri parsed = Uri.parse(uri);
|
||||
|
||||
if (parsed.getScheme() == null || parsed.getScheme().isEmpty()) {
|
||||
if (parsed.getScheme() == null || parsed
|
||||
.getScheme()
|
||||
.isEmpty()) {
|
||||
return Uri.fromFile(new File(uri));
|
||||
}
|
||||
return parsed;
|
||||
|
@ -470,14 +507,18 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
} else if (file != null) {
|
||||
String mimeType = null;
|
||||
|
||||
if (file.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
|
||||
if (file
|
||||
.getScheme()
|
||||
.equals(ContentResolver.SCHEME_CONTENT)) {
|
||||
ContentResolver cr = getReactApplicationContext().getContentResolver();
|
||||
mimeType = cr.getType(file);
|
||||
} else {
|
||||
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(file
|
||||
.toString());
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
|
||||
fileExtension.toLowerCase());
|
||||
.toString());
|
||||
mimeType = MimeTypeMap
|
||||
.getSingleton()
|
||||
.getMimeTypeFromExtension(
|
||||
fileExtension.toLowerCase());
|
||||
}
|
||||
|
||||
if (mimeType != null) metadataBuilder.setContentType(mimeType);
|
||||
|
@ -498,7 +539,12 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
private WritableMap getDownloadTaskAsMap(final StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
|
||||
resp.putString("ref", taskSnapshot.getStorage().getPath());
|
||||
resp.putString(
|
||||
"ref",
|
||||
taskSnapshot
|
||||
.getStorage()
|
||||
.getPath()
|
||||
);
|
||||
resp.putString("state", this.getTaskStatus(taskSnapshot.getTask()));
|
||||
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
|
||||
|
||||
|
@ -512,29 +558,40 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
* @param taskSnapshot
|
||||
* @return
|
||||
*/
|
||||
private void getUploadTaskAsMap(final UploadTask.TaskSnapshot taskSnapshot, final OnSuccessListener<WritableMap> listener) {
|
||||
private void getUploadTaskAsMap(
|
||||
final UploadTask.TaskSnapshot taskSnapshot,
|
||||
final OnSuccessListener<WritableMap> listener
|
||||
) {
|
||||
if (taskSnapshot != null) {
|
||||
taskSnapshot.getStorage().getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
|
||||
@Override
|
||||
public void onSuccess(Uri downloadUrl) {
|
||||
WritableMap resp = Arguments.createMap();
|
||||
taskSnapshot
|
||||
.getStorage()
|
||||
.getDownloadUrl()
|
||||
.addOnSuccessListener(new OnSuccessListener<Uri>() {
|
||||
@Override
|
||||
public void onSuccess(Uri downloadUrl) {
|
||||
WritableMap resp = Arguments.createMap();
|
||||
|
||||
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
|
||||
resp.putString("downloadURL", downloadUrl.toString());
|
||||
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
|
||||
resp.putString("downloadURL", downloadUrl.toString());
|
||||
|
||||
StorageMetadata d = taskSnapshot.getMetadata();
|
||||
if (d != null) {
|
||||
WritableMap metadata = getMetadataAsMap(d);
|
||||
resp.putMap("metadata", metadata);
|
||||
StorageMetadata d = taskSnapshot.getMetadata();
|
||||
if (d != null) {
|
||||
WritableMap metadata = getMetadataAsMap(d);
|
||||
resp.putMap("metadata", metadata);
|
||||
}
|
||||
|
||||
resp.putString(
|
||||
"ref",
|
||||
taskSnapshot
|
||||
.getStorage()
|
||||
.getPath()
|
||||
);
|
||||
resp.putString("state", RNFirebaseStorage.this.getTaskStatus(taskSnapshot.getTask()));
|
||||
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
|
||||
|
||||
listener.onSuccess(resp);
|
||||
}
|
||||
|
||||
resp.putString("ref", taskSnapshot.getStorage().getPath());
|
||||
resp.putString("state", RNFirebaseStorage.this.getTaskStatus(taskSnapshot.getTask()));
|
||||
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
|
||||
|
||||
listener.onSuccess(resp);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
listener.onSuccess(Arguments.createMap());
|
||||
}
|
||||
|
@ -683,10 +740,33 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath());
|
||||
constants.put(TemporaryDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath());
|
||||
constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
|
||||
constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath());
|
||||
constants.put(
|
||||
DocumentDirectoryPath,
|
||||
this
|
||||
.getReactApplicationContext()
|
||||
.getFilesDir()
|
||||
.getAbsolutePath()
|
||||
);
|
||||
constants.put(
|
||||
TemporaryDirectoryPath,
|
||||
this
|
||||
.getReactApplicationContext()
|
||||
.getCacheDir()
|
||||
.getAbsolutePath()
|
||||
);
|
||||
constants.put(
|
||||
PicturesDirectoryPath,
|
||||
Environment
|
||||
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
.getAbsolutePath()
|
||||
);
|
||||
constants.put(
|
||||
CachesDirectoryPath,
|
||||
this
|
||||
.getReactApplicationContext()
|
||||
.getCacheDir()
|
||||
.getAbsolutePath()
|
||||
);
|
||||
constants.put(FileTypeRegular, 0);
|
||||
constants.put(FileTypeDirectory, 1);
|
||||
|
||||
|
@ -697,7 +777,9 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
|||
constants.put(ExternalStorageDirectoryPath, null);
|
||||
}
|
||||
|
||||
File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null);
|
||||
File externalDirectory = this
|
||||
.getReactApplicationContext()
|
||||
.getExternalFilesDir(null);
|
||||
if (externalDirectory != null) {
|
||||
constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath());
|
||||
} else {
|
||||
|
|
52
bin/test.js
|
@ -1,52 +0,0 @@
|
|||
const shell = require('shelljs');
|
||||
|
||||
const WATCH_SRC = require('path').resolve('./lib');
|
||||
const WATCH_OUT = require('path').resolve('./tests/firebase');
|
||||
|
||||
/*
|
||||
"tests-watch-init": "wml add $(node --eval \"console.log(require('path').resolve('./lib'));\") $(node --eval \"console.log(require('path').resolve('./tests/firebase'));\")",
|
||||
"tests-watch-start": "watchman watch $(node --eval \"console.log(require('path').resolve('./lib'));\") && wml start",
|
||||
"tests-watch-stop": "watchman watch-del $(node --eval \"console.log(require('path').resolve('./lib'));\") && wml stop"
|
||||
*/
|
||||
|
||||
if (process.argv.includes('watch')) {
|
||||
if (!shell.which('wml')) {
|
||||
shell.echo('');
|
||||
shell.echo('---------------------------------------------------');
|
||||
shell.echo(' Missing required npm global from library wix/wml. ');
|
||||
shell.echo('---------------------------------------------------');
|
||||
shell.echo('');
|
||||
shell.exit(1);
|
||||
}
|
||||
|
||||
if (!shell.which('watchman')) {
|
||||
shell.echo('');
|
||||
shell.echo('---------------------------------------------------');
|
||||
shell.echo(' Missing required executable: watchman ');
|
||||
shell.echo('---------------------------------------------------');
|
||||
shell.echo('');
|
||||
shell.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv.includes('init')) {
|
||||
console.log(`wml add ${WATCH_SRC} ${WATCH_OUT}`);
|
||||
if (shell.exec(`wml add ${WATCH_SRC} ${WATCH_OUT}`).code !== 0) {
|
||||
shell.echo('Error setting up watched location via WML.');
|
||||
shell.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv.includes('start')) {
|
||||
console.log(`watchman watch ${WATCH_SRC} && wml start`);
|
||||
const watcher = shell.exec(`watchman watch ${WATCH_SRC} && wml start`, { async: true });
|
||||
watcher.stdout.on('data', console.log);
|
||||
watcher.stderr.on('data', console.error);
|
||||
}
|
||||
|
||||
if (process.argv.includes('stop')) {
|
||||
console.log(`watchman watch-del ${WATCH_SRC} && wml stop && wml rm all`);
|
||||
const watcher = shell.exec(`watchman watch-del ${WATCH_SRC} && wml stop && wml rm all`, { async: true });
|
||||
watcher.stdout.on('data', console.log);
|
||||
watcher.stderr.on('data', console.error);
|
||||
}
|
||||
}
|
|
@ -99,7 +99,6 @@ dependencies {
|
|||
implementation "com.google.firebase:firebase-ads:15.0.1"
|
||||
implementation "com.google.firebase:firebase-auth:16.0.2"
|
||||
implementation "com.google.firebase:firebase-config:16.0.0"
|
||||
implementation "com.google.firebase:firebase-crash:16.0.1"
|
||||
implementation "com.google.firebase:firebase-database:16.0.1"
|
||||
implementation "com.google.firebase:firebase-firestore:17.0.2"
|
||||
implementation "com.google.firebase:firebase-functions:16.0.1"
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.testing;
|
|||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import io.invertase.bridge.RNBridgePackage;
|
||||
import io.salakar.bridge.RNBridgePackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
|
@ -14,7 +14,6 @@ import io.invertase.firebase.admob.RNFirebaseAdMobPackage;
|
|||
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
|
||||
import io.invertase.firebase.auth.RNFirebaseAuthPackage;
|
||||
import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage;
|
||||
import io.invertase.firebase.crash.RNFirebaseCrashPackage;
|
||||
import io.invertase.firebase.database.RNFirebaseDatabasePackage;
|
||||
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
|
||||
import io.invertase.firebase.firestore.RNFirebaseFirestorePackage;
|
||||
|
@ -48,7 +47,6 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
new RNFirebaseAnalyticsPackage(),
|
||||
new RNFirebaseAuthPackage(),
|
||||
new RNFirebaseRemoteConfigPackage(),
|
||||
new RNFirebaseCrashPackage(),
|
||||
new RNFirebaseCrashlyticsPackage(),
|
||||
new RNFirebaseDatabasePackage(),
|
||||
new RNFirebaseFirestorePackage(),
|
||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.3'
|
||||
classpath 'com.google.gms:google-services:4.0.1'
|
||||
classpath 'com.google.firebase:firebase-plugins:1.1.1'
|
||||
classpath 'com.google.firebase:firebase-plugins:1.1.5'
|
||||
classpath 'io.fabric.tools:gradle:1.25.4'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('bridge', () => {
|
|||
// we shim our own reloadReactNative functionality as the detox reloadReactNative built-in
|
||||
// hangs often and seems unpredictable - todo: investigate & PR if solution found
|
||||
// reloadReactNative is replaced on init with bridge.root automatically
|
||||
it('should allow reloadReactNative usage without breaking remote debug', async () => {
|
||||
xit('should allow reloadReactNative usage without breaking remote debug', async () => {
|
||||
should(bridge.reload).be.a.Function();
|
||||
// and check it works without breaking anything
|
||||
await device.reloadReactNative();
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
describe('crash()', () => {
|
||||
describe('setCrashCollectionEnabled()', () => {
|
||||
it('true', async () => {
|
||||
await firebase.crash().setCrashCollectionEnabled(true);
|
||||
await sleep(100);
|
||||
const enabled = await firebase.crash().isCrashCollectionEnabled();
|
||||
should.equal(enabled, true, 'collection enabled boolean should be true');
|
||||
});
|
||||
|
||||
it('false', async () => {
|
||||
await firebase.crash().setCrashCollectionEnabled(false);
|
||||
|
||||
// does not exist on ios
|
||||
if (device.getPlatform() === 'android') {
|
||||
await sleep(150);
|
||||
const enabled = await firebase.crash().isCrashCollectionEnabled();
|
||||
should.equal(
|
||||
enabled,
|
||||
false,
|
||||
'collection enabled boolean should be false'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('log()', () => {
|
||||
it('accepts a string log', async () => {
|
||||
await firebase.crash().log('hello world');
|
||||
await sleep(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logcat()', () => {
|
||||
it('accepts a log level, log tag and message', async () => {
|
||||
await firebase.crash().logcat(0, 'HELLO_TAG', 'hello world');
|
||||
await sleep(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('report()', () => {
|
||||
it('accepts an error with customisable stack size', async () => {
|
||||
const error = new Error('Oh noes!');
|
||||
await firebase.crash().report(error);
|
||||
await firebase.crash().report(error, 5);
|
||||
error.code = 'NETWORK_ERROR';
|
||||
await firebase.crash().report(error);
|
||||
delete error.message;
|
||||
await firebase.crash().report(error);
|
||||
await sleep(50);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,93 @@
|
|||
describe('perf()', () => {
|
||||
describe('HttpMetric', () => {
|
||||
it('start() & stop()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('removeAttribute()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.putAttribute('foo', 'bar');
|
||||
const value = await httpMetric.getAttribute('foo');
|
||||
should.equal(value, 'bar');
|
||||
await httpMetric.removeAttribute('foo');
|
||||
const value2 = await httpMetric.getAttribute('foo');
|
||||
should.equal(value2, null);
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('getAttribute() should return null', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
const value = await httpMetric.getAttribute('foo');
|
||||
should.equal(value, null);
|
||||
await httpMetric.removeAttribute('foo');
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('getAttribute() should return string value', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.putAttribute('foo', 'bar');
|
||||
const value = await httpMetric.getAttribute('foo');
|
||||
should.equal(value, 'bar');
|
||||
await httpMetric.removeAttribute('foo');
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('putAttribute()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.putAttribute('foo', 'bar');
|
||||
const value = await httpMetric.getAttribute('foo');
|
||||
value.should.equal('bar');
|
||||
await httpMetric.removeAttribute('foo');
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('getAttributes()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.putAttribute('foo', 'bar');
|
||||
await httpMetric.putAttribute('bar', 'baz');
|
||||
const value = await httpMetric.getAttributes();
|
||||
value.should.deepEqual({
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
});
|
||||
await httpMetric.removeAttribute('foo');
|
||||
await httpMetric.removeAttribute('bar');
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('setHttpResponseCode()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.setHttpResponseCode(500);
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('setRequestPayloadSize()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.setRequestPayloadSize(1234567);
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('setResponseContentType()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.setResponseContentType('application/foobar');
|
||||
await httpMetric.stop();
|
||||
});
|
||||
|
||||
it('setResponsePayloadSize()', async () => {
|
||||
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
await httpMetric.start();
|
||||
await httpMetric.setResponsePayloadSize(123456789);
|
||||
await httpMetric.stop();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,9 +8,10 @@ describe('perf()', () => {
|
|||
await firebase.perf().setPerformanceCollectionEnabled(false);
|
||||
});
|
||||
|
||||
xit('errors if not boolean', async () => {
|
||||
// TODO add validations to lib
|
||||
await firebase.perf().setPerformanceCollectionEnabled();
|
||||
it('errors if not boolean', async () => {
|
||||
(() => firebase.perf().setPerformanceCollectionEnabled()).should.throw(
|
||||
'firebase.perf().setPerformanceCollectionEnabled() requires a boolean value'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -20,15 +21,27 @@ describe('perf()', () => {
|
|||
trace.constructor.name.should.be.equal('Trace');
|
||||
});
|
||||
|
||||
xit('errors if identifier not a string', async () => {
|
||||
// TODO add validations to lib
|
||||
try {
|
||||
firebase.perf().newTrace([1, 2, 3, 4]);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
it('errors if identifier not a string', async () => {
|
||||
(() => firebase.perf().newTrace([1, 2, 3, 4])).should.throw(
|
||||
'firebase.perf().newTrace() requires a string value'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
throw new Error('Trace did not error on invalid identifier');
|
||||
describe('newHttpMetric()', () => {
|
||||
it('returns an instance of HttpMetric', async () => {
|
||||
const trace = firebase.perf().newHttpMetric('http://foo.com', 'GET');
|
||||
trace.constructor.name.should.be.equal('HttpMetric');
|
||||
});
|
||||
|
||||
it('errors if url/httpMethod not a string', async () => {
|
||||
(() => firebase.perf().newHttpMetric(123, [1, 2])).should.throw(
|
||||
'firebase.perf().newHttpMetric() requires url and httpMethod string values'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors if httpMethod not a valid type', async () => {
|
||||
(() => firebase.perf().newHttpMetric('foo', 'FOO')).should.throw(); // TODO error
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,23 +6,83 @@ describe('perf()', () => {
|
|||
await trace.stop();
|
||||
});
|
||||
|
||||
describe('incrementCounter()', () => {
|
||||
it('accepts a string event', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.incrementCounter('fooby');
|
||||
await trace.incrementCounter('fooby');
|
||||
await trace.incrementCounter('fooby');
|
||||
await trace.incrementCounter('fooby');
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
xit('errors if event is not a string', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.incrementCounter([1, 2, 3, 4]);
|
||||
await trace.stop();
|
||||
});
|
||||
it('getAttribute() should return null', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
const value = await trace.getAttribute('foo');
|
||||
should.equal(value, null);
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('getAttribute() should return string value', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.putAttribute('foo', 'bar');
|
||||
const value = await trace.getAttribute('foo');
|
||||
should.equal(value, 'bar');
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('putAttribute()', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.putAttribute('foo', 'bar');
|
||||
const value = await trace.getAttribute('foo');
|
||||
value.should.equal('bar');
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('getAttributes()', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.putAttribute('foo', 'bar');
|
||||
await trace.putAttribute('bar', 'baz');
|
||||
const value = await trace.getAttributes();
|
||||
value.should.deepEqual({
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
});
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('removeAttribute()', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.putAttribute('foobar', 'bar');
|
||||
const value = await trace.getAttribute('foobar');
|
||||
value.should.equal('bar');
|
||||
await trace.removeAttribute('foobar');
|
||||
const removed = await trace.getAttribute('foobar');
|
||||
should.equal(removed, null);
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('getMetric()', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
const metric = await trace.getMetric('foo');
|
||||
metric.should.equal(0);
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('putMetric()', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.putMetric('baz', 1);
|
||||
const metric = await trace.getMetric('baz');
|
||||
metric.should.equal(1);
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
it('incrementMetric()', async () => {
|
||||
const trace = firebase.perf().newTrace('bar');
|
||||
await trace.start();
|
||||
await trace.putMetric('baz', 1);
|
||||
await trace.incrementMetric('baz', 2);
|
||||
const metric = await trace.getMetric('baz');
|
||||
metric.should.equal(3);
|
||||
await trace.stop();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,7 +21,6 @@ target 'testing' do
|
|||
pod 'Firebase/AdMob', '~> 5.3.0'
|
||||
pod 'Firebase/Auth', '~> 5.3.0'
|
||||
pod 'Firebase/Core', '~> 5.3.0'
|
||||
pod 'Firebase/Crash', '~> 5.3.0'
|
||||
pod 'Firebase/Database', '~> 5.3.0'
|
||||
pod 'Firebase/Functions', '~> 5.3.0'
|
||||
pod 'Firebase/DynamicLinks', '~> 5.3.0'
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
PODS:
|
||||
- BoringSSL (10.0.5):
|
||||
- BoringSSL/Implementation (= 10.0.5)
|
||||
- BoringSSL/Interface (= 10.0.5)
|
||||
- BoringSSL/Implementation (10.0.5):
|
||||
- BoringSSL/Interface (= 10.0.5)
|
||||
- BoringSSL/Interface (10.0.5)
|
||||
- Crashlytics (3.10.4):
|
||||
- BoringSSL (10.0.6):
|
||||
- BoringSSL/Implementation (= 10.0.6)
|
||||
- BoringSSL/Interface (= 10.0.6)
|
||||
- BoringSSL/Implementation (10.0.6):
|
||||
- BoringSSL/Interface (= 10.0.6)
|
||||
- BoringSSL/Interface (10.0.6)
|
||||
- Crashlytics (3.10.5):
|
||||
- Fabric (~> 1.7.9)
|
||||
- Fabric (1.7.9)
|
||||
- Firebase/AdMob (5.3.0):
|
||||
|
@ -19,9 +19,6 @@ PODS:
|
|||
- FirebaseAnalytics (= 5.0.1)
|
||||
- Firebase/CoreOnly (5.3.0):
|
||||
- FirebaseCore (= 5.0.4)
|
||||
- Firebase/Crash (5.3.0):
|
||||
- Firebase/Core
|
||||
- FirebaseCrash (= 3.0.0)
|
||||
- Firebase/Database (5.3.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseDatabase (= 5.0.1)
|
||||
|
@ -62,12 +59,6 @@ PODS:
|
|||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- FirebaseCore (5.0.4):
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- FirebaseCrash (3.0.0):
|
||||
- FirebaseAnalytics (~> 5.0)
|
||||
- FirebaseInstanceID (~> 3.0)
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- Protobuf (~> 3.5)
|
||||
- FirebaseDatabase (5.0.1):
|
||||
- FirebaseCore (~> 5.0)
|
||||
- leveldb-library (~> 1.18)
|
||||
|
@ -131,10 +122,10 @@ PODS:
|
|||
- FirebaseSwizzlingUtilities/MethodSwizzling (2.0.0):
|
||||
- FirebaseCore (~> 5.0)
|
||||
- Google-Mobile-Ads-SDK (7.31.0)
|
||||
- GoogleAPIClientForREST (1.3.4):
|
||||
- GoogleAPIClientForREST/Core (= 1.3.4)
|
||||
- GoogleAPIClientForREST (1.3.5):
|
||||
- GoogleAPIClientForREST/Core (= 1.3.5)
|
||||
- GTMSessionFetcher (>= 1.1.7)
|
||||
- GoogleAPIClientForREST/Core (1.3.4):
|
||||
- GoogleAPIClientForREST/Core (1.3.5):
|
||||
- GTMSessionFetcher (>= 1.1.7)
|
||||
- GoogleSignIn (4.1.2):
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
|
@ -162,34 +153,34 @@ PODS:
|
|||
- GoogleToolboxForMac/Defines (= 2.1.4)
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (= 2.1.4)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (= 2.1.4)"
|
||||
- gRPC (1.13.0):
|
||||
- gRPC-RxLibrary (= 1.13.0)
|
||||
- gRPC/Main (= 1.13.0)
|
||||
- gRPC-Core (1.13.0):
|
||||
- gRPC-Core/Implementation (= 1.13.0)
|
||||
- gRPC-Core/Interface (= 1.13.0)
|
||||
- gRPC-Core/Implementation (1.13.0):
|
||||
- gRPC (1.14.0):
|
||||
- gRPC-RxLibrary (= 1.14.0)
|
||||
- gRPC/Main (= 1.14.0)
|
||||
- gRPC-Core (1.14.0):
|
||||
- gRPC-Core/Implementation (= 1.14.0)
|
||||
- gRPC-Core/Interface (= 1.14.0)
|
||||
- gRPC-Core/Implementation (1.14.0):
|
||||
- BoringSSL (~> 10.0)
|
||||
- gRPC-Core/Interface (= 1.13.0)
|
||||
- gRPC-Core/Interface (= 1.14.0)
|
||||
- nanopb (~> 0.3)
|
||||
- gRPC-Core/Interface (1.13.0)
|
||||
- gRPC-ProtoRPC (1.13.0):
|
||||
- gRPC-ProtoRPC/Main (= 1.13.0)
|
||||
- gRPC-ProtoRPC/Main (1.13.0):
|
||||
- gRPC (= 1.13.0)
|
||||
- gRPC-RxLibrary (= 1.13.0)
|
||||
- gRPC-Core/Interface (1.14.0)
|
||||
- gRPC-ProtoRPC (1.14.0):
|
||||
- gRPC-ProtoRPC/Main (= 1.14.0)
|
||||
- gRPC-ProtoRPC/Main (1.14.0):
|
||||
- gRPC (= 1.14.0)
|
||||
- gRPC-RxLibrary (= 1.14.0)
|
||||
- Protobuf (~> 3.0)
|
||||
- gRPC-RxLibrary (1.13.0)
|
||||
- gRPC/Main (1.13.0):
|
||||
- gRPC-Core (= 1.13.0)
|
||||
- gRPC-RxLibrary (= 1.13.0)
|
||||
- gRPC-RxLibrary (1.14.0)
|
||||
- gRPC/Main (1.14.0):
|
||||
- gRPC-Core (= 1.14.0)
|
||||
- gRPC-RxLibrary (= 1.14.0)
|
||||
- GTMOAuth2 (1.1.6):
|
||||
- GTMSessionFetcher (~> 1.1)
|
||||
- GTMSessionFetcher (1.1.15):
|
||||
- GTMSessionFetcher/Full (= 1.1.15)
|
||||
- GTMSessionFetcher/Core (1.1.15)
|
||||
- GTMSessionFetcher/Full (1.1.15):
|
||||
- GTMSessionFetcher/Core (= 1.1.15)
|
||||
- GTMSessionFetcher (1.2.0):
|
||||
- GTMSessionFetcher/Full (= 1.2.0)
|
||||
- GTMSessionFetcher/Core (1.2.0)
|
||||
- GTMSessionFetcher/Full (1.2.0):
|
||||
- GTMSessionFetcher/Core (= 1.2.0)
|
||||
- leveldb-library (1.20)
|
||||
- nanopb (0.3.8):
|
||||
- nanopb/decode (= 0.3.8)
|
||||
|
@ -197,25 +188,25 @@ PODS:
|
|||
- nanopb/decode (0.3.8)
|
||||
- nanopb/encode (0.3.8)
|
||||
- Protobuf (3.6.0)
|
||||
- React (0.55.3):
|
||||
- React/Core (= 0.55.3)
|
||||
- React/Core (0.55.3):
|
||||
- yoga (= 0.55.3.React)
|
||||
- React/fishhook (0.55.3)
|
||||
- React/RCTBlob (0.55.3):
|
||||
- React (0.56.0):
|
||||
- React/Core (= 0.56.0)
|
||||
- React/Core (0.56.0):
|
||||
- yoga (= 0.56.0.React)
|
||||
- React/fishhook (0.56.0)
|
||||
- React/RCTBlob (0.56.0):
|
||||
- React/Core
|
||||
- React/RCTNetwork (0.55.3):
|
||||
- React/RCTNetwork (0.56.0):
|
||||
- React/Core
|
||||
- React/RCTText (0.55.3):
|
||||
- React/RCTText (0.56.0):
|
||||
- React/Core
|
||||
- React/RCTWebSocket (0.55.3):
|
||||
- React/RCTWebSocket (0.56.0):
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNFirebase (4.2.0):
|
||||
- RNFirebase (5.0.0):
|
||||
- Firebase/Core
|
||||
- React
|
||||
- yoga (0.55.3.React)
|
||||
- yoga (0.56.0.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Crashlytics (~> 3.10.4)
|
||||
|
@ -223,7 +214,6 @@ DEPENDENCIES:
|
|||
- Firebase/AdMob (~> 5.3.0)
|
||||
- Firebase/Auth (~> 5.3.0)
|
||||
- Firebase/Core (~> 5.3.0)
|
||||
- Firebase/Crash (~> 5.3.0)
|
||||
- Firebase/Database (~> 5.3.0)
|
||||
- Firebase/DynamicLinks (~> 5.3.0)
|
||||
- Firebase/Firestore (~> 5.3.0)
|
||||
|
@ -250,7 +240,6 @@ SPEC REPOS:
|
|||
- FirebaseAnalytics
|
||||
- FirebaseAuth
|
||||
- FirebaseCore
|
||||
- FirebaseCrash
|
||||
- FirebaseDatabase
|
||||
- FirebaseDynamicLinks
|
||||
- FirebaseFirestore
|
||||
|
@ -285,15 +274,14 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
BoringSSL: cf3f1793eb6e3c445c4d150456341f149c268a35
|
||||
Crashlytics: 915a7787b84f635fb2a81f92a90e265c2c413f76
|
||||
BoringSSL: e10f92a27043805c01071fe815a5cd98ae8212e7
|
||||
Crashlytics: 7f2e38d302d9da96475b3d64d86fb29e31a542b7
|
||||
Fabric: a2917d3895e4c1569b9c3170de7320ea1b1e6661
|
||||
Firebase: 68afeeb05461db02d7c9e3215cda28068670f4aa
|
||||
FirebaseABTesting: 1f50b8d50f5e3469eea54e7463a7b7fe221d1f5e
|
||||
FirebaseAnalytics: b3628aea54c50464c32c393fb2ea032566e7ecc2
|
||||
FirebaseAuth: 463b8ce33bd5d05f706dcd4615499e3212b4132b
|
||||
FirebaseCore: 62f1b792a49bb9e8b4073f24606d2c93ffc352f0
|
||||
FirebaseCrash: 8900571fd763fd5bdda04522ec53da979456e3ce
|
||||
FirebaseDatabase: 482bad9c2abd422bb2321194fb8c937e67426a89
|
||||
FirebaseDynamicLinks: d624a7adc81a8fd70d52be5a6a47a2bc0644b923
|
||||
FirebaseFirestore: 53f6fe858494c39dbfd5237655e0641152a88c89
|
||||
|
@ -306,22 +294,22 @@ SPEC CHECKSUMS:
|
|||
FirebaseStorage: 7ca4bb7b58a25fa647b04f524033fc7cb7eb272b
|
||||
FirebaseSwizzlingUtilities: 6c22677c50d0b6f5f0dc637c1233f13694a3003f
|
||||
Google-Mobile-Ads-SDK: 6e529e748b45507a2ca904e0b5a52669ba3920c4
|
||||
GoogleAPIClientForREST: f7951c455df271bc6259b3ddb4073d0026475ccf
|
||||
GoogleAPIClientForREST: b4c23f6ec9b8c29419d0377168007963d0e25551
|
||||
GoogleSignIn: d9ef55b10f0aa401a5de2747f59b725e4b9732ac
|
||||
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
|
||||
gRPC: 81b990caa01d7c32605d300c2ffd1b5ba0ef9e49
|
||||
gRPC-Core: a56b086a8ef5ced6f6ba5be5bc0a63fca185015a
|
||||
gRPC-ProtoRPC: 842797fbe05dfcf891afb4a5048e5ccf5f06ef86
|
||||
gRPC-RxLibrary: fa6852f98d6ec0b73c0ec2f6406c26943099d501
|
||||
gRPC: 9d4c549f37c7cef22478634bac7a0405b6e3dbfb
|
||||
gRPC-Core: f4836515817c0eb479aeeb9cc27c91c4ba62a9f6
|
||||
gRPC-ProtoRPC: 76f6053639a842e1fb45fa82aa598bf3f4d9f66f
|
||||
gRPC-RxLibrary: 3c305f32155024b116b780f389323c73a28197e6
|
||||
GTMOAuth2: c77fe325e4acd453837e72d91e3b5f13116857b2
|
||||
GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2
|
||||
GTMSessionFetcher: 0c4baf0a73acd0041bf9f71ea018deedab5ea84e
|
||||
leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5
|
||||
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
|
||||
Protobuf: 0fc0ad8bec688b2a3017a139953e01374fedbd5f
|
||||
React: aa2040dbb6f317b95314968021bd2888816e03d5
|
||||
React: 1fe0eb13d90b625d94c3b117c274dcfd2e760e11
|
||||
RNFirebase: 2b25fd2e60269f26bb0a76c71dcc942b35a77df0
|
||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||
yoga: b1ce48b6cf950b98deae82838f5173ea7cf89e85
|
||||
|
||||
PODFILE CHECKSUM: af3286375d5c28aa5d912b64dee23c8f7c4f9282
|
||||
PODFILE CHECKSUM: dd2c5548ed2d8f457d4009b6313da04af8f98f40
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
|
|
|
@ -20,36 +20,35 @@
|
|||
"ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .."
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-preset-es2015-mod": "^6.6.0",
|
||||
"babel-preset-es3": "^1.0.1",
|
||||
"bridge": "^0.2.7",
|
||||
"detox": "^7.3.3",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"detox": "^8.1.1",
|
||||
"fbjs": "^0.8.16",
|
||||
"firebase-admin": "^5.12.0",
|
||||
"jsonwebtoken": "^8.2.1",
|
||||
"mocha": "^5.1.1",
|
||||
"mocha": "^5.2.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.3.2",
|
||||
"react-native": "^0.55.3",
|
||||
"react": "^16.4.1",
|
||||
"react-native": "^0.56.0",
|
||||
"should": "^13.2.1",
|
||||
"should-sinon": "0.0.6",
|
||||
"sinon": "^4.5.0"
|
||||
"sinon": "^6.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.0",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"@babel/cli": "7.0.0-beta.47",
|
||||
"@babel/core": "7.0.0-beta.47",
|
||||
"babel-eslint": "^8.2.6",
|
||||
"babel-plugin-istanbul": "^5.0.1",
|
||||
"babel-plugin-module-resolver": "^3.1.1",
|
||||
"babel-preset-react-native": "^4.0.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb": "^16.1.0",
|
||||
"babel-preset-react-native": "^5.0.2",
|
||||
"eslint": "^5.3.0",
|
||||
"eslint-config-airbnb": "^17.0.0",
|
||||
"eslint-plugin-flowtype": "^2.46.3",
|
||||
"eslint-plugin-import": "^2.11.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"flow-bin": "^0.70.0",
|
||||
"nyc": "^11.7.1"
|
||||
"flow-bin": "^0.78.0",
|
||||
"nyc": "^12.0.2",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"nyc": {
|
||||
"check-coverage": false,
|
||||
|
@ -57,12 +56,22 @@
|
|||
"statements": 95,
|
||||
"functions": 95,
|
||||
"branches": 95,
|
||||
"include": ["**/dist/**"],
|
||||
"exclude": ["node_modules", "**/dist/utils/emitter**", "**/dist/modules/admob**", "**/dist/modules/auth/phone**"],
|
||||
"include": [
|
||||
"**/dist/**"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/dist/utils/emitter**",
|
||||
"**/dist/modules/admob**",
|
||||
"**/dist/modules/auth/phone**"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"instrument": true,
|
||||
"cwd": "..",
|
||||
"reporter": ["lcov", "text-summary"]
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
]
|
||||
},
|
||||
"detox": {
|
||||
"test-runner": "mocha",
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
# App Navigation
|
||||
|
||||
React Navigation has gone through many cycles of navigation implementations and has been a pain point for developers for a good while.
|
||||
A current "go to" navigation library is called [react-navigation](https://reactnavigation.org/). It's pure JavaScript implementation
|
||||
which performs well and provides a solid foundation for navigation on both Android and iOS.
|
||||
|
||||
In this step we'll be focusing on adding a Login & Register screen to our app.
|
||||
|
||||
## Installation
|
||||
|
||||
Simply install the dependency via NPM, no native installation is needed:
|
||||
|
||||
```bash
|
||||
npm install --save react-navigation
|
||||
```
|
||||
|
||||
## Navigation Stacks
|
||||
|
||||
Navigation on an app typically works in stacks, where a user can navigate to a new screen (pushing a new screen onto the stack), or backwards (popping
|
||||
a screen off the stack).
|
||||
|
||||
What's great about this concept is that we can create multiple instances of a stack, for example a stack for unauthenticated users and another for
|
||||
authenticated ones.
|
||||
|
||||
To create a new stack, we import the `StackNavigator` from `react-navigation`. In it's basic form, the first item of the `StackNavigator` object
|
||||
acts as our initial screen on the stack. Lets create a new directory and component for our unauthenticated state:
|
||||
|
||||
```js
|
||||
// src/screens/unauthenticated/index.js
|
||||
|
||||
import { StackNavigator } from 'react-navigation';
|
||||
|
||||
import Login from './Login';
|
||||
import Register from './Register';
|
||||
|
||||
export default StackNavigator({
|
||||
Login: {
|
||||
screen: Login,
|
||||
},
|
||||
Register: {
|
||||
screen: Register,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
In both the `Login` & `Register` files, create a basic React component (change Login to Register where appropriate):
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
// src/screens/unauthenticated/Register.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Login</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using the stack
|
||||
|
||||
StackNavigator returns a React component which can be rendered in our app. If we go back to our `src/App.js` component, we can now return
|
||||
the stack:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import UnauthenticatedStack from './screens/unauthenticated';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <UnauthenticatedStack />;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
Our `UnauthenticatedStack` component will now show the `Login` component as it's the first item in the `StackNavigator`. Reload your app and you
|
||||
should have your `Login` component rendering!
|
||||
|
||||
![Basic Navigation](assets/1-unauthenticated-nav.jpg =300x\*)
|
||||
|
||||
## Styling the navigator
|
||||
|
||||
As you can see, `react-navigation` provides basic styling to mimic the feel of Android's [Material Design](https://material.io). The
|
||||
library provides a simple, React like API to style and control your app.
|
||||
|
||||
> If you're using iOS, the functionality will remain the same however the basic styling will represent that of the iOS interface instead!
|
||||
|
||||
For this example we're going to add a title to our screen and liven up the colors - there's loads more you can do with `react-navigation` though,
|
||||
just check out their in-depth [documentation](https://reactnavigation.org/docs/getting-started.html).
|
||||
|
||||
Lets go ahead and style the screen, using a class static `navigationOptions` object which lets `react-navigation` access our screen component styling:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
// Add our react-navigation static method:
|
||||
static navigationOptions = {
|
||||
title: 'Login',
|
||||
headerStyle: {
|
||||
backgroundColor: '#E6853E',
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Login</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
||||
```
|
||||
|
||||
With this basic config you'll end up with an Android looking app with minimal configuration. Whats better is that `react-navigation` will also
|
||||
take care of any back buttons and screen animations when navigating through the stack, pretty nifty.
|
||||
|
||||
![Styled Navigation](assets/2-unauthenticated-nav.jpg =300x\*)
|
||||
|
||||
## Pushing to a new stack
|
||||
|
||||
Pushing a new screen onto the stack is a common practice on mobile apps, however requires a slightly different mindset if you're from a web development
|
||||
background. The basics of a stack allow you to `push` and `pop` where screens effectively overlay each other. The user cannot change stack item
|
||||
unless you give them the ability to (compared to a website where the user could manually enter a different URL). This allows for greater
|
||||
control over what a user is able to push/pop to.
|
||||
|
||||
Each component we assign to our `StackNavigator` gets cloned by `react-navigation` with a prop called `navigation` which gives us full control over
|
||||
all of the navigation functionality we'll need.
|
||||
|
||||
* To "push" to a new screen we call the `navigate` method with the screen name we defined as the object key within `StackNavigator`.
|
||||
* To "pop", or go back to the previous screen on the stack we call the `goBack` method.
|
||||
|
||||
Lets add a simple button to push to the `Register` screen we defined:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
import React, { Component } from 'react';
|
||||
import { View, Button } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Login',
|
||||
headerStyle: {
|
||||
backgroundColor: '#E6853E',
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
};
|
||||
|
||||
// Call this method on the button press
|
||||
_register = () => {
|
||||
this.props.navigation.navigate('Register');
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Button onPress={this._register} title="Register Now!" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
||||
```
|
||||
|
||||
Go ahead and click the button, you'll be pushed to a new screen. By pressing the back arrow on the header, `react-navigation` will automatically
|
||||
call the `goBack` method for us:
|
||||
|
||||
![Transition!](assets/3-unauthenticated-push-pop.gif =300x\*)
|
||||
|
||||
> To style the `Register` page, simply add it's own `navigationOptions` static config!
|
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 341 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 219 KiB |
Before Width: | Height: | Size: 374 KiB |
|
@ -1,28 +0,0 @@
|
|||
# Conclusion
|
||||
|
||||
Hopefully from this Codorial you have seen the power of Firebase - allowing you to concentrate on your application and not worry about other complicated
|
||||
topics, such as authentication. However understanding how to integrate this into your main business logic can seem challenging at first. This Codorial
|
||||
has brought together a number of separate libaries which can be applied to any application context.
|
||||
|
||||
## Starter Kits
|
||||
|
||||
Invertase has provided a couple of starter kits to get your app up and running quicker:
|
||||
|
||||
### React Native Firebase Starter
|
||||
|
||||
https://github.com/invertase/react-native-firebase-starter
|
||||
|
||||
The React Native Firebase Starter is a free, pre-setup React Native app which integrates the [react-native-firebase](https://github.com/invertase/react-native-firebase)
|
||||
modules. It's perfect if you want to get up and running with no native module installation on both Android & iOS.
|
||||
|
||||
### React Native Authentication Starter Kit for Firebase
|
||||
|
||||
https://rnfirebase.io/kits/auth-starter
|
||||
|
||||
The React Native Authentication Starter Kit for Firebase is a premium starter kit for Android & iOS with a fully integrated authentication flow out of the box.
|
||||
It features email/password, Facebook, Google and Phone Number login, routing with [react-navigation](https://reactnavigation.org/) and Google Analytics for Firebase.
|
||||
|
||||
## Hire Us
|
||||
|
||||
The developers behind react-native-firebase at Invertase are available for hire on your next project, big or small. If you'd like to find out more,
|
||||
head over to our website and drop us a contact message: https://invertase.io/
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"title": "React Native authentication with Firebase",
|
||||
"description": "Create a React Native app from scratch, implementing authentication with react-native-firebase. This Codorial covers navigation, firebase, redux and more!",
|
||||
"tags": ["react-native", "redux", "react-redux", "firebase", "firebase-auth", "react-native-firebase", "react-navigation"],
|
||||
"steps": [
|
||||
{
|
||||
"title": "Getting Started",
|
||||
"file": "getting-started"
|
||||
},
|
||||
{
|
||||
"title": "Project Structure",
|
||||
"file": "project-structure"
|
||||
},
|
||||
{
|
||||
"title": "Understanding Firebase Auth",
|
||||
"file": "understanding-firebase-auth"
|
||||
},
|
||||
{
|
||||
"title": "Integrating Redux",
|
||||
"file": "integrating-redux"
|
||||
},
|
||||
{
|
||||
"title": "App Navigation",
|
||||
"file": "app-navigation"
|
||||
},
|
||||
{
|
||||
"title": "Handling Authentication State",
|
||||
"file": "handling-authentication-state"
|
||||
},
|
||||
{
|
||||
"title": "Creating a sign-in form",
|
||||
"file": "creating-a-signin-form"
|
||||
},
|
||||
{
|
||||
"title": "Facebook Login",
|
||||
"file": "facebook-login"
|
||||
},
|
||||
{
|
||||
"title": "Google Login",
|
||||
"file": "google-login"
|
||||
},
|
||||
{
|
||||
"title": "Conclusion",
|
||||
"file": "conclusion"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
# Creating a sign-in form
|
||||
|
||||
We can now get onto allowing the user to login with their email and password. First off we need to create a dummy user for testing. This can be
|
||||
done via the Firebase console on the 'Authentication' tab. Lets go ahead and create one now:
|
||||
|
||||
![Add new user](assets/add-user.jpg)
|
||||
|
||||
## Handling user input
|
||||
|
||||
React Native provides us with a [`TextInput`](https://facebook.github.io/react-native/docs/textinput.html) component, which renders the web
|
||||
equivalent of an `input` box in our app. `TextInput` components are 'uncontrolled' meaning we have to explicitly give it a value and handle
|
||||
updates the user enters. We're going to do this via component state, however you could also do this via our Redux store which is an option
|
||||
other React developers would go down.
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, TextInput } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Login',
|
||||
headerStyle: {
|
||||
backgroundColor: '#E6853E',
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
}
|
||||
|
||||
_updateEmail = email => {
|
||||
this.setState({ email });
|
||||
};
|
||||
|
||||
_updatePassword = password => {
|
||||
this.setState({ password });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder={'Email Address'}
|
||||
onChangeText={this._updateEmail}
|
||||
value={this.state.email}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder={'Password'}
|
||||
onChangeText={this._updatePassword}
|
||||
value={this.state.password}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
||||
```
|
||||
|
||||
If you reload your app, you will see two plain `TextInput` boxes which can accept input. As these are updated, the `onChangeText` prop is triggered
|
||||
which then updates state for that specific value. The inputs then individually update whenever their `value` from state changes:
|
||||
|
||||
![TextInput Changes](assets/textinput-update.gif =300x\*)
|
||||
|
||||
> If you want to hide your users password, use the `secureTextEntry` prop.
|
||||
|
||||
## Communicating with Firebase
|
||||
|
||||
Now we've got our users input readily available in state, we can use the values to send to Firebase! First off we need a trigger to do this:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, TextInput, Button } from 'react-native';
|
||||
|
||||
...
|
||||
|
||||
_signIn = () => {
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder={'Email Address'}
|
||||
onChangeText={this._updateEmail}
|
||||
value={this.state.email}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder={'Password'}
|
||||
onChangeText={this._updatePassword}
|
||||
value={this.state.password}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={'Sign In'}
|
||||
onPress={this._signIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
As mentioned in 'Understanding Firebase Auth', we can call the `signInAndRetrieveDataWithEmailAndPassword` method on the Firebase API within our `_signIn` method,
|
||||
using the state values.
|
||||
|
||||
```js
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
...
|
||||
|
||||
_signIn = () => {
|
||||
// extract the values from state
|
||||
const { email, password } = this.state;
|
||||
|
||||
firebase.auth().signInAndRetrieveDataWithEmailAndPassword(email, password)
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The Firebase call will catch any errors (see list [here](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#signInAndRetrieveDataWithEmailAndPassword))
|
||||
which may occur, such as a bad email address or incorrect email/password combination.
|
||||
|
||||
You may notice we don't listen our for the success response from the call (via the `.then`). As you may remember, back in our `App` component
|
||||
our listener using `onAuthStateChanged` will pick up any successful sign in that occurs - and you guessed it, update the Redux store with
|
||||
our users details, which causes `App` to re-render with our new `AuthenticatedStack`!
|
||||
|
||||
The `Welcome` component implemented below on the `AuthenticatedStack` contains a button which calls `firebase.auth().signOut()`, which triggers the
|
||||
reverse motion of showing the `UnauthenticatedStack` to the user - simple!
|
||||
|
||||
![Sign In](assets/signin.gif =300x\*)
|
||||
|
||||
> You may notice there's a delay when pressing the "Sign In" button and the `onAuthStateChanged` lister firing. An app should always give feedback
|
||||
> to a user when an action is happening. This can be accomplished through state/redux using many of the React Native components available such as
|
||||
> [`ActivityIndicator`](https://facebook.github.io/react-native/docs/activityindicator.html). You'll also want to handle any errors back from
|
||||
> Firebase to show to your user!
|
||||
|
||||
## Register / Forgot Password
|
||||
|
||||
The logic for registering or submitting a forgot password request is exactly the same as our sign in logic. Simply follow the same pattern
|
||||
as we've done above using `TextInput` components and state. Once you've got the desired information, call Firebase with the methods you need, such as
|
||||
[`createUserAndRetrieveDataWithEmailAndPassword`](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#createUserAndRetrieveDataWithEmailAndPassword)
|
||||
or [`sendPasswordResetEmail`](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#sendPasswordResetEmail)!
|
||||
|
||||
Remember to allow the user to navigate between these screens using the `navigate` method provided by `react-navigation`.
|
||||
|
||||
> You could also try implementing [`TabNavigation`](https://reactnavigation.org/docs/tab-based-navigation.html) to allow horizontal navigation between
|
||||
> screens which is another common navigation pattern.
|
|
@ -1,115 +0,0 @@
|
|||
# Facebook Login
|
||||
|
||||
Rather than users signing in to your app with an email and password, Firebase provides the opportunity to integrate with a number of login providers
|
||||
(or even your own!). It does this by creating a `credential` from an [OAuth](https://oauth.net/) request which your login provider returns, such as an
|
||||
`accessToken`.
|
||||
|
||||
The Firebase API allows us to call `signInAndRetrieveDataWithCredential` with a generated credential. You guessed it, just as we accomplished in
|
||||
'Creating a sign in form', the method triggers `onAuthStateChanged` if the request was a success - super simple!
|
||||
|
||||
## Installing `react-native-fbsdk`
|
||||
|
||||
Luckily as Facebook own React Native, they provide a handy wrapper around their own SDK to integrate with React Native, called [`react-native-fbsdk`](https://github.com/facebook/react-native-fbsdk).
|
||||
|
||||
```bash
|
||||
npm install --save react-native-fbsdk
|
||||
```
|
||||
|
||||
To save explaining how to install this library, refer to their [documentation](https://developers.facebook.com/docs/react-native) on how to
|
||||
install the library into your React Native project on both Android & iOS.
|
||||
|
||||
## Creating a credential
|
||||
|
||||
A credential can be generated by first obtaining an `accessToken` from Facebook. This is returned once a user successfully signs in via their
|
||||
Facebook app or popup (which the `react-native-fbsdk` handles).
|
||||
|
||||
In our `Login.js` component, go ahead and create a new method called `_facebookLogin`:
|
||||
|
||||
```js
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import { AccessToken, LoginManager } from 'react-native-fbsdk'; // import AccessToken and LoginManager
|
||||
|
||||
...
|
||||
|
||||
_facebookLogin = async () => {
|
||||
try {
|
||||
const result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
|
||||
|
||||
if (result.isCancelled) {
|
||||
throw new Error('User cancelled request'); // Handle this however fits the flow of your app
|
||||
}
|
||||
|
||||
console.log(`Login success with permissions: ${result.grantedPermissions.toString()}`);
|
||||
|
||||
// get the access token
|
||||
const data = await AccessToken.getCurrentAccessToken();
|
||||
|
||||
if (!data) {
|
||||
throw new Error('Something went wrong obtaining the users access token'); // Handle this however fits the flow of your app
|
||||
}
|
||||
|
||||
// create a new firebase credential with the token
|
||||
const credential = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
|
||||
|
||||
// login with credential
|
||||
await firebase.auth().signInAndRetrieveDataWithCredential(credential);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
There's quite a few steps involved here, which require asynchronous calls to both Facebook and Firebase so it's important to give feedback to your
|
||||
user whilst this process is running. There's also a number of errors which can occur, caused by the user (such as rejecting the request) or
|
||||
Firebase not having the Facebook provider enabled, so ensure the error is shown back to the user.
|
||||
|
||||
> You may notice here we make use of [`async/await`](https://ponyfoo.com/articles/understanding-javascript-async-await). This allows us to keep our code
|
||||
> feeling synchronous and handle any false positive errors we want to catch without worry about promise chaining.
|
||||
|
||||
### Triggering the method
|
||||
|
||||
Quite simply, just like the `_signIn` method we call the `_facebookLogin` method with a custom `Button`:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
...
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder={'Email Address'}
|
||||
onChangeText={this._updateEmail}
|
||||
value={this.state.email}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder={'Password'}
|
||||
onChangeText={this._updatePassword}
|
||||
value={this.state.password}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={'Sign In'}
|
||||
onPress={this._signIn}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={'Sign In with Facebook'}
|
||||
onPress={this._facebookLogin}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Updating your Facebook app ID & secret
|
||||
|
||||
Back in step 'Understanding Firebase Authentication', we enabled Firebase as a login provider on Facebook, however may have entered dummy values.
|
||||
|
||||
The `react-native-fbsdk` requires you to assign an app ID to your install as mentioned in their [quickstart guide](https://developers.facebook.com/quickstarts/?platform=android).
|
||||
Ensure that the app you choose also has the credentials on the Firebase console, otherwise you'll get an error back from Firebase when attempting to
|
||||
sign in with the generated credential.
|
|
@ -1,42 +0,0 @@
|
|||
# Getting Started
|
||||
|
||||
Welcome to the 'Authentication with Firebase' Codorial, using [React Native](http://facebook.github.io/react-native/) and [react-native-firebase](https://rnfirebase.io).
|
||||
Over the Codorial we'll cover how to setup your application to require both email/password login and social login using Facebook,
|
||||
to handling the users authenticated state using the popular [redux](https://redux.js.org/introduction) library whilst also integrating routing
|
||||
using [react-navigation](https://reactnavigation.org/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This Codorial assumes you know the basics of the following topics:
|
||||
|
||||
* React JS.
|
||||
* ES6 JavaScript.
|
||||
* Starting an app using an emulator on Android/iOS.
|
||||
* Understand how to setup a new Firebase project.
|
||||
* Managing your project using Android Stuido and/or XCode.
|
||||
* Installation of the [react-native-firebase](https://rnfirebase.io) library (see "Creating a base project" below).
|
||||
|
||||
This Codorial was created with React Native version `0.53.0`.
|
||||
|
||||
## Creating a base project
|
||||
|
||||
This project will take a bare bones React Native setup and explain every step required to implement a solid authentication flow in your application.
|
||||
To start we need a base project to work from.
|
||||
|
||||
Both options below require you to setup a new Firebase project and add the configuration file to your project - check out the [documentation](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) on how to do that if needed.
|
||||
|
||||
### Option 1: Using `react-native init`
|
||||
|
||||
You can quickly create a base React Native project using `react-native init` by following the React Native [documentation](http://facebook.github.io/react-native/docs/getting-started.html).
|
||||
|
||||
> Ensure you follow the "Building Projects with Native Code" tab, as the project won't work using Expo due to requiring native modules.
|
||||
|
||||
Once installed, you need to install the [react-native-firebase](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) library. Ensure you've
|
||||
also installed the Authentication module on your platform ([Android](https://rnfirebase.io/docs/v3.2.x/auth/android) or [iOS](https://rnfirebase.io/docs/v3.2.x/auth/ios))!
|
||||
|
||||
### Option 2: Using [react-native-firebase-starter](https://github.com/invertase/react-native-firebase-starter)
|
||||
|
||||
A starter kit has been created to help you get up and running with minimal setup needed. If you're new to React Native this will be perfect starting point.
|
||||
|
||||
> Keep in mind every Firebase module is installed in this starter kit. You can refer to the react-native-firebase [documentation](https://rnfirebase.io/docs) if you want to remove
|
||||
> any unwanted modules.
|
|
@ -1,48 +0,0 @@
|
|||
# Google Login
|
||||
|
||||
Much like Facebook loginin, Firebase provides the ability to accept and sign in with a Google credential.
|
||||
|
||||
## Installing `react-native-google-signin`
|
||||
|
||||
To sign in with Google, we recommend using `react-native-google-signin`. This provides a native way of obtaining the users
|
||||
Google accounts and their required `accessToken`.
|
||||
|
||||
```
|
||||
npm install react-native-google-signin --save
|
||||
react-native link react-native-google-signin
|
||||
```
|
||||
|
||||
## Creating a credential
|
||||
|
||||
To generate a new credential for the user, simply call the asynchronous `signIn` method `react-native-google-signin` provides and create a new
|
||||
credential from the `firebase.auth.GoogleAuthProvider`:
|
||||
|
||||
```js
|
||||
import { GoogleSignin } from 'react-native-google-signin';
|
||||
|
||||
// Calling this function will open Google for login.
|
||||
export const googleLogin = async () => {
|
||||
try {
|
||||
// Add any configuration settings here:
|
||||
await GoogleSignin.configure();
|
||||
|
||||
const data = await GoogleSignin.signIn();
|
||||
|
||||
// create a new firebase credential with the token
|
||||
const credential = firebase.auth.GoogleAuthProvider.credential(
|
||||
data.idToken,
|
||||
data.accessToken
|
||||
);
|
||||
|
||||
// login with credential
|
||||
const currentUser = await firebase
|
||||
.auth()
|
||||
.signInAndRetrieveDataWithCredential(credential);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The flow here is exactly the same as the rest of our app; logging in with the newly created credential will trigger our `onAuthStateChanged` lister to fire with
|
||||
the new user, logging us into the application.
|
|
@ -1,236 +0,0 @@
|
|||
# Handling Authentication State
|
||||
|
||||
Now we've got a basic navigation stack in place along with Redux, we can combine the two together to handle the users authenticated state.
|
||||
|
||||
## Listen for authentication state changes
|
||||
|
||||
As mentioned in "Understanding Firebase Auth", we can listen for auth state changes via `onAuthStateChanged`. As our app will require authentication
|
||||
to view the main content, we can conditionally render the 'unauthenticated' `StackNavigator` if the user is signed out in our `src/App.js`. Lets go
|
||||
ahead and add the boilerplate code to get this in motion:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
import UnauthenticatedStack from './screens/unauthenticated';
|
||||
|
||||
class App extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Listen for user auth state changes
|
||||
firebase.auth().onAuthStateChanged(user => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// Render a blank screen whilst we wait for Firebase.
|
||||
// The listener generally trigger immediately so it will be too fast for the user to see
|
||||
if (this.state.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <UnauthenticatedStack />;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
## Updating Redux with the user state
|
||||
|
||||
Rather than passing our `user` into component `state`, we're going to add it into into Redux instead. Firebase does provide direct access to the user
|
||||
via `firebase.auth().currentUser`, however as our app complexity grows we may want to integrate parts of the users data (for example the `uid`) into
|
||||
other parts of our Redux store. By storing the user in Redux, it is guaranteed that the user details will keep in-sync throughout our Redux store.
|
||||
|
||||
### Dispatching Actions
|
||||
|
||||
Another common Redux concept is called 'Dispatching Actions'. An action is an event with a unique name, which our reducer can listen out for and react
|
||||
to the action. Every action requires a `type` property and can pass any additional data along which the reducer needs to handle the action. Lets go ahead
|
||||
and create an `actions.js` file, where we'll define our first action:
|
||||
|
||||
```js
|
||||
// src/actions.js
|
||||
|
||||
// define our action type as a exportable constant
|
||||
export const USER_STATE_CHANGED = 'USER_STATE_CHANGED';
|
||||
|
||||
// define our action function
|
||||
export function userStateChanged(user) {
|
||||
return {
|
||||
type: USER_STATE_CHANGED, // required
|
||||
user: user ? user.toJSON() : null, // the response from Firebase: if a user exists, pass the serialized data down, else send a null value.
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To dispatch this action we need to again make use of `react-redux`. As our `App.js` has been provided the Redux store via the `Provider`
|
||||
component within `index.js`, we can use a [higher order component (HOC)](https://reactjs.org/docs/higher-order-components.html) called `connect` to provide the component with access to Redux:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
...
|
||||
|
||||
export default connect()(App);
|
||||
```
|
||||
|
||||
The `connect` HOC clones the given component with a function prop called `dispatch`. The `dispatch` function then takes an action, which when called 'dispatches'
|
||||
it to Redux. Lets jump back into our `App.js` and dispatch our action when `onAuthStateChanged` is triggered:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
// import our userStateChanged action
|
||||
import { userStateChanged } from './actions';
|
||||
|
||||
...
|
||||
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged((user) => {
|
||||
|
||||
// dispatch the imported action using the dispatch prop:
|
||||
this.props.dispatch(userStateChanged(user));
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
> You may want to consider implementing [`mapDispatchToProps`](https://github.com/reactjs/react-redux/blob/master/docs/api.md) to keep the action
|
||||
> usage reusable & cleaner.
|
||||
|
||||
Now every time `onAuthStateChanged` is triggered by Firebase, our Redux action will be dispatched regardless of whether a user is signed in our out!
|
||||
|
||||
### Reducing state
|
||||
|
||||
Back on step 'Integrating Redux' we setup a very basic Redux store. In order for us to latch onto the dispatched action we need to listen out
|
||||
for the events being sent to the reducer. To do this we import the action type which we exported within `actions.js` and conditionally
|
||||
return new state when that action is dispatched:
|
||||
|
||||
```jsx
|
||||
// src/store.js
|
||||
|
||||
import { createStore } from 'redux';
|
||||
|
||||
// import the action type
|
||||
import { USER_STATE_CHANGED } from './actions';
|
||||
|
||||
function reducer(state = {}, action) {
|
||||
// When USER_STATE_CHANGED is dispatched, update the store with new state
|
||||
if (action.type === USER_STATE_CHANGED) {
|
||||
return {
|
||||
user: action.user,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export default createStore(reducer);
|
||||
```
|
||||
|
||||
You may notice here that we return a brand new object rather than modifying the existing state. This is because Redux state is
|
||||
[immutable](https://facebook.github.io/immutable-js/). In order for Redux to know whether state has actually changed, it needs to compare the
|
||||
previous state with a new one.
|
||||
|
||||
> As your Redux state grows in complexity, it may be worth breaking your store out into multiple reducers. This can easily be achieved using
|
||||
> [combineReducers](https://redux.js.org/api-reference/combinereducers) from the `redux` package.
|
||||
|
||||
### Subscribing to Redux state
|
||||
|
||||
Now our action is updating the store whenever it's dispatched, we can subscribe to specific parts of the data which we need in our React
|
||||
components. The power of using `react-redux` is that it allows us to subscribe to data within our store and update the component whenever that
|
||||
data changes - we do this via a function known as `mapStateToProps`. This function is passed as the first argument of our `connect` HOC and gets
|
||||
given the current Redux state. It returns an object, which is cloned as props into our component. Here's how it works:
|
||||
|
||||
```js
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isUserAuthenticated: !!state.user,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(App);
|
||||
```
|
||||
|
||||
With this code, our `App` component will receive a prop called `isUserAuthenticated`, which in our case will be a `true` or `false` value based on
|
||||
whether the `state.user` object exists or not. Every time Redux state changes, this logic is run. What's handy is that if the result of any
|
||||
prop has changed, the component will be updated with the new data. If none of the props-to-be have changed, the component doesn't update.
|
||||
|
||||
> Keep in mind that if you return a complex `Array` or `object`, `react-redux` will only shallow compare them. Even if your state does not change
|
||||
> the component will still be re-rendered with the same data which can cause performance issues in our app if not handled. Therefore it is wise
|
||||
> to break components out to only subscribe to specific parts of primitive state (such as `strings`, `booleans` etc).
|
||||
|
||||
As our `App` component contains our routes, any change in the `isUserAuthenticated` value will cause the entire app to re-render - which in this case
|
||||
is fine as we're conditionally changing navigation stacks. Lets implement that logic:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import firebase from 'react-native-firebase';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UnauthenticatedStack from './screens/unauthenticated';
|
||||
import AuthenticatedStack from './screens/authenticated';
|
||||
import { userStateChanged } from './actions';
|
||||
|
||||
class App extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged(user => {
|
||||
this.props.dispatch(userStateChanged(user));
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// Render a blank screen whilst we wait for Firebase.
|
||||
// The listener generally trigger immediately so it will be too fast for the user to see
|
||||
if (this.state.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.props.isUserAuthenticated) {
|
||||
return <UnauthenticatedStack />;
|
||||
}
|
||||
|
||||
return <AuthenticatedStack />;
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isUserAuthenticated: !!state.user,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(App);
|
||||
```
|
||||
|
||||
As you can see in our `render` method, if the `isUserAuthenticated` value is `false`, we render our `UnauthenticatedStack`. If it's `true` we can
|
||||
render a new stack, in this case called `AuthenticatedStack` which is waiting for you to setup!
|
|
@ -1,84 +0,0 @@
|
|||
# Integrating Redux
|
||||
|
||||
Redux has become somewhat of a buzz word in the React community, and is generally used in most projects without thought. This Codorial
|
||||
won't go into details on what it is as their own [documentation](https://redux.js.org/introduction/motivation) does a wonderful job at explaining
|
||||
what it's for and why to use it.
|
||||
|
||||
_TLDR;_ Redux provides your app with a single "state" (data), which can be accessed by any component. You can subscribe to this data to cause
|
||||
a component update whenever something changes, even if it's deeply nested.
|
||||
|
||||
Although the end product of this Codorial certainly doesn't require Redux to function, as your app grows in complexity Redux becomes more and
|
||||
more important to manage your data.
|
||||
|
||||
## Installing Redux
|
||||
|
||||
Lets go ahead by installing the core Redux library and the React bindings:
|
||||
|
||||
```bash
|
||||
npm install --save redux react-redux
|
||||
```
|
||||
|
||||
Now within our projects `src` directory, create a `store.js` file. This file will contain all of our Redux logic, however you may want to break
|
||||
this out into multiple directories as your projects grows in complexity.
|
||||
|
||||
```js
|
||||
// src/store.js
|
||||
import { createStore } from 'redux';
|
||||
|
||||
// Create a reducer with empty state (see below for explanation)
|
||||
function reducer(state = {}, action) {
|
||||
return state;
|
||||
}
|
||||
|
||||
export default createStore(reducer);
|
||||
```
|
||||
|
||||
By default state is `null`, however we're setting it to an empty `object` (`state = {}`) so we can attempt to access
|
||||
shallow nested properties even if they don't exist.
|
||||
|
||||
> You may want to consider installing the [redux-logger](https://github.com/evgenyrodionov/redux-logger) library to improve
|
||||
> your Redux experience.
|
||||
|
||||
### Reducer
|
||||
|
||||
A reducer is a simple JavaScript function which takes two arguments: `state` & `action`. The idea of a reducer is to take "some data" from an `action`
|
||||
and return new state.
|
||||
|
||||
* `state` is any sort of data, which cannot be altered (immutable). A reducer must return a new value each time. More on this later.
|
||||
* `action` is an object containing a `type`, and any unreduced data. More on this later.
|
||||
|
||||
## Integrating Redux into the app
|
||||
|
||||
Our Redux store is now ready to be used. `react-redux` provides us with a `Provider` component which "provides" any children
|
||||
with access to the store via [context](https://reactjs.org/docs/context.html). Luckily we don't need to worry about this too much as the library
|
||||
takes care of the hard work!
|
||||
|
||||
Back within our original bootstrap file, we'll wrap the `App` component in the `Provider` component, so our business logic has access to Redux.
|
||||
|
||||
```jsx
|
||||
// src/index.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux'; // Import the Provider component
|
||||
|
||||
import App from './App';
|
||||
import store from './store';
|
||||
|
||||
function bootstrap() {
|
||||
// Init any external libraries here!
|
||||
|
||||
return class extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default bootstrap;
|
||||
```
|
||||
|
||||
Although noting will visually change, our app now has access to the power of Redux!
|
|
@ -1,113 +0,0 @@
|
|||
# Project Structure
|
||||
|
||||
Although it may seem trivial, having a good initial project structure ensures your code will be clean and reusable.
|
||||
The following step gives an opinionated guide to how this might look, which will work across both Android & iOS.
|
||||
|
||||
## Entry file
|
||||
|
||||
Every fresh React Native project a key file, an `index.js`, which currently renders a simple React component
|
||||
with basic styling. Rather than keeping our business logic within this file, we're going to keep it contained in it's own
|
||||
directory.
|
||||
|
||||
We'll achieve this by creating a `src` directory where our own code for the app will live. Create the directory with an `index.js` file, so your
|
||||
project structure resembles the following:
|
||||
|
||||
```
|
||||
- node_modules/
|
||||
- android/
|
||||
- ios/
|
||||
- src/
|
||||
-- index.js
|
||||
- index.js
|
||||
```
|
||||
|
||||
Now we can reference our bootstrap file in the `index.js` file, so both our platform share the same entry point:
|
||||
|
||||
```js
|
||||
// index.js
|
||||
|
||||
import { AppRegistry } from 'react-native';
|
||||
import bootstrap from './src';
|
||||
|
||||
AppRegistry.registerComponent('RNFirebaseStarter', () => bootstrap());
|
||||
```
|
||||
|
||||
## Bootstrapping your project
|
||||
|
||||
You may have noticed before, but the `bootstrap` import is a function. This allows us to setup or initialize any external modules before our
|
||||
React based application kick starts (such as [react-native-i18n](https://github.com/AlexanderZaytsev/react-native-i18n)).
|
||||
|
||||
Lets go ahead and setup our bootstrap file:
|
||||
|
||||
```js
|
||||
// src/index.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
function bootstrap() {
|
||||
// Init any external libraries here!
|
||||
|
||||
return class extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Bootstrapped!</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default bootstrap;
|
||||
```
|
||||
|
||||
Although this function simply returns a basic React component, later we'll be able to see the power of having a bootstrap file which
|
||||
consumes our entire application.
|
||||
|
||||
Go ahead and boot up your app onto your emulator. You should simply be presented with a plain screen with the words "Bootstrapped!".
|
||||
|
||||
![Bootstrapped!](assets/app-bootstrapped.jpg =300x\*)
|
||||
|
||||
Although a good starting point, we want to separate we'll our business logic out of the bootstrap file, keeping it purely for app
|
||||
initialization purposes. This can simply be done by creating a basic React component called `App.js`, which will also live in the `src` directory;
|
||||
|
||||
```js
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Bootstrapped!</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
Now we can reference this component within our bootstrap setup and return it from the bootstrap component:
|
||||
|
||||
```js
|
||||
// src/index.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import App from './App';
|
||||
|
||||
function bootstrap() {
|
||||
// Init any external libraries here!
|
||||
|
||||
return class extends Component {
|
||||
render() {
|
||||
return <App />;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default bootstrap;
|
||||
```
|
|
@ -1,206 +0,0 @@
|
|||
# Understanding Firebase Authentication
|
||||
|
||||
Before we dive into the logic of implementing authentication, it's first important to understand the Firebase API, and how it handles authentication
|
||||
with the various options we have.
|
||||
|
||||
As we're also working in React, we'll cover how Firebase's asynchronous API fits in with Reacts lifecycle methods.
|
||||
Luckily [react-native-firebase](https://rnfirebase.io) follows the Firebase web SDK API making this a breeze!
|
||||
|
||||
## Enabling authentication
|
||||
|
||||
We need to tell Firebase that we plan on using authentication and also enable a couple of the many login providers
|
||||
which Firebase supports. Head over to the [Firebase console](https://console.firebase.google.com/u/0/) and select the project you're using.
|
||||
|
||||
Find the Authentication section and you'll be prompted with a number of options. To get started, we want to select the "SIGN-IN METHOD" tab.
|
||||
|
||||
You'll see we have a number of options here, however for the purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers.
|
||||
Go ahead and enable these:
|
||||
|
||||
![Enabled Providers](assets/auth-providers.jpg)
|
||||
|
||||
> If you don't have a Facebook app, simply enter dummy values. We'll cover this later on.
|
||||
|
||||
## Listening to the users authentication state
|
||||
|
||||
The Firebase API provides a simple yet powerful listener, which triggers when some event changes with the user.
|
||||
This can be as obvious the user signing out or as subtle as the user validating their email address. Whatever the event, it triggers the same method: `onAuthStateChanged`.
|
||||
|
||||
```js
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
firebase.auth().onAuthStateChanged(user => {
|
||||
console.log(user);
|
||||
});
|
||||
```
|
||||
|
||||
The callback for the `onAuthStateChanged` method returns a single parameter, commonly referred to as `user`.
|
||||
|
||||
The concept here is simple;
|
||||
|
||||
* the method is first called once Firebase responds, then any time user state changes thereafter.
|
||||
* if a user is "signed in", our parameter will be a [`User`](https://firebase.google.com/docs/reference/js/firebase.User) `class`, containing all sorts of information we know about the user,
|
||||
from their e-mail address to any social provider IDs they may have signed in through.
|
||||
* if the user signed out, the parameter will be `null` value.
|
||||
|
||||
> The `user` class provides a `.toJSON()` method to serialize the users details if required.
|
||||
|
||||
### Handling authentication state when the app closes
|
||||
|
||||
A common question we get is how to handle the users authenticated state when the app closes/restarts so they don't have to keep logging in each
|
||||
time they open the app. Luckily this is all handled through Firebase so you don't have to worry about a thing - they'll only be signed out if they
|
||||
choose to, or the app is uninstalled.
|
||||
|
||||
## Creating a new account
|
||||
|
||||
Creating a new account on Firebase is very easy. Another method called `createUserAndRetrieveDataWithEmailAndPassword` is available which does exactly what it
|
||||
says on the tin! This is an asynchronous promise which will throw an exception if something is wrong (such as email taken, or password too short).
|
||||
Creating a user will also sign them in at the same time.
|
||||
|
||||
```js
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
firebase
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(
|
||||
'jim.bob@gmail.com',
|
||||
'supersecret!'
|
||||
)
|
||||
.then(user => {
|
||||
console.log('New User', user);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Woops, something went wrong!', error);
|
||||
});
|
||||
```
|
||||
|
||||
What's great about this is we don't need to know about the user within the `.then`, as any `onAuthStateChanged` listener would get triggered with our new
|
||||
users details - how awesome is that.
|
||||
|
||||
## Signing into an existing account
|
||||
|
||||
Unsurprisingly, Firebase offers a method called `signInAndRetrieveDataWithEmailAndPassword`, which follows the exact same flow as `createUserAndRetrieveDataWithEmailAndPassword`:
|
||||
|
||||
```js
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
firebase
|
||||
.auth()
|
||||
.signInAndRetrieveDataWithEmailAndPassword(
|
||||
'jim.bob@gmail.com',
|
||||
'supersecret!'
|
||||
)
|
||||
.then(user => {
|
||||
console.log('Existing User', user);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Woops, something went wrong!', error);
|
||||
});
|
||||
```
|
||||
|
||||
## Using with React
|
||||
|
||||
Firebase on it's own is super simple, however when using in a React environment there's some gotchas you need to be mindful of.
|
||||
|
||||
### Handling state changes
|
||||
|
||||
For any React component to update, a state or prop change needs to occur. As our Firebase auth methods are asynchronous we cannot rely on
|
||||
the data being available on component mount. To solve this issue, we can make use of state:
|
||||
|
||||
```jsx
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
class App extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: false,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged(user => {
|
||||
if (user) {
|
||||
this.setState({
|
||||
user: user.toJSON(), // serialize the user class
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, user } = this.state;
|
||||
|
||||
// Firebase hasn't responded yet
|
||||
if (loading) return null;
|
||||
|
||||
// Firebase has responded, but no user exists
|
||||
if (!user) {
|
||||
return (
|
||||
<View>
|
||||
<Text>Not signed in</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Firebase has responded, and a user exists
|
||||
return (
|
||||
<View>
|
||||
<Text>User signed in! {user.email}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribing/Un-subscribing from listeners
|
||||
|
||||
When subscribing to a new listener, such as `onAuthStateChanged`, a new reference to it is made in memory which has no knowledge of the
|
||||
React environment. If a component within your app mounts and subscribes, the method will still trigger even if your component unmounted.
|
||||
If this happens and you're updating state, you'll get a yellow box warning.
|
||||
|
||||
To get around this, Firebase returns an unsubscribe function to every subscriber method, which when calls removes the subscription from memory.
|
||||
This can be easily implemented using React lifecycle methods and class properties:
|
||||
|
||||
```jsx
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.unsubscribe = null; // Set a empty class method
|
||||
this.state = {
|
||||
loading: true,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Assign the class method to the unsubscriber response
|
||||
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
|
||||
// handle state changes
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Call the unsubscriber if it has been set
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Further reading
|
||||
|
||||
The above examples just scratch the surface of whats available with Firebase auth. Firebase itself provides some in-depth documentation
|
||||
on authentication and the many different implementation paths you can follow.
|
|
@ -28,7 +28,6 @@
|
|||
839D916F1EF3E20B0077C7C8 /* RNFirebaseAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91561EF3E20A0077C7C8 /* RNFirebaseAnalytics.m */; };
|
||||
839D91701EF3E20B0077C7C8 /* RNFirebaseAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91591EF3E20A0077C7C8 /* RNFirebaseAuth.m */; };
|
||||
839D91711EF3E20B0077C7C8 /* RNFirebaseRemoteConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D915C1EF3E20A0077C7C8 /* RNFirebaseRemoteConfig.m */; };
|
||||
839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D915F1EF3E20A0077C7C8 /* RNFirebaseCrash.m */; };
|
||||
839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91621EF3E20A0077C7C8 /* RNFirebaseDatabase.m */; };
|
||||
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.m */; };
|
||||
83AAA0792063DEC2007EC5F7 /* RNFirebaseInvites.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AAA0772063DEC2007EC5F7 /* RNFirebaseInvites.m */; };
|
||||
|
@ -93,8 +92,6 @@
|
|||
839D91591EF3E20A0077C7C8 /* RNFirebaseAuth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAuth.m; sourceTree = "<group>"; };
|
||||
839D915B1EF3E20A0077C7C8 /* RNFirebaseRemoteConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseRemoteConfig.h; sourceTree = "<group>"; };
|
||||
839D915C1EF3E20A0077C7C8 /* RNFirebaseRemoteConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseRemoteConfig.m; sourceTree = "<group>"; };
|
||||
839D915E1EF3E20A0077C7C8 /* RNFirebaseCrash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseCrash.h; sourceTree = "<group>"; };
|
||||
839D915F1EF3E20A0077C7C8 /* RNFirebaseCrash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseCrash.m; sourceTree = "<group>"; };
|
||||
839D91611EF3E20A0077C7C8 /* RNFirebaseDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseDatabase.h; sourceTree = "<group>"; };
|
||||
839D91621EF3E20A0077C7C8 /* RNFirebaseDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseDatabase.m; sourceTree = "<group>"; };
|
||||
839D91671EF3E20A0077C7C8 /* RNFirebasePerformance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebasePerformance.h; sourceTree = "<group>"; };
|
||||
|
@ -174,7 +171,6 @@
|
|||
839D91541EF3E20A0077C7C8 /* analytics */,
|
||||
839D91571EF3E20A0077C7C8 /* auth */,
|
||||
839D915A1EF3E20A0077C7C8 /* config */,
|
||||
839D915D1EF3E20A0077C7C8 /* crash */,
|
||||
839D91601EF3E20A0077C7C8 /* database */,
|
||||
8376F70D1F7C141500D45A85 /* firestore */,
|
||||
839D91631EF3E20A0077C7C8 /* messaging */,
|
||||
|
@ -291,16 +287,6 @@
|
|||
path = RNFirebase/config;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
839D915D1EF3E20A0077C7C8 /* crash */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
839D915E1EF3E20A0077C7C8 /* RNFirebaseCrash.h */,
|
||||
839D915F1EF3E20A0077C7C8 /* RNFirebaseCrash.m */,
|
||||
);
|
||||
name = crash;
|
||||
path = RNFirebase/crash;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
839D91601EF3E20A0077C7C8 /* database */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -431,7 +417,6 @@
|
|||
83AAA0792063DEC2007EC5F7 /* RNFirebaseInvites.m in Sources */,
|
||||
8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */,
|
||||
83C3EEEE1FA1EACC00B64D3C /* RNFirebaseUtil.m in Sources */,
|
||||
839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */,
|
||||
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */,
|
||||
8323CF061F6FBD870071420B /* BannerComponent.m in Sources */,
|
||||
839D916D1EF3E20B0077C7C8 /* RNFirebaseAdMobInterstitial.m in Sources */,
|
||||
|
@ -535,7 +520,6 @@
|
|||
"$(SRCROOT)/../../../ios/Pods/FirebaseAnalytics/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseAuth/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseCore/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseCrash/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseDatabase/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseDynamicLinks/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseFirestore/Frameworks",
|
||||
|
@ -579,7 +563,6 @@
|
|||
"$(SRCROOT)/../../../ios/Pods/FirebaseAnalytics/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseAuth/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseCore/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseCrash/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseDatabase/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseDynamicLinks/Frameworks",
|
||||
"$(SRCROOT)/../../../ios/Pods/FirebaseFirestore/Frameworks",
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
#ifndef RNFirebaseCrash_h
|
||||
#define RNFirebaseCrash_h
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if __has_include(<FirebaseCrash/FIRCrashLog.h>)
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RNFirebaseCrash : NSObject <RCTBridgeModule> {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#else
|
||||
@interface RNFirebaseCrash : NSObject
|
||||
@end
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,35 +0,0 @@
|
|||
#import "RNFirebaseCrash.h"
|
||||
|
||||
#if __has_include(<FirebaseCrash/FIRCrashLog.h>)
|
||||
#import <FirebaseCrash/FIRCrashLog.h>
|
||||
|
||||
@implementation RNFirebaseCrash
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
RCT_EXPORT_METHOD(log:(NSString *)message) {
|
||||
FIRCrashLog(@"%@", message);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(logcat:(nonnull NSNumber *) level tag:(NSString *) tag message:(NSString *) message) {
|
||||
FIRCrashLog(@"%@", message);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(report:(NSString *) message) {
|
||||
FIRCrashLog(@"%@", message);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setCrashCollectionEnabled:(BOOL *) enabled) {
|
||||
// Not available yet
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(isCrashCollectionEnabled:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
// Not available yet
|
||||
resolve(@YES);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#else
|
||||
@implementation RNFirebaseCrash
|
||||
@end
|
||||
#endif
|
|
@ -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) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
}
|
||||
|
||||
@property NSMutableDictionary *traces;
|
||||
@property NSMutableDictionary *httpMetrics;
|
||||
|
||||
@end
|
||||
|
||||
|
|