RNFirebase android

This commit is contained in:
Salakar
2017-03-02 11:30:08 +00:00
parent 1c114a447b
commit c652d73c9f
14 changed files with 2844 additions and 0 deletions

10
android/.editorconfig Normal file
View File

@@ -0,0 +1,10 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

56
android/build.gradle Normal file
View File

@@ -0,0 +1,56 @@
// START - required to allow working on this project inside Android Studio
// YES, jcenter is required twice - it somehow tricks studio into compiling deps below
// doesn't break anything anywhere else and projects using this lib work as normal
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.3'
}
}
// END
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
}
}
}
// START - required to allow working on this project inside Android Studio
// YES, jcenter is required twice - it somehow tricks studio into compiling deps below
// doesn't break anything anywhere else and projects using this lib work as normal
// you'll now have code completion/validation and all the other AS goodies.
allprojects {
repositories {
jcenter()
}
}
// END
dependencies {
compile 'com.facebook.react:react-native:0.20.+'
compile 'com.google.android.gms:play-services-base:9.8.0'
compile 'com.google.firebase:firebase-core:9.8.0'
compile 'com.google.firebase:firebase-config:9.8.0'
compile 'com.google.firebase:firebase-auth:9.8.0'
compile 'com.google.firebase:firebase-analytics:9.8.0'
compile 'com.google.firebase:firebase-database:9.8.0'
compile 'com.google.firebase:firebase-storage:9.8.0'
compile 'com.google.firebase:firebase-messaging:9.8.0'
}

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.invertase.firebase">
</manifest>

View File

@@ -0,0 +1,29 @@
package io.invertase.firebase;
import android.util.Log;
import android.os.Bundle;
import android.content.Intent;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
import io.invertase.firebase.messaging.RNFirebaseMessaging;
public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService {
private static final String TAG = "FSInstanceIdService";
/**
*
*/
@Override
public void onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_TOKEN);
Bundle bundle = new Bundle();
bundle.putString("token", refreshedToken);
i.putExtras(bundle);
sendBroadcast(i);
}
}

View File

@@ -0,0 +1,74 @@
package io.invertase.firebase;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.firebase.messaging.SendException;
import io.invertase.firebase.messaging.RNFirebaseMessaging;
public class RNFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "FSMessagingService";
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "Remote message received");
// debug
Log.d(TAG, "From: " + remoteMessage.getFrom());
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
}
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_NOTIFICATION);
i.putExtra("data", remoteMessage);
sendOrderedBroadcast(i, null);
}
@Override
public void onMessageSent(String msgId) {
// Called when an upstream message has been successfully sent to the GCM connection server.
Log.d(TAG, "upstream message has been successfully sent");
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_SEND);
i.putExtra("msgId", msgId);
sendOrderedBroadcast(i, null);
}
@Override
public void onSendError(String msgId, Exception exception) {
// Called when there was an error sending an upstream message.
Log.d(TAG, "error sending an upstream message");
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_SEND);
i.putExtra("msgId", msgId);
i.putExtra("hasError", true);
SendException sendException = (SendException) exception;
i.putExtra("errorCode", sendException.getErrorCode());
switch(sendException.getErrorCode()){
case SendException.ERROR_INVALID_PARAMETERS:
i.putExtra("errorMessage", "Message was sent with invalid parameters.");
break;
case SendException.ERROR_SIZE:
i.putExtra("errorMessage", "Message exceeded the maximum payload size.");
break;
case SendException.ERROR_TOO_MANY_MESSAGES:
i.putExtra("errorMessage", "App has too many pending messages so this one was dropped.");
break;
case SendException.ERROR_TTL_EXCEEDED:
i.putExtra("errorMessage", "Message time to live (TTL) was exceeded before the message could be sent.");
break;
case SendException.ERROR_UNKNOWN:
default:
i.putExtra("errorMessage", "Unknown error.");
break;
}
sendOrderedBroadcast(i, null);
}
}

View File

@@ -0,0 +1,208 @@
package io.invertase.firebase;
import java.util.Map;
import java.util.HashMap;
import android.util.Log;
import android.content.Context;
import android.support.annotation.Nullable;
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.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
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 com.google.firebase.database.ServerValue;
interface KeySetterFn {
String setKeyOrDefault(String a, String b);
}
@SuppressWarnings("WeakerAccess")
public class RNFirebaseModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
private static final String TAG = "RNFirebase";
private FirebaseApp app;
public RNFirebaseModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return TAG;
}
private WritableMap getPlayServicesStatus() {
GoogleApiAvailability gapi = GoogleApiAvailability.getInstance();
final int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext());
WritableMap result = Arguments.createMap();
result.putInt("status", status);
if (status == ConnectionResult.SUCCESS) {
result.putBoolean("isAvailable", true);
} else {
result.putBoolean("isAvailable", false);
result.putBoolean("isUserResolvableError", gapi.isUserResolvableError(status));
result.putString("error", gapi.getErrorString(status));
}
return result;
}
@ReactMethod
public void configureWithOptions(final ReadableMap params, @Nullable final Callback onComplete) {
Log.i(TAG, "configureWithOptions");
FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
FirebaseOptions defaultOptions = FirebaseOptions.fromResource(getReactApplicationContext().getBaseContext());
if (defaultOptions == null) {
defaultOptions = new FirebaseOptions.Builder().build();
}
KeySetterFn fn = new KeySetterFn() {
public String setKeyOrDefault(
final String key,
final String defaultValue) {
if (params.hasKey(key)) {
// User-set key
final String val = params.getString(key);
Log.d(TAG, "Setting " + key + " from params to: " + val);
return val;
} else if (defaultValue != null && !defaultValue.equals("")) {
Log.d(TAG, "Setting " + key + " from params to: " + defaultValue);
return defaultValue;
} else {
return null;
}
}
};
String val = fn.setKeyOrDefault("applicationId", defaultOptions.getApplicationId());
if (val != null) builder.setApplicationId(val);
val = fn.setKeyOrDefault("apiKey", defaultOptions.getApiKey());
if (val != null) builder.setApiKey(val);
val = fn.setKeyOrDefault("gcmSenderID", defaultOptions.getGcmSenderId());
if (val != null) builder.setGcmSenderId(val);
val = fn.setKeyOrDefault("storageBucket", defaultOptions.getStorageBucket());
if (val != null) builder.setStorageBucket(val);
val = fn.setKeyOrDefault("databaseURL", defaultOptions.getDatabaseUrl());
if (val != null) builder.setDatabaseUrl(val);
val = fn.setKeyOrDefault("databaseUrl", defaultOptions.getDatabaseUrl());
if (val != null) builder.setDatabaseUrl(val);
val = fn.setKeyOrDefault("clientId", defaultOptions.getApplicationId());
if (val != null) builder.setApplicationId(val);
// if (params.hasKey("applicationId")) {
// final String applicationId = params.getString("applicationId");
// Log.d(TAG, "Setting applicationId from params " + applicationId);
// builder.setApplicationId(applicationId);
// }
// if (params.hasKey("apiKey")) {
// final String apiKey = params.getString("apiKey");
// Log.d(TAG, "Setting API key from params " + apiKey);
// builder.setApiKey(apiKey);
// }
// if (params.hasKey("APIKey")) {
// final String apiKey = params.getString("APIKey");
// Log.d(TAG, "Setting API key from params " + apiKey);
// builder.setApiKey(apiKey);
// }
// if (params.hasKey("gcmSenderID")) {
// final String gcmSenderID = params.getString("gcmSenderID");
// Log.d(TAG, "Setting gcmSenderID from params " + gcmSenderID );
// builder.setGcmSenderId(gcmSenderID);
// }
// if (params.hasKey("storageBucket")) {
// final String storageBucket = params.getString("storageBucket");
// Log.d(TAG, "Setting storageBucket from params " + storageBucket);
// builder.setStorageBucket(storageBucket);
// }
// if (params.hasKey("databaseURL")) {
// final String databaseURL = params.getString("databaseURL");
// Log.d(TAG, "Setting databaseURL from params " + databaseURL);
// builder.setDatabaseUrl(databaseURL);
// }
// if (params.hasKey("clientID")) {
// final String clientID = params.getString("clientID");
// Log.d(TAG, "Setting clientID from params " + clientID);
// builder.setApplicationId(clientID);
// }
try {
Log.i(TAG, "Configuring app");
if (app == null) {
app = FirebaseApp.initializeApp(getReactApplicationContext().getBaseContext(), builder.build());
}
Log.i(TAG, "Configured");
WritableMap resp = Arguments.createMap();
resp.putString("msg", "success");
onComplete.invoke(null, resp);
} catch (Exception ex) {
Log.e(TAG, "ERROR configureWithOptions");
Log.e(TAG, ex.getMessage());
WritableMap resp = Arguments.createMap();
resp.putString("msg", ex.getMessage());
onComplete.invoke(resp);
}
}
@ReactMethod
public void serverValue(@Nullable final Callback onComplete) {
WritableMap timestampMap = Arguments.createMap();
for (Map.Entry<String, String> entry : ServerValue.TIMESTAMP.entrySet()) {
timestampMap.putString(entry.getKey(), entry.getValue());
}
WritableMap map = Arguments.createMap();
map.putMap("TIMESTAMP", timestampMap);
if (onComplete != null) onComplete.invoke(null, map);
}
// Internal helpers
@Override
public void onHostResume() {
WritableMap params = Arguments.createMap();
params.putBoolean("isForground", true);
Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params);
}
@Override
public void onHostPause() {
WritableMap params = Arguments.createMap();
params.putBoolean("isForground", false);
Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params);
}
@Override
public void onHostDestroy() {
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("googleApiAvailability", getPlayServicesStatus());
// TODO remove once this has been moved on ios
constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
return constants;
}
}

