diff --git a/android/build.gradle b/android/build.gradle index 033ba411..bb5b03cc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -102,6 +102,7 @@ dependencies { compileOnly "com.google.firebase:firebase-ads:$firebaseVersion" compileOnly "com.google.firebase:firebase-firestore:$firebaseVersion" compileOnly "com.google.firebase:firebase-invites:$firebaseVersion" + compileOnly "com.google.firebase:firebase-functions:$firebaseVersion" compileOnly('com.crashlytics.sdk.android:crashlytics:2.9.1@aar') { transitive = true } diff --git a/android/src/main/java/io/invertase/firebase/Utils.java b/android/src/main/java/io/invertase/firebase/Utils.java index efc829e7..5921d146 100644 --- a/android/src/main/java/io/invertase/firebase/Utils.java +++ b/android/src/main/java/io/invertase/firebase/Utils.java @@ -21,7 +21,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMapKeySetIterator; - @SuppressWarnings("WeakerAccess") public class Utils { private static final String TAG = "Utils"; @@ -64,6 +63,10 @@ public class Utils { Long longVal = (Long) value; map.putDouble(key, (double) longVal); break; + case "java.lang.Float": + float floatVal = (float) value; + map.putDouble(key, (double) floatVal); + break; case "java.lang.Double": map.putDouble(key, (Double) value); break; @@ -71,14 +74,12 @@ public class Utils { map.putString(key, (String) value); break; default: - map.putString(key, null); + map.putNull(key); } } - /** - * * @param map * @return */ diff --git a/android/src/main/java/io/invertase/firebase/functions/RNFirebaseFunctions.java b/android/src/main/java/io/invertase/firebase/functions/RNFirebaseFunctions.java new file mode 100644 index 00000000..45507474 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/functions/RNFirebaseFunctions.java @@ -0,0 +1,130 @@ +package io.invertase.firebase.functions; + +import android.support.annotation.NonNull; +import android.util.Log; +import android.support.annotation.Nullable; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; + +import com.facebook.react.bridge.ReadableNativeArray; +import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.functions.FirebaseFunctions; +import com.google.firebase.functions.FirebaseFunctionsException; +import com.google.firebase.functions.HttpsCallableReference; +import com.google.firebase.functions.HttpsCallableResult; + +import java.util.List; +import java.util.Map; + +import io.invertase.firebase.Utils; + +public class RNFirebaseFunctions extends ReactContextBaseJavaModule { + + private static final String TAG = "RNFirebaseFunctions"; + + public RNFirebaseFunctions(ReactApplicationContext reactContext) { + super(reactContext); + Log.d(TAG, "New instance"); + } + + /** + * @return + */ + @Override + public String getName() { + return TAG; + } + + @ReactMethod + public void httpsCallable(final String name, @Nullable final Object data, final Promise promise) { + Object input; + if (data == null + || data instanceof String + || data instanceof Boolean + || data instanceof Integer + || data instanceof Long + || data instanceof Float) { + input = data; + } else if (data instanceof ReadableArray) { + input = ((ReadableArray) data).toArrayList(); + } else if (data instanceof ReadableMap) { + input = ((ReadableMap) data).toHashMap(); + } else { + input = null; + } + + HttpsCallableReference httpsCallableReference = FirebaseFunctions + .getInstance() + .getHttpsCallable(name); + + httpsCallableReference + .call(input) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(HttpsCallableResult httpsCallableResult) { + Log.d(TAG, "function:call:onSuccess:" + name); + + WritableMap map = Arguments.createMap(); + Object result = httpsCallableResult.getData(); + if (result == null + || result instanceof String + || result instanceof Boolean + || result instanceof Integer + || result instanceof Long + || result instanceof Float) { + Utils.mapPutValue("data", result, map); + } else if (result instanceof List) { + map.putArray("data", Arguments.makeNativeArray((List) result)); + } else if (result instanceof Map) { + map.putMap("data", Arguments.makeNativeMap((Map) result)); + } else { + // TODO check for other instance types e.g. ArrayList ? + map.putNull("data"); + } + + promise.resolve(map); + + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.d(TAG, "function:call:onFailure:" + name, exception); + if (exception instanceof FirebaseFunctionsException) { + FirebaseFunctionsException ffe = (FirebaseFunctionsException) exception; + /* + OK(0), + CANCELLED(1), + UNKNOWN(2), + INVALID_ARGUMENT(3), + DEADLINE_EXCEEDED(4), + NOT_FOUND(5), + ALREADY_EXISTS(6), + PERMISSION_DENIED(7), + RESOURCE_EXHAUSTED(8), + FAILED_PRECONDITION(9), + ABORTED(10), + OUT_OF_RANGE(11), + UNIMPLEMENTED(12), + INTERNAL(13), + UNAVAILABLE(14), + DATA_LOSS(15), + UNAUTHENTICATED(16); + */ + FirebaseFunctionsException.Code code = ffe.getCode(); + String message = ffe.getMessage(); + Object details = ffe.getDetails(); + // TODO promise resolve so we can send details + } + } + }); + } +} diff --git a/android/src/main/java/io/invertase/firebase/functions/RNFirebaseFunctionsPackage.java b/android/src/main/java/io/invertase/firebase/functions/RNFirebaseFunctionsPackage.java new file mode 100644 index 00000000..769abdb0 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/functions/RNFirebaseFunctionsPackage.java @@ -0,0 +1,37 @@ +package io.invertase.firebase.functions; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.bridge.ReactApplicationContext; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +@SuppressWarnings("unused") +public class RNFirebaseFunctionsPackage implements ReactPackage { + public RNFirebaseFunctionsPackage() { + } + + /** + * @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 createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new RNFirebaseFunctions(reactContext)); + return modules; + } + + /** + * @param reactContext + * @return a list of view managers that should be registered with {@link UIManagerModule} + */ + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/lib/modules/functions/index.js b/lib/modules/functions/index.js index 8a7e780a..f1868e54 100644 --- a/lib/modules/functions/index.js +++ b/lib/modules/functions/index.js @@ -10,7 +10,11 @@ import type App from '../core/app'; export const MODULE_NAME = 'RNFirebaseFunctions'; export const NAMESPACE = 'functions'; -type HttpsCallable = (data?: any) => Promise; +type HttpsCallableResult = { + data: Object, +}; + +type HttpsCallable = (data?: any) => Promise; export default class Analytics extends ModuleBase { constructor(app: App) { diff --git a/lib/types/index.js b/lib/types/index.js index 3257b6ac..f59f7ca6 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -15,6 +15,8 @@ import type Database from '../modules/database'; import { typeof statics as DatabaseStatics } from '../modules/database'; import type Firestore from '../modules/firestore'; import { typeof statics as FirestoreStatics } from '../modules/firestore'; +import type Functions from '../modules/functions'; +import { typeof statics as FunctionsStatics } from '../modules/functions'; import type InstanceId from '../modules/iid'; import { typeof statics as InstanceIdStatics } from '../modules/iid'; import type Invites from '../modules/invites'; @@ -63,6 +65,7 @@ export type FirebaseModuleName = | 'RNFirebaseCrashlytics' | 'RNFirebaseDatabase' | 'RNFirebaseFirestore' + | 'RNFirebaseFunctions' | 'RNFirebaseInstanceId' | 'RNFirebaseInvites' | 'RNFirebaseLinks' @@ -171,6 +174,13 @@ export type FirestoreModule = { nativeModuleExists: boolean, } & FirestoreStatics; +/* Functions types */ + +export type FunctionsModule = { + (): Functions, + nativeModuleExists: boolean, +} & FunctionsStatics; + /* InstanceId types */ export type InstanceIdModule = {