View File

@@ -0,0 +1,64 @@
package io.invertase.firebase;
import android.content.Context;
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.List;
import java.util.ArrayList;
import java.util.Collections;
import io.invertase.firebase.auth.RNFirebaseAuth;
import io.invertase.firebase.storage.RNFirebaseStorage;
import io.invertase.firebase.database.RNFirebaseDatabase;
import io.invertase.firebase.analytics.RNFirebaseAnalytics;
import io.invertase.firebase.messaging.RNFirebaseMessaging;
@SuppressWarnings("unused")
public class RNFirebasePackage implements ReactPackage {
private Context mContext;
public RNFirebasePackage() {
}
/**
* @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 RNFirebaseModule(reactContext));
modules.add(new RNFirebaseAuth(reactContext));
modules.add(new RNFirebaseDatabase(reactContext));
modules.add(new RNFirebaseAnalytics(reactContext));
modules.add(new RNFirebaseStorage(reactContext));
modules.add(new RNFirebaseMessaging(reactContext));
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @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();
}
}

View File

@@ -0,0 +1,301 @@
package io.invertase.firebase;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.ReadableArray;
import com.google.firebase.database.DataSnapshot;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
@SuppressWarnings("WeakerAccess")
public class Utils {
private static final String TAG = "Utils";
// TODO NOTE
public static void todoNote(final String tag, final String name, final Callback callback) {
Log.e(tag, "The method " + name + " has not yet been implemented.");
Log.e(tag, "Feel free to contribute to finish the method in the source.");
WritableMap errorMap = Arguments.createMap();
errorMap.putString("error", "unimplemented");
callback.invoke(null, errorMap);
}
/**
* send a JS event
**/
public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) {
if (context != null) {
context
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
} else {
Log.d(TAG, "Missing context - cannot send event!");
}
}
// snapshot
public static WritableMap dataSnapshotToMap(
String name,
String path,
String modifiersString,
DataSnapshot dataSnapshot
) {
WritableMap data = Arguments.createMap();
data.putString("key", dataSnapshot.getKey());
data.putBoolean("exists", dataSnapshot.exists());
data.putBoolean("hasChildren", dataSnapshot.hasChildren());
data.putDouble("childrenCount", dataSnapshot.getChildrenCount());
if (!dataSnapshot.hasChildren()) {
Object value = dataSnapshot.getValue();
String type = value != null ? value.getClass().getName() : "";
switch (type) {
case "java.lang.Boolean":
data.putBoolean("value", (Boolean) value);
break;
case "java.lang.Long":
Long longVal = (Long) value;
data.putDouble("value", (double) longVal);
break;
case "java.lang.Double":
data.putDouble("value", (Double) value);
break;
case "java.lang.String":
data.putString("value", (String) value);
break;
default:
data.putString("value", null);
}
} else {
Object value = Utils.castSnapshotValue(dataSnapshot);
if (value instanceof WritableNativeArray) {
data.putArray("value", (WritableArray) value);
} else {
data.putMap("value", (WritableMap) value);
}
}
// Child keys
WritableArray childKeys = Utils.getChildKeys(dataSnapshot);
data.putArray("childKeys", childKeys);
Object priority = dataSnapshot.getPriority();
if (priority == null) {
data.putString("priority", null);
} else {
data.putString("priority", priority.toString());
}
WritableMap eventMap = Arguments.createMap();
eventMap.putString("eventName", name);
eventMap.putMap("snapshot", data);
eventMap.putString("path", path);
eventMap.putString("modifiersString", modifiersString);
return eventMap;
}
public static <Any> Any castSnapshotValue(DataSnapshot snapshot) {
if (snapshot.hasChildren()) {
if (isArray(snapshot)) {
return (Any) buildArray(snapshot);
} else {
return (Any) buildMap(snapshot);
}
} else {
if (snapshot.getValue() != null) {
String type = snapshot.getValue().getClass().getName();
switch (type) {
case "java.lang.Boolean":
return (Any) (snapshot.getValue());
case "java.lang.Long":
return (Any) (snapshot.getValue());
case "java.lang.Double":
return (Any) (snapshot.getValue());
case "java.lang.String":
return (Any) (snapshot.getValue());
default:
Log.w(TAG, "Invalid type: " + type);
return (Any) null;
}
}
return (Any) null;
}
}
private static boolean isArray(DataSnapshot snapshot) {
long expectedKey = 0;
for (DataSnapshot child : snapshot.getChildren()) {
try {
long key = Long.parseLong(child.getKey());
if (key == expectedKey) {
expectedKey++;
} else {
return false;
}
} catch (NumberFormatException ex) {
return false;
}
}
return true;
}
private static <Any> WritableArray buildArray(DataSnapshot snapshot) {
WritableArray array = Arguments.createArray();
for (DataSnapshot child : snapshot.getChildren()) {
Any castedChild = castSnapshotValue(child);
switch (castedChild.getClass().getName()) {
case "java.lang.Boolean":
array.pushBoolean((Boolean) castedChild);
break;
case "java.lang.Long":
Long longVal = (Long) castedChild;
array.pushDouble((double) longVal);
break;
case "java.lang.Double":
array.pushDouble((Double) castedChild);
break;
case "java.lang.String":
array.pushString((String) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeMap":
array.pushMap((WritableMap) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeArray":
array.pushArray((WritableArray) castedChild);
break;
default:
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
break;
}
}
return array;
}
private static <Any> WritableMap buildMap(DataSnapshot snapshot) {
WritableMap map = Arguments.createMap();
for (DataSnapshot child : snapshot.getChildren()) {
Any castedChild = castSnapshotValue(child);
switch (castedChild.getClass().getName()) {
case "java.lang.Boolean":
map.putBoolean(child.getKey(), (Boolean) castedChild);
break;
case "java.lang.Long":
Long longVal = (Long) castedChild;
map.putDouble(child.getKey(), (double) longVal);
break;
case "java.lang.Double":
map.putDouble(child.getKey(), (Double) castedChild);
break;
case "java.lang.String":
map.putString(child.getKey(), (String) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeMap":
map.putMap(child.getKey(), (WritableMap) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeArray":
map.putArray(child.getKey(), (WritableArray) castedChild);
break;
default:
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
break;
}
}
return map;
}
public static WritableArray getChildKeys(DataSnapshot snapshot) {
WritableArray childKeys = Arguments.createArray();
if (snapshot.hasChildren()) {
for (DataSnapshot child : snapshot.getChildren()) {
childKeys.pushString(child.getKey());
}
}
return childKeys;
}
public static Map<String, Object> recursivelyDeconstructReadableMap(ReadableMap readableMap) {
Map<String, Object> deconstructedMap = new HashMap<>();
if (readableMap == null) {
return deconstructedMap;
}
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
ReadableType type = readableMap.getType(key);
switch (type) {
case Null:
deconstructedMap.put(key, null);
break;
case Boolean:
deconstructedMap.put(key, readableMap.getBoolean(key));
break;
case Number:
deconstructedMap.put(key, readableMap.getDouble(key));
break;
case String:
deconstructedMap.put(key, readableMap.getString(key));
break;
case Map:
deconstructedMap.put(key, Utils.recursivelyDeconstructReadableMap(readableMap.getMap(key)));
break;
case Array:
deconstructedMap.put(key, Utils.recursivelyDeconstructReadableArray(readableMap.getArray(key)));
break;
default:
throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
}
}
return deconstructedMap;
}
public static List<Object> recursivelyDeconstructReadableArray(ReadableArray readableArray) {
List<Object> deconstructedList = new ArrayList<>(readableArray.size());
for (int i = 0; i < readableArray.size(); i++) {
ReadableType indexType = readableArray.getType(i);
switch (indexType) {
case Null:
deconstructedList.add(i, null);
break;
case Boolean:
deconstructedList.add(i, readableArray.getBoolean(i));
break;
case Number:
deconstructedList.add(i, readableArray.getDouble(i));
break;
case String:
deconstructedList.add(i, readableArray.getString(i));
break;
case Map:
deconstructedList.add(i, Utils.recursivelyDeconstructReadableMap(readableArray.getMap(i)));
break;
case Array:
deconstructedList.add(i, Utils.recursivelyDeconstructReadableArray(readableArray.getArray(i)));
break;
default:
throw new IllegalArgumentException("Could not convert object at index " + i + ".");
}
}
return deconstructedList;
}
}

View File

@@ -0,0 +1,103 @@
package io.invertase.firebase.analytics;
import android.util.Log;
import android.app.Activity;
import android.support.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
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 {
private static final String TAG = "RNFirebaseAnalytics";
public RNFirebaseAnalytics(ReactApplicationContext reactContext) {
super(reactContext);
Log.d(TAG, "New instance");
}
/**
*
* @return
*/
@Override
public String getName() {
return TAG;
}
@ReactMethod
public void logEvent(final String name, @Nullable final ReadableMap params) {
FirebaseAnalytics.getInstance(getReactApplicationContext()).logEvent(name, Arguments.toBundle(params));
}
/**
*
* @param enabled
*/
@ReactMethod
public void setAnalyticsCollectionEnabled(final Boolean enabled) {
FirebaseAnalytics.getInstance(getReactApplicationContext()).setAnalyticsCollectionEnabled(enabled);
}
/**
*
* @param screenName
* @param screenClassOverride
*/
@ReactMethod
public void setCurrentScreen(final String screenName, final String screenClassOverride) {
final Activity activity = getCurrentActivity();
if (activity != null) {
// needs to be run on main thread
Log.d(TAG, "setCurrentScreen " + screenName + " - " + screenClassOverride);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
FirebaseAnalytics.getInstance(getReactApplicationContext()).setCurrentScreen(activity, screenName, screenClassOverride);
}
});
}
}
/**
*
* @param milliseconds
*/
@ReactMethod
public void setMinimumSessionDuration(final double milliseconds) {
FirebaseAnalytics.getInstance(getReactApplicationContext()).setMinimumSessionDuration((long) milliseconds);
}
/**
*
* @param milliseconds
*/
@ReactMethod
public void setSessionTimeoutDuration(final double milliseconds) {
FirebaseAnalytics.getInstance(getReactApplicationContext()).setSessionTimeoutDuration((long) milliseconds);
}
/**
*
* @param id
*/
@ReactMethod
public void setUserId(final String id) {
FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserId(id);
}
/**
*
* @param name
* @param value
*/
@ReactMethod
public void setUserProperty(final String name, final String value) {
FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserProperty(name, value);
}
}

View File

@@ -0,0 +1,619 @@
package io.invertase.firebase.auth;
import android.util.Log;
import java.util.Map;
import android.net.Uri;
import android.support.annotation.NonNull;
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.Callback;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactContext;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.UserProfileChangeRequest;
import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GetTokenResult;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.auth.EmailAuthProvider;
import io.invertase.firebase.Utils;
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class RNFirebaseAuth extends ReactContextBaseJavaModule {
private final int NO_CURRENT_USER = 100;
private final int ERROR_FETCHING_TOKEN = 101;
private final int ERROR_SENDING_VERIFICATION_EMAIL = 102;
private static final String TAG = "RNFirebaseAuth";
// private Context context;
private ReactContext mReactContext;
private FirebaseAuth mAuth;
private FirebaseAuth.AuthStateListener mAuthListener;
public RNFirebaseAuth(ReactApplicationContext reactContext) {
super(reactContext);
mReactContext = reactContext;
mAuth = FirebaseAuth.getInstance();
Log.d(TAG, "New RNFirebaseAuth instance");
}
@Override
public String getName() {
return TAG;
}
/**
* Returns a no user error.
*
* @param callback JS callback
*/
private void callbackNoUser(Callback callback, Boolean isError) {
WritableMap err = Arguments.createMap();
err.putInt("errorCode", NO_CURRENT_USER);
err.putString("errorMessage", "No current user");
if (isError) {
callback.invoke(err);
} else {
callback.invoke(null, null);
}
}
@ReactMethod
public void listenForAuth() {
if (mAuthListener == null) {
mAuthListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
WritableMap msgMap = Arguments.createMap();
msgMap.putString("eventName", "listenForAuth");
if (user != null) {
// TODO move to helper
WritableMap userMap = getUserMap(user);
msgMap.putBoolean("authenticated", true);
msgMap.putMap("user", userMap);
Utils.sendEvent(mReactContext, "listenForAuth", msgMap);
} else {
msgMap.putBoolean("authenticated", false);
Utils.sendEvent(mReactContext, "listenForAuth", msgMap);
}
}
};
mAuth.addAuthStateListener(mAuthListener);
}
}
@ReactMethod
public void unlistenForAuth(final Callback callback) {
if (mAuthListener != null) {
mAuth.removeAuthStateListener(mAuthListener);
// TODO move to helper
WritableMap resp = Arguments.createMap();
resp.putString("status", "complete");
callback.invoke(null, resp);
}
}
@ReactMethod
public void createUserWithEmail(final String email, final String password, final Callback callback) {
mAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
try {
if (task.isSuccessful()) {
userCallback(task.getResult().getUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
@ReactMethod
public void signInWithEmail(final String email, final String password, final Callback callback) {
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
try {
if (task.isSuccessful()) {
userCallback(task.getResult().getUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
@ReactMethod
public void signInWithProvider(final String provider, final String authToken, final String authSecret, final Callback callback) {
if (provider.equals("facebook")) {
this.facebookLogin(authToken, callback);
} else if (provider.equals("google")) {
this.googleLogin(authToken, callback);
} else
// TODO
Utils.todoNote(TAG, "signInWithProvider", callback);
}
@ReactMethod
public void linkPassword(final String email, final String password, final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
AuthCredential credential = EmailAuthProvider.getCredential(email, password);
user
.linkWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
try {
if (task.isSuccessful()) {
Log.d(TAG, "user linked with password credential");
userCallback(mAuth.getCurrentUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void link(final String provider, final String authToken, final String authSecret, final Callback callback) {
if (provider.equals("password")) {
linkPassword(authToken, authSecret, callback);
} else
// TODO other providers
Utils.todoNote(TAG, "linkWithProvider", callback);
}
@ReactMethod
public void signInAnonymously(final Callback callback) {
Log.d(TAG, "signInAnonymously:called:");
mAuth.signInAnonymously()
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful());
try {
if (task.isSuccessful()) {
userCallback(task.getResult().getUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
@ReactMethod
public void signInWithCustomToken(final String customToken, final Callback callback) {
mAuth.signInWithCustomToken(customToken)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful());
try {
if (task.isSuccessful()) {
userCallback(task.getResult().getUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
@ReactMethod
public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) {
// TODO:
Utils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback);
// AuthCredential credential;
// Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider);
}
@ReactMethod
public void updateUserEmail(final String email, final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
user
.updateEmail(email)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
try {
if (task.isSuccessful()) {
Log.d(TAG, "User email address updated");
userCallback(mAuth.getCurrentUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void updateUserPassword(final String newPassword, final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
user.updatePassword(newPassword)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
try {
if (task.isSuccessful()) {
Log.d(TAG, "User password updated");
userCallback(mAuth.getCurrentUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void sendPasswordResetWithEmail(final String email, final Callback callback) {
mAuth.sendPasswordResetEmail(email)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
try {
if (task.isSuccessful()) {
WritableMap resp = Arguments.createMap();
resp.putString("status", "complete");
callback.invoke(null, resp);
} else {
callback.invoke(task.getException().toString());
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
@ReactMethod
public void deleteUser(final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
user.delete()
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
try {
if (task.isSuccessful()) {
Log.d(TAG, "User account deleted");
WritableMap resp = Arguments.createMap();
resp.putString("status", "complete");
resp.putString("msg", "User account deleted");
callback.invoke(null, resp);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void sendEmailVerification(final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
user.sendEmailVerification()
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
try {
if (task.isSuccessful()) {
WritableMap resp = Arguments.createMap();
resp.putString("status", "complete");
resp.putString("msg", "User verification email sent");
callback.invoke(null, resp);
} else {
WritableMap err = Arguments.createMap();
err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL);
err.putString("errorMessage", task.getException().getMessage());
callback.invoke(err);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void getToken(final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
user.getToken(true)
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
try {
if (task.isSuccessful()) {
String token = task.getResult().getToken();
WritableMap resp = Arguments.createMap();
resp.putString("status", "complete");
resp.putString("token", token);
callback.invoke(null, resp);
} else {
WritableMap err = Arguments.createMap();
err.putInt("errorCode", ERROR_FETCHING_TOKEN);
err.putString("errorMessage", task.getException().getMessage());
callback.invoke(err);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void updateUserProfile(ReadableMap props, final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder();
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
if (m.containsKey("displayName")) {
String displayName = (String) m.get("displayName");
profileBuilder.setDisplayName(displayName);
}
if (m.containsKey("photoUri")) {
String photoUriStr = (String) m.get("photoUri");
Uri uri = Uri.parse(photoUriStr);
profileBuilder.setPhotoUri(uri);
}
UserProfileChangeRequest profileUpdates = profileBuilder.build();
user.updateProfile(profileUpdates)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
try {
if (task.isSuccessful()) {
Log.d(TAG, "User profile updated");
userCallback(mAuth.getCurrentUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
@ReactMethod
public void signOut(final Callback callback) {
mAuth.signOut();
WritableMap resp = Arguments.createMap();
resp.putString("status", "complete");
resp.putString("msg", "User signed out");
callback.invoke(null, resp);
}
@ReactMethod
public void reloadUser(final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user == null) {
callbackNoUser(callback, false);
} else {
user.reload()
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "user reloaded");
userCallback(mAuth.getCurrentUser(), callback);
} else {
userErrorCallback(task, callback);
}
}
});
}
}
@ReactMethod
public void getCurrentUser(final Callback callback) {
FirebaseUser user = mAuth.getCurrentUser();
if (user == null) {
callbackNoUser(callback, false);
} else {
Log.d("USRC", user.getUid());
userCallback(user, callback);
}
}
// TODO: Check these things
@ReactMethod
public void googleLogin(String IdToken, final Callback callback) {
AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
try {
if (task.isSuccessful()) {
userCallback(task.getResult().getUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
@ReactMethod
public void facebookLogin(String Token, final Callback callback) {
AuthCredential credential = FacebookAuthProvider.getCredential(Token);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
try {
if (task.isSuccessful()) {
userCallback(task.getResult().getUser(), callback);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
}
// Internal helpers
private void userCallback(final FirebaseUser user, final Callback callback) {
if (user != null) {
user.getToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
try {
if (task.isSuccessful()) {
WritableMap userMap = getUserMap(user);
userMap.putString("token", task.getResult().getToken());
callback.invoke(null, userMap);
} else {
userErrorCallback(task, callback);
}
} catch (Exception ex) {
userExceptionCallback(ex, callback);
}
}
});
} else {
callbackNoUser(callback, true);
}
}
private void userErrorCallback(Task task, final Callback onFail) {
WritableMap error = Arguments.createMap();
error.putString("code", ((FirebaseAuthException) task.getException()).getErrorCode());
error.putString("message", task.getException().getMessage());
onFail.invoke(error);
}
private void userExceptionCallback(Exception ex, final Callback onFail) {
WritableMap error = Arguments.createMap();
error.putInt("code", ex.hashCode());
error.putString("message", ex.getMessage());
onFail.invoke(error);
}
private WritableMap getUserMap(FirebaseUser user) {
WritableMap userMap = Arguments.createMap();
if (user != null) {
final String email = user.getEmail();
final String uid = user.getUid();
final String provider = user.getProviderId();
final String name = user.getDisplayName();
final Boolean verified = user.isEmailVerified();
final Uri photoUrl = user.getPhotoUrl();
userMap.putString("email", email);
userMap.putString("uid", uid);
userMap.putString("providerId", provider);
userMap.putBoolean("emailVerified", verified);
if (name != null) {
userMap.putString("name", name);
}
if (photoUrl != null) {
userMap.putString("photoURL", photoUrl.toString());
}
} else {
userMap.putString("msg", "no user");
}
return userMap;
}
}

View File

@@ -0,0 +1,368 @@
package io.invertase.firebase.database;
import java.util.List;
import java.util.Map;
import android.net.Uri;
import android.util.Log;
import java.util.HashMap;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.google.firebase.database.OnDisconnect;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ServerValue;
import io.invertase.firebase.Utils;
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseDatabase";
private HashMap<String, RNFirebaseDatabaseReference> mDBListeners = new HashMap<String, RNFirebaseDatabaseReference>();
private FirebaseDatabase mFirebaseDatabase;
public RNFirebaseDatabase(ReactApplicationContext reactContext) {
super(reactContext);
mFirebaseDatabase = FirebaseDatabase.getInstance();
}
@Override
public String getName() {
return TAG;
}
// Persistence
@ReactMethod
public void enablePersistence(
final Boolean enable,
final Callback callback) {
try {
mFirebaseDatabase.setPersistenceEnabled(enable);
} catch (Throwable t) {
Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t);
}
WritableMap res = Arguments.createMap();
res.putString("status", "success");
callback.invoke(null, res);
}
@ReactMethod
public void keepSynced(
final String path,
final Boolean enable,
final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
ref.keepSynced(enable);
WritableMap res = Arguments.createMap();
res.putString("status", "success");
res.putString("path", path);
callback.invoke(null, res);
}
// RNFirebaseDatabase
@ReactMethod
public void set(
final String path,
final ReadableMap props,
final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("set", callback, error);
}
};
ref.setValue(m.get("value"), listener);
}
@ReactMethod
public void update(final String path,
final ReadableMap props,
final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("update", callback, error);
}
};
ref.updateChildren(m, listener);
}
@ReactMethod
public void remove(final String path,
final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("remove", callback, error);
}
};
ref.removeValue(listener);
}
@ReactMethod
public void push(final String path,
final ReadableMap props,
final Callback callback) {
Log.d(TAG, "Called push with " + path);
DatabaseReference ref = mFirebaseDatabase.getReference(path);
DatabaseReference newRef = ref.push();
final Uri url = Uri.parse(newRef.toString());
final String newPath = url.getPath();
ReadableMapKeySetIterator iterator = props.keySetIterator();
if (iterator.hasNextKey()) {
Log.d(TAG, "Passed value to push");
// lame way to check if the `props` are empty
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
if (error != null) {
WritableMap err = Arguments.createMap();
err.putInt("errorCode", error.getCode());
err.putString("errorDetails", error.getDetails());
err.putString("description", error.getMessage());
callback.invoke(err);
} else {
WritableMap res = Arguments.createMap();
res.putString("status", "success");
res.putString("ref", newPath);
callback.invoke(null, res);
}
}
};
newRef.setValue(m.get("value"), listener);
} else {
Log.d(TAG, "No value passed to push: " + newPath);
WritableMap res = Arguments.createMap();
res.putString("status", "success");
res.putString("ref", newPath);
callback.invoke(null, res);
}
}
@ReactMethod
public void on(final String path,
final String modifiersString,
final ReadableArray modifiersArray,
final String eventName,
final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
if (eventName.equals("value")) {
ref.addValueEventListener();
} else {
ref.addChildEventListener(eventName);
}
WritableMap resp = Arguments.createMap();
resp.putString("status", "success");
resp.putString("handle", path);
callback.invoke(null, resp);
}
@ReactMethod
public void onOnce(final String path,
final String modifiersString,
final ReadableArray modifiersArray,
final String eventName,
final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
ref.addOnceValueEventListener(callback);
}
/**
* At the time of this writing, off() only gets called when there are no more subscribers to a given path.
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventTypes, so
* it doesn't really matter- just polluting the RN bridge a little more than necessary.
* off() should therefore clean *everything* up
*/
@ReactMethod
public void off(
final String path,
final String modifiersString,
final String eventName,
final Callback callback) {
String key = this.getDBListenerKey(path, modifiersString);
RNFirebaseDatabaseReference r = mDBListeners.get(key);
if (r != null) {
if (eventName == null || "".equals(eventName)) {
r.cleanup();
mDBListeners.remove(key);
} else {
r.removeEventListener(eventName);
if (!r.hasListeners()) {
mDBListeners.remove(key);
}
}
}
Log.d(TAG, "Removed listener " + path + "/" + modifiersString);
WritableMap resp = Arguments.createMap();
resp.putString("handle", path);
resp.putString("status", "success");
resp.putString("modifiersString", modifiersString);
//TODO: Remaining listeners
callback.invoke(null, resp);
}
@ReactMethod
public void onDisconnectSet(final String path, final ReadableMap props, final Callback callback) {
String type = props.getString("type");
DatabaseReference ref = mFirebaseDatabase.getReference(path);
OnDisconnect od = ref.onDisconnect();
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("onDisconnectSet", callback, error);
}
};
switch (type) {
case "object":
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props.getMap("value"));
od.setValue(map, listener);
break;
case "array":
List<Object> list = Utils.recursivelyDeconstructReadableArray(props.getArray("value"));
od.setValue(list, listener);
break;
case "string":
od.setValue(props.getString("value"), listener);
break;
case "number":
od.setValue(props.getDouble("value"), listener);
break;
case "boolean":
od.setValue(props.getBoolean("value"), listener);
break;
case "null":
od.setValue(null, listener);
break;
}
}
@ReactMethod
public void onDisconnectUpdate(final String path, final ReadableMap props, final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
OnDisconnect od = ref.onDisconnect();
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props);
od.updateChildren(map, new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("onDisconnectUpdate", callback, error);
}
});
}
@ReactMethod
public void onDisconnectRemove(final String path, final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
OnDisconnect od = ref.onDisconnect();
od.removeValue(new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("onDisconnectRemove", callback, error);
}
});
}
@ReactMethod
public void onDisconnectCancel(final String path, final Callback callback) {
DatabaseReference ref = mFirebaseDatabase.getReference(path);
OnDisconnect od = ref.onDisconnect();
od.cancel(new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
handleCallback("onDisconnectCancel", callback, error);
}
});
}
@ReactMethod
public void goOnline() {
mFirebaseDatabase.goOnline();
}
@ReactMethod
public void goOffline() {
mFirebaseDatabase.goOffline();
}
private void handleCallback(
final String methodName,
final Callback callback,
final DatabaseError databaseError) {
if (databaseError != null) {
WritableMap err = Arguments.createMap();
err.putInt("errorCode", databaseError.getCode());
err.putString("errorDetails", databaseError.getDetails());
err.putString("description", databaseError.getMessage());
callback.invoke(err);
} else {
WritableMap res = Arguments.createMap();
res.putString("status", "success");
res.putString("method", methodName);
callback.invoke(null, res);
}
}
private RNFirebaseDatabaseReference getDBHandle(final String path,
final ReadableArray modifiersArray,
final String modifiersString) {
String key = this.getDBListenerKey(path, modifiersString);
RNFirebaseDatabaseReference r = mDBListeners.get(key);
if (r == null) {
ReactContext ctx = getReactApplicationContext();
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, path, modifiersArray, modifiersString);
mDBListeners.put(key, r);
}
return r;
}
private String getDBListenerKey(String path, String modifiersString) {
return path + " | " + modifiersString;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
return constants;
}
}

View File

@@ -0,0 +1,296 @@
package io.invertase.firebase.database;
import java.util.HashSet;
import java.util.List;
import android.util.Log;
import java.util.ListIterator;
import java.util.Set;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.google.firebase.database.Query;
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.ValueEventListener;
import io.invertase.firebase.Utils;
public class RNFirebaseDatabaseReference {
private static final String TAG = "RNFirebaseDBReference";
private Query mQuery;
private String mPath;
private String mModifiersString;
private ChildEventListener mEventListener;
private ValueEventListener mValueListener;
private ReactContext mReactContext;
private Set<String> childEventListeners = new HashSet<>();
public RNFirebaseDatabaseReference(final ReactContext context,
final FirebaseDatabase firebaseDatabase,
final String path,
final ReadableArray modifiersArray,
final String modifiersString) {
mReactContext = context;
mPath = path;
mModifiersString = modifiersString;
mQuery = this.buildDatabaseQueryAtPathAndModifiers(firebaseDatabase, path, modifiersArray);
}
public void addChildEventListener(final String eventName) {
if (mEventListener == null) {
mEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
handleDatabaseEvent("child_added", dataSnapshot);
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
handleDatabaseEvent("child_changed", dataSnapshot);
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
handleDatabaseEvent("child_removed", dataSnapshot);
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
handleDatabaseEvent("child_moved", dataSnapshot);
}
@Override
public void onCancelled(DatabaseError error) {
handleDatabaseError(error);
}
};
mQuery.addChildEventListener(mEventListener);
Log.d(TAG, "Added ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
} else {
Log.w(TAG, "Trying to add duplicate ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
}
//Keep track of the events that the JS is interested in knowing about
childEventListeners.add(eventName);
}
public void addValueEventListener() {
if (mValueListener == null) {
mValueListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
handleDatabaseEvent("value", dataSnapshot);
}
@Override
public void onCancelled(DatabaseError error) {
handleDatabaseError(error);
}
};
mQuery.addValueEventListener(mValueListener);
Log.d(TAG, "Added ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
//this.setListeningTo(mPath, modifiersString, "value");
} else {
Log.w(TAG, "Trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
}
}
public void addOnceValueEventListener(final Callback callback) {
final ValueEventListener onceValueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
WritableMap data = Utils.dataSnapshotToMap("value", mPath, mModifiersString, dataSnapshot);
callback.invoke(null, data);
}
@Override
public void onCancelled(DatabaseError error) {
WritableMap err = Arguments.createMap();
err.putInt("errorCode", error.getCode());
err.putString("errorDetails", error.getDetails());
err.putString("description", error.getMessage());
callback.invoke(err);
}
};
mQuery.addListenerForSingleValueEvent(onceValueEventListener);
Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString);
}
public void removeEventListener(String eventName) {
if ("value".equals(eventName)) {
this.removeValueEventListener();
} else {
childEventListeners.remove(eventName);
if (childEventListeners.isEmpty()) {
this.removeChildEventListener();
}
}
}
public boolean hasListeners() {
return mEventListener != null || mValueListener != null;
}
public void cleanup() {
Log.d(TAG, "cleaning up database reference " + this);
childEventListeners.clear();
this.removeChildEventListener();
this.removeValueEventListener();
}
private void removeChildEventListener() {
if (mEventListener != null) {
mQuery.removeEventListener(mEventListener);
mEventListener = null;
}
}
private void removeValueEventListener() {
if (mValueListener != null) {
mQuery.removeEventListener(mValueListener);
mValueListener = null;
}
}
private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) {
WritableMap data = Utils.dataSnapshotToMap(name, mPath, mModifiersString, dataSnapshot);
WritableMap evt = Arguments.createMap();
evt.putString("eventName", name);
evt.putMap("body", data);
Utils.sendEvent(mReactContext, "database_event", evt);
}
private void handleDatabaseError(final DatabaseError error) {
WritableMap err = Arguments.createMap();
err.putString("eventName", "database_error");
err.putString("path", mPath);
err.putString("modifiersString", mModifiersString);
err.putInt("errorCode", error.getCode());
err.putString("errorDetails", error.getDetails());
err.putString("msg", error.getMessage());
WritableMap evt = Arguments.createMap();
evt.putString("eventName", "database_error");
evt.putMap("body", err);
Utils.sendEvent(mReactContext, "database_error", evt);
}
private Query buildDatabaseQueryAtPathAndModifiers(final FirebaseDatabase firebaseDatabase,
final String path,
final ReadableArray modifiers) {
Query query = firebaseDatabase.getReference(path);
List<Object> strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers);
ListIterator<Object> it = strModifiers.listIterator();
while(it.hasNext()) {
String str = (String) it.next();
String[] strArr = str.split(":");
String methStr = strArr[0];
if (methStr.equalsIgnoreCase("orderByKey")) {
query = query.orderByKey();
} else if (methStr.equalsIgnoreCase("orderByValue")) {
query = query.orderByValue();
} else if (methStr.equalsIgnoreCase("orderByPriority")) {
query = query.orderByPriority();
} else if (methStr.contains("orderByChild")) {
String key = strArr[1];
Log.d(TAG, "orderByChild: " + key);
query = query.orderByChild(key);
} else if (methStr.contains("limitToLast")) {
String key = strArr[1];
int limit = Integer.parseInt(key);
Log.d(TAG, "limitToLast: " + limit);
query = query.limitToLast(limit);
} else if (methStr.contains("limitToFirst")) {
String key = strArr[1];
int limit = Integer.parseInt(key);
Log.d(TAG, "limitToFirst: " + limit);
query = query.limitToFirst(limit);
} else if (methStr.contains("equalTo")) {
String value = strArr[1];
String type = strArr[2];
if ("number".equals(type)) {
double doubleValue = Double.parseDouble(value);
if (strArr.length > 3) {
query = query.equalTo(doubleValue, strArr[3]);
} else {
query = query.equalTo(doubleValue);
}
} else if ("boolean".equals(type)) {
boolean booleanValue = Boolean.parseBoolean(value);
if (strArr.length > 3) {
query = query.equalTo(booleanValue, strArr[3] );
} else {
query = query.equalTo(booleanValue);
}
} else {
if (strArr.length > 3) {
query = query.equalTo(value, strArr[3]);
} else {
query = query.equalTo(value);
}
}
} else if (methStr.contains("endAt")) {
String value = strArr[1];
String type = strArr[2];
if ("number".equals(type)) {
double doubleValue = Double.parseDouble(value);
if (strArr.length > 3) {
query = query.endAt(doubleValue, strArr[3]);
} else {
query = query.endAt(doubleValue);
}
} else if ("boolean".equals(type)) {
boolean booleanValue = Boolean.parseBoolean(value);
if (strArr.length > 3) {
query = query.endAt(booleanValue, strArr[3] );
} else {
query = query.endAt(booleanValue);
}
} else {
if (strArr.length > 3) {
query = query.endAt(value, strArr[3]);
} else {
query = query.endAt(value);
}
}
} else if (methStr.contains("startAt")) {
String value = strArr[1];
String type = strArr[2];
if ("number".equals(type)) {
double doubleValue = Double.parseDouble(value);
if (strArr.length > 3) {
query = query.startAt(doubleValue, strArr[3]);
} else {
query = query.startAt(doubleValue);
}
} else if ("boolean".equals(type)) {
boolean booleanValue = Boolean.parseBoolean(value);
if (strArr.length > 3) {
query = query.startAt(booleanValue, strArr[3] );
} else {
query = query.startAt(booleanValue);
}
} else {
if (strArr.length > 3) {
query = query.startAt(value, strArr[3]);
} else {
query = query.startAt(value);
}
}
}
}
return query;
}
}

View File

@@ -0,0 +1,229 @@
package io.invertase.firebase.messaging;
import java.util.Map;
import android.content.Context;
import android.content.IntentFilter;
import android.content.Intent;
import android.content.BroadcastReceiver;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableMap;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.RemoteMessage;
import io.invertase.firebase.Utils;
public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseMessaging";
private static final String EVENT_NAME_TOKEN = "RNFirebaseRefreshToken";
private static final String EVENT_NAME_NOTIFICATION = "RNFirebaseReceiveNotification";
private static final String EVENT_NAME_SEND = "RNFirebaseUpstreamSend";
public static final String INTENT_NAME_TOKEN = "io.invertase.firebase.refreshToken";
public static final String INTENT_NAME_NOTIFICATION = "io.invertase.firebase.ReceiveNotification";
public static final String INTENT_NAME_SEND = "io.invertase.firebase.Upstream";
private IntentFilter mRefreshTokenIntentFilter;
private IntentFilter mReceiveNotificationIntentFilter;
private IntentFilter mReceiveSendIntentFilter;
private BroadcastReceiver mBroadcastReceiver;
public RNFirebaseMessaging(ReactApplicationContext reactContext) {
super(reactContext);
mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN);
mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION);
mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND);
initRefreshTokenHandler();
initMessageHandler();
initSendHandler();
Log.d(TAG, "New instance");
}
@Override
public String getName() {
return TAG;
}
private void initMessageHandler() {
Log.d(TAG, "RNFirebase initMessageHandler called");
if (mBroadcastReceiver == null) {
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
RemoteMessage remoteMessage = intent.getParcelableExtra("data");
Log.d(TAG, "Firebase onReceive: " + remoteMessage);
WritableMap params = Arguments.createMap();
params.putNull("data");
params.putNull("notification");
params.putString("id", remoteMessage.getMessageId());
params.putString("messageId", remoteMessage.getMessageId());
if (remoteMessage.getData().size() != 0) {
WritableMap dataMap = Arguments.createMap();
Map<String, String> data = remoteMessage.getData();
for (String key : data.keySet()) {
dataMap.putString(key, data.get(key));
}
params.putMap("data", dataMap);
}
if (remoteMessage.getNotification() != null) {
WritableMap notificationMap = Arguments.createMap();
RemoteMessage.Notification notification = remoteMessage.getNotification();
notificationMap.putString("title", notification.getTitle());
notificationMap.putString("body", notification.getBody());
notificationMap.putString("icon", notification.getIcon());
notificationMap.putString("sound", notification.getSound());
notificationMap.putString("tag", notification.getTag());
params.putMap("notification", notificationMap);
}
ReactContext ctx = getReactApplicationContext();
Utils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params);
}
};
}
getReactApplicationContext().registerReceiver(mBroadcastReceiver, mReceiveNotificationIntentFilter);
}
/**
*
*/
private void initRefreshTokenHandler() {
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
WritableMap params = Arguments.createMap();
params.putString("token", intent.getStringExtra("token"));
ReactContext ctx = getReactApplicationContext();
Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN);
Utils.sendEvent(ctx, EVENT_NAME_TOKEN, params);
}
;
}, mRefreshTokenIntentFilter);
}
@ReactMethod
public void subscribeToTopic(String topic, final Callback callback) {
try {
FirebaseMessaging.getInstance().subscribeToTopic(topic);
callback.invoke(null, topic);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "Firebase token: " + e);
WritableMap error = Arguments.createMap();
error.putString("message", e.getMessage());
callback.invoke(error);
}
}
@ReactMethod
public void getToken(final Callback callback) {
try {
String token = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Firebase token: " + token);
callback.invoke(null, token);
} catch (Exception e) {
WritableMap error = Arguments.createMap();
error.putString("message", e.getMessage());
callback.invoke(error);
}
}
@ReactMethod
public void unsubscribeFromTopic(String topic, final Callback callback) {
try {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
callback.invoke(null, topic);
} catch (Exception e) {
WritableMap error = Arguments.createMap();
error.putString("message", e.getMessage());
callback.invoke(error);
}
}
// String senderId, String messageId, String messageType,
@ReactMethod
public void send(ReadableMap params, final Callback callback) {
ReadableMap data = params.getMap("data");
FirebaseMessaging fm = FirebaseMessaging.getInstance();
RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(params.getString("sender"));
remoteMessage.setMessageId(params.getString("id"));
remoteMessage.setMessageType(params.getString("type"));
if (params.hasKey("ttl")) {
remoteMessage.setTtl(params.getInt("ttl"));
}
if (params.hasKey("collapseKey")) {
remoteMessage.setCollapseKey(params.getString("collapseKey"));
}
ReadableMapKeySetIterator iterator = data.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
ReadableType type = data.getType(key);
if (type == ReadableType.String) {
remoteMessage.addData(key, data.getString(key));
}
}
try {
fm.send(remoteMessage.build());
WritableMap res = Arguments.createMap();
res.putString("status", "success");
Log.d(TAG, "send: Message sent");
callback.invoke(null, res);
} catch (Exception e) {
Log.e(TAG, "send: error sending message", e);
WritableMap error = Arguments.createMap();
error.putString("code", e.toString());
error.putString("message", e.toString());
callback.invoke(error);
}
}
private void initSendHandler() {
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
WritableMap params = Arguments.createMap();
if (intent.getBooleanExtra("hasError", false)) {
WritableMap error = Arguments.createMap();
error.putInt("code", intent.getIntExtra("errCode", 0));
error.putString("message", intent.getStringExtra("errorMessage"));
params.putMap("err", error);
} else {
params.putNull("err");
}
ReactContext ctx = getReactApplicationContext();
Utils.sendEvent(ctx, EVENT_NAME_SEND, params);
}
}, mReceiveSendIntentFilter);
}
}

View File

@@ -0,0 +1,484 @@
package io.invertase.firebase.storage;
import android.util.Log;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.HashMap;
import android.net.Uri;
import android.database.Cursor;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.storage.StorageException;
import com.google.firebase.storage.StorageTask;
import com.google.firebase.storage.StreamDownloadTask;
import com.google.firebase.storage.UploadTask;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageMetadata;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.OnPausedListener;
import com.google.firebase.storage.OnProgressListener;
import io.invertase.firebase.Utils;
@SuppressWarnings("WeakerAccess")
public class RNFirebaseStorage extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseStorage";
private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH";
private static final String ExternalDirectoryPath = "EXTERNAL_DIRECTORY_PATH";
private static final String ExternalStorageDirectoryPath = "EXTERNAL_STORAGE_DIRECTORY_PATH";
private static final String PicturesDirectoryPath = "PICTURES_DIRECTORY_PATH";
private static final String TemporaryDirectoryPath = "TEMPORARY_DIRECTORY_PATH";
private static final String CachesDirectoryPath = "CACHES_DIRECTORY_PATH";
private static final String DocumentDirectory = "DOCUMENT_DIRECTORY_PATH";
private static final String FileTypeRegular = "FILETYPE_REGULAR";
private static final String FileTypeDirectory = "FILETYPE_DIRECTORY";
private static final String STORAGE_EVENT = "storage_event";
private static final String STORAGE_ERROR = "storage_error";
private static final String STORAGE_STATE_CHANGED = "state_changed";
private static final String STORAGE_UPLOAD_SUCCESS = "upload_success";
private static final String STORAGE_UPLOAD_FAILURE = "upload_failure";
private static final String STORAGE_DOWNLOAD_SUCCESS = "download_success";
private static final String STORAGE_DOWNLOAD_FAILURE = "download_failure";
private ReactContext mReactContext;
public RNFirebaseStorage(ReactApplicationContext reactContext) {
super(reactContext);
Log.d(TAG, "New instance");
}
@Override
public String getName() {
return TAG;
}
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
@ReactMethod
public void delete(final String path,
final Callback callback) {
StorageReference reference = this.getReference(path);
reference.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
WritableMap data = Arguments.createMap();
data.putString("success", "success");
data.putString("path", path);
callback.invoke(null, data);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception exception) {
callback.invoke(makeErrorPayload(1, exception));
}
});
}
@ReactMethod
public void getDownloadURL(final String path,
final Callback callback) {
Log.d(TAG, "Download url for remote path: " + path);
final StorageReference reference = this.getReference(path);
Task<Uri> downloadTask = reference.getDownloadUrl();
downloadTask
.addOnSuccessListener(new OnSuccessListener<Uri>() {
@Override
public void onSuccess(Uri uri) {
callback.invoke(null, uri.toString());
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
callback.invoke(makeErrorPayload(1, exception));
}
});
}
@ReactMethod
public void getMetadata(final String path,
final Callback callback) {
StorageReference reference = this.getReference(path);
reference.getMetadata().addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
@Override
public void onSuccess(StorageMetadata storageMetadata) {
WritableMap data = getMetadataAsMap(storageMetadata);
callback.invoke(null, data);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception exception) {
callback.invoke(makeErrorPayload(1, exception));
}
});
}
@ReactMethod
public void updateMetadata(final String path,
final ReadableMap metadata,
final Callback callback) {
StorageReference reference = this.getReference(path);
StorageMetadata md = buildMetadataFromMap(metadata);
reference.updateMetadata(md).addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
@Override
public void onSuccess(StorageMetadata storageMetadata) {
WritableMap data = getMetadataAsMap(storageMetadata);
callback.invoke(null, data);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception exception) {
callback.invoke(makeErrorPayload(1, exception));
}
});
}
@ReactMethod
public void downloadFile(final String path,
final String localPath,
final Callback callback) {
if (!isExternalStorageWritable()) {
Log.w(TAG, "downloadFile failed: external storage not writable");
WritableMap error = Arguments.createMap();
final int errorCode = 1;
error.putDouble("code", errorCode);
error.putString("description", "downloadFile failed: external storage not writable");
callback.invoke(error);
return;
}
Log.d(TAG, "downloadFile from remote path: " + path);
StorageReference reference = this.getReference(path);
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);
fileWithJustPath.mkdirs();
File fileWithFullPath = new File(pathMinusFileName, filename);
FileOutputStream output = new FileOutputStream(fileWithFullPath);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
output.close();
}
}).addOnProgressListener(new OnProgressListener<StreamDownloadTask.TaskSnapshot>() {
@Override
public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "Got download progress " + taskSnapshot);
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
}
}).addOnPausedListener(new OnPausedListener<StreamDownloadTask.TaskSnapshot>() {
@Override
public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "Download is paused " + taskSnapshot);
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
}
}).addOnSuccessListener(new OnSuccessListener<StreamDownloadTask.TaskSnapshot>() {
@Override
public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "Successfully downloaded file " + taskSnapshot);
WritableMap resp = getDownloadTaskAsMap(taskSnapshot);
handleStorageEvent(STORAGE_DOWNLOAD_SUCCESS, path, resp);
//TODO: A little hacky, but otherwise throws a not consumed exception
resp = getDownloadTaskAsMap(taskSnapshot);
callback.invoke(null, resp);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
Log.e(TAG, "Failed to download file " + exception.getMessage());
//TODO: JS Error event
callback.invoke(makeErrorPayload(1, exception));
}
});
}
@ReactMethod
public void putFile(final String path, final String localPath, final ReadableMap metadata, final Callback callback) {
StorageReference reference = this.getReference(path);
Log.i(TAG, "Upload file: " + localPath + " to " + path);
try {
Uri file;
if (localPath.startsWith("content://")) {
String realPath = getRealPathFromURI(localPath);
file = Uri.fromFile(new File(realPath));
} else {
file = Uri.fromFile(new File(localPath));
}
StorageMetadata md = buildMetadataFromMap(metadata);
UploadTask uploadTask = reference.putFile(file, md);
// register observers to listen for when the download is done or if it fails
uploadTask
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
// handle unsuccessful uploads
Log.e(TAG, "Failed to upload file " + exception.getMessage());
//TODO: JS Error event
callback.invoke(makeErrorPayload(1, exception));
}
})
.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
@Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "Successfully uploaded file " + taskSnapshot);
WritableMap resp = getUploadTaskAsMap(taskSnapshot);
handleStorageEvent(STORAGE_UPLOAD_SUCCESS, path, resp);
//TODO: A little hacky, but otherwise throws a not consumed exception
resp = getUploadTaskAsMap(taskSnapshot);
callback.invoke(null, resp);
}
})
.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
@Override
public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "Got upload progress " + taskSnapshot);
WritableMap event = getUploadTaskAsMap(taskSnapshot);
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
}
})
.addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
@Override
public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "Upload is paused " + taskSnapshot);
WritableMap event = getUploadTaskAsMap(taskSnapshot);
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
}
});
} catch (Exception ex) {
final int errorCode = 2;
callback.invoke(makeErrorPayload(errorCode, ex));
}
}
//Firebase.Storage methods
@ReactMethod
public void setMaxDownloadRetryTime(final double milliseconds) {
FirebaseStorage.getInstance().setMaxDownloadRetryTimeMillis((long)milliseconds);
}
@ReactMethod
public void setMaxOperationRetryTime(final double milliseconds) {
FirebaseStorage.getInstance().setMaxOperationRetryTimeMillis((long)milliseconds);
}
@ReactMethod
public void setMaxUploadRetryTime(final double milliseconds) {
FirebaseStorage.getInstance().setMaxUploadRetryTimeMillis((long)milliseconds);
}
private StorageReference getReference(String path) {
if (path.startsWith("url::")) {
String url = path.substring(5);
return FirebaseStorage.getInstance().getReferenceFromUrl(url);
} else {
return FirebaseStorage.getInstance().getReference(path);
}
}
private StorageMetadata buildMetadataFromMap(ReadableMap metadata) {
StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder();
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(metadata);
for (Map.Entry<String, Object> entry : m.entrySet()) {
metadataBuilder.setCustomMetadata(entry.getKey(), entry.getValue().toString());
}
return metadataBuilder.build();
}
private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) {
WritableMap metadata = Arguments.createMap();
metadata.putString("bucket", storageMetadata.getBucket());
metadata.putString("generation", storageMetadata.getGeneration());
metadata.putString("metageneration", storageMetadata.getMetadataGeneration());
metadata.putString("fullPath", storageMetadata.getPath());
metadata.putString("name", storageMetadata.getName());
metadata.putDouble("size", storageMetadata.getSizeBytes());
metadata.putDouble("timeCreated", storageMetadata.getCreationTimeMillis());
metadata.putDouble("updated", storageMetadata.getUpdatedTimeMillis());
metadata.putString("md5hash", storageMetadata.getMd5Hash());
metadata.putString("cacheControl", storageMetadata.getCacheControl());
metadata.putString("contentDisposition", storageMetadata.getContentDisposition());
metadata.putString("contentEncoding", storageMetadata.getContentEncoding());
metadata.putString("contentLanguage", storageMetadata.getContentLanguage());
metadata.putString("contentType", storageMetadata.getContentType());
WritableArray downloadURLs = Arguments.createArray();
for (Uri uri : storageMetadata.getDownloadUrls()) {
downloadURLs.pushString(uri.getPath());
}
metadata.putArray("downloadURLs", downloadURLs);
WritableMap customMetadata = Arguments.createMap();
for (String key : storageMetadata.getCustomMetadataKeys()) {
customMetadata.putString(key, storageMetadata.getCustomMetadata(key));
}
metadata.putMap("customMetadata", customMetadata);
return metadata;
}
private String getRealPathFromURI(final String uri) {
Cursor cursor = null;
try {
String[] proj = {MediaStore.Images.Media.DATA};
cursor = getReactApplicationContext().getContentResolver().query(Uri.parse(uri), proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private WritableMap getDownloadTaskAsMap(final StreamDownloadTask.TaskSnapshot taskSnapshot) {
WritableMap resp = Arguments.createMap();
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
resp.putString("ref", taskSnapshot.getStorage().getPath());
resp.putString("state", this.getTaskStatus(taskSnapshot.getTask()));
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
return resp;
}
private WritableMap getUploadTaskAsMap(final UploadTask.TaskSnapshot taskSnapshot) {
StorageMetadata d = taskSnapshot.getMetadata();
WritableMap resp = Arguments.createMap();
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
resp.putString("downloadUrl", taskSnapshot.getDownloadUrl() != null ? taskSnapshot.getDownloadUrl().toString() : null);
resp.putString("ref", taskSnapshot.getStorage().getPath());
resp.putString("state", this.getTaskStatus(taskSnapshot.getTask()));
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
if (taskSnapshot.getMetadata() != null) {
WritableMap metadata = getMetadataAsMap(taskSnapshot.getMetadata());
resp.putMap("metadata", metadata);
}
return resp;
}
private String getTaskStatus(StorageTask<?> task) {
if (task.isInProgress()) {
return "RUNNING";
} else if (task.isPaused()) {
return "PAUSED";
} else if (task.isSuccessful() || task.isComplete()) {
return "SUCCESS";
} else if (task.isCanceled()) {
return "CANCELLED";
} else if (task.getException() != null) {
return "ERROR";
} else {
return "UNKNOWN";
}
}
private void handleStorageEvent(final String name, final String path, WritableMap body) {
WritableMap evt = Arguments.createMap();
evt.putString("eventName", name);
evt.putString("path", path);
evt.putMap("body", body);
Utils.sendEvent(this.getReactApplicationContext(), STORAGE_EVENT, evt);
}
private void handleStorageError(final String path, final StorageException error) {
WritableMap body = Arguments.createMap();
body.putString("path", path);
body.putString("message", error.getMessage());
WritableMap evt = Arguments.createMap();
evt.putString("eventName", STORAGE_ERROR);
evt.putMap("body", body);
Utils.sendEvent(this.getReactApplicationContext(), STORAGE_ERROR, evt);
}
private WritableMap makeErrorPayload(double code, Exception ex) {
WritableMap error = Arguments.createMap();
error.putDouble("code", code);
error.putString("message", ex.getMessage());
return error;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DocumentDirectory, 0);
constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath());
constants.put(TemporaryDirectoryPath, null);
constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath());
constants.put(FileTypeRegular, 0);
constants.put(FileTypeDirectory, 1);
File externalStorageDirectory = Environment.getExternalStorageDirectory();
if (externalStorageDirectory != null) {
constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath());
} else {
constants.put(ExternalStorageDirectoryPath, null);
}
File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null);
if (externalDirectory != null) {
constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath());
} else {
constants.put(ExternalDirectoryPath, null);
}
return constants;
}
}