calling firebase cloud function in flutter - firebase

I've sent a function to firebase through typescript and don't know how to access it in my flutter app. The code needs to send the uid of the firebase user (the user will always already be logged in, so this isn't an issue) but then I also need to write into the function through message parameter, as is shown in my typescript code below. Again, I am unsure how to do this in Flutter.
This is my typescript code:
import * as functions from 'firebase-functions';
export const testFunction = functions.https.onCall( async (data, context) => {
const uid = context.auth && context.auth.uid;
const message = data.message;
return `${uid} sent a message of ${message}`
});
Here is my Flutter code:
import 'package:cloud_functions/cloud_functions.dart';
Future<void> getFunction() async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('testFunction', options: HttpsCallableOptions(timeout: Duration(seconds: 5)));
final results = await callable();
print('${results.data}');
}
#override
void initState() {
super.initState();
getFunction();
}

As far as I can see in this documentation you can just pass the parameters into the call(...). Did you give that a try?

Related

Firebase Function Unable to Find userId and tweetId

I am using Firebase functions for Firestore database. I am trying to update a field based on the new tweet being added.
Here is my Firebase Function on production:
const admin = require('firebase-admin')
admin.initializeApp()
const db = admin.firestore()
const functions = require("firebase-functions");
functions.logger.log("START OF FUNCTION");
exports.myFunction = functions.firestore
.document('timelines/{userId}/tweets/{tweetId}')
.onCreate((change, context) => {
const userId = context.params.userId
const tweetId = context.params.tweetId
functions.logger.log(context.params.userId);
functions.logger.log(context.params.tweetId);
db.doc(`/timelines/${userId}/tweets/${tweetId}`).update({likeCount: 200})
})
I am triggering it through an iPhone app. I am logged in to my account and I add a new Tweet. The Firebase function does get called but userId and tweetId are undefined. I am not sure why they are undefined. Any ideas?
Without knowing your client-side logic it's difficult to know if there are other issues. I would suggest adding some error handling to narrow down the cause. You could also try pulling it from the data response instead of context (assuming the schema matches).
Also note using 'snap' instead of 'change' as change is generally reserved for 'onWrite' and 'onUpdate' hooks.
exports.myFunction = functions.firestore
.document('timelines/{userId}/tweets/{tweetId}')
.onCreate(async (snap, context) => {
try {
const { userId, tweetId } = snap.data();
functions.logger.log(userId);
functions.logger.log(tweetId);
return await db.doc(`/timelines/${userId}/tweets/${tweetId}`).update({ likeCount: 200 });
}
catch (error) {
functions.logger.log(error);
}
});

FIREBASE FATAL ERROR: Database initialized multiple times

I have multiple database instances in my firebase app. I am trying to write into three database instances in firebase cloud functions. My understanding by following this document is no need to initialize multiple apps for each database instance. We can initialize one and pass in the database url. As a side note, I have another function with similar kind of functionality where I have trigger event in one database and write data to other database instance and it works fine.
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
const app = admin.app();
export const onStart =
functions.database.instance('my-db-1')
.ref('path')
.onCreate(async (snapshot, context) => {
return await onCreate('my-db-1',snapshot,context);
});
export const onStartDb01 = functions.database.instance('my-db-2')
.ref('path')
.onCreate(async (snapshot, context) => {
return await onCreate('my-db-2', snapshot, context);
});
async function onCreate(dbInstance: string, snapshot:
functions.database.DataSnapshot, context: functions.EventContext):
Promise<any> {
const defaultDb = app.database(defaultDbUrl);
const actvDb = app.database(actvDbUrl);
await defaultDb.ref('path')
.once("value")
.then(snap => {
const val = snap.val();
---do something and write back---
});
await actvDb.ref('path')
.once("value")
.then(snap => {
const val = snap.val();
---do something and write back---
});
return true;
}
But when a db event is fired, it logs the error as below
Error: FIREBASE FATAL ERROR: Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.
You'll need to initialize a separate app() for each database instance.
Based on Doug's answer here that should be something like this:
const app1 = admin.initializeApp(functions.config().firebase)
const app2 = admin.initializeApp(functions.config().firebase)
And then:
const defaultDb = app1.database(defaultDbUrl);
const actvDb = app2.database(actvDbUrl);

Getting "firebase.functions(app)" arg expects a FirebaseApp instance or undefined in React-Native Firebase

I am triggering an httpscallable function in GoogleCloud but receiving this error back which I could not find anywhere in documentation what is it:
"firebase.functions(app)" arg expects a FirebaseApp instance or undefined.
Ensure the arg provided is a Firebase app instance; or no args to use
the default Firebase app.
Here is my code in RN app:
import { firebase } from '#react-native-firebase/functions';
...
try {
await firebase.functions('europe-west1').httpsCallable('createUserTest')();
}
catch (httpsError) {
console.log(httpsError.message);
}
And my Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUserTest = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
const callerUid = context.auth.uid;
const callerUserRecord = await admin.auth().getUser(callerUid);
return { result: callerUserRecord.customClaims };
} catch (error) {
return { error: error };
}
});
I am using this function for testing purposes just to see if I can receive back the current user custom claims or not, however, its returning that error.
It looks like you're not using the provided client API correctly. I suggest reviewing the documentation, especially example 3. You'll want to do this instead:
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
await functionsForRegion.httpsCallable("createUserTest")()

Why can I call firestore from a firebase function using onRequest, but not when using onCall

I have two firebase functions deployed, one uses functions.https.onRequest (companyRequest) and one uses functions.https.onCall (companyCall). Both do the exact same thing: retrieve a document from firestore (the exact same document from the exact same collection).
Here are the functions:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp(functions.config().firebase);
export const companyRequest = functions.https.onRequest((request, response) => {
const companyId = "VpOVFo82m9ls3aIK7dra";
admin
.firestore()
.collection("company")
.doc(companyId)
.get()
.then(result => {
response.send(result.data());
})
.catch(error => {
response.send(error);
});
});
export const companyCall = functions.https.onCall((data, context) => {
if (context && context.auth) {
console.log(
"AUTH",
context.auth.uid,
context.auth.token.email
);
}
const companyId = "VpOVFo82m9ls3aIK7dra";
admin
.firestore()
.collection("company")
.doc(companyId)
.get()
.then(result => {
return result.data();
})
.catch(error => {
return error;
});
});
I call companyRequest with curl and it works:
> curl https://us-central1-xxxxx.cloudfunctions.net/company
{"name":"Duny Comp."}
I call companyCall from flutter and it fails (on firebase, server site):
Future click() async {
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'companyCall',
);
HttpsCallableResult resp = await callable.call(<String, dynamic>{
'companyId': 'VpOVFo82m9ls3aIK7dra',
});
print(resp.data);
}
the error I get for companyCall is this:
AUTH 3gvfgYJpk2gIgUpkOLxdkdId uuuu#xxxx.com
ERROR: Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information
The error seems quite clear, but why does the unauthenticated call with curl work, but companyCall with firebase authentication via flutter does have a permission problems? In the output you can even see the auth information from the enduser in the flutter app, so he is authenticated.
The question is, why there is a difference between the two? The proposed solutions like Error: Could not load the default credentials (Firebase function to firestore) also feel very strange...
Update:
This is not the same question as in Firebase Cloud Functions: Difference between onRequest and onCall, in this question I ask why there is a difference in the security behaves different between the two different methods. Why do I need to authenticate with an admin account to access the same collection from onCall as I don't need it when access the collection from a onRequest method?

How can I access the Firebase Flutter plugin from a test?

I want to run the real Firebase (not a mock) during a Flutter test. I'm trying to authenticate Firebase with FirebaseOptions:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import "package:test/test.dart";
Future<void> main() async {
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
googleAppID: 'xxxxxxx',
projectID: 'yyyyyy',
),
);
final Firestore firestore = Firestore(app: app);
await firestore.settings(timestampsInSnapshotsEnabled: true);
test('Testing access.', () async {
final FirebaseAuth _auth = FirebaseAuth.instance;
FirebaseUser user = await _auth.signInAnonymously();
firestore.collection('aaaaa').document('bbbbb').get().then((documentSnaphot) {
expect(documentSnaphot['xxxx'], 'ccccc');
});
});
}
However, I'm getting the following error:
Failed to load "C:\Users\Ed\testing\app\test\user_test.dart":
MissingPluginException(No implementation found for method
FirebaseApp#appNamed on channel plugins.flutter.io/firebase_core)
package:flutter/src/services/platform_channel.dart 278:7
MethodChannel.invokeMethod
===== asynchronous gap ===========================
c: 38:53
FirebaseApp.appNamed
===== asynchronous gap ===========================
c: 64:55
FirebaseApp.configure
===== asynchronous gap ===========================
test\usuario_test.dart 7:45
main
===== asynchronous gap ===========================
package:test
serializeSuite
..\..\..\AppData\Local\Temp\flutter_test_listener.64a30b51-eb69-11e8-
a427-1831bf4c06e8\listener.dart 19:27 main
How can I solve this?
Plugins run only on mobile devices (or emulators).
To make testing code that uses plugins possible, you can register your own handlers that respond to method channel requests like the native side of plugins would
test('gets greeting from platform', () async {
const channel = MethodChannel('foo');
channel.setMockMethodCallHandler((MethodCall call) async {
if (call.method == 'bar')
return 'Hello, ${call.arguments}';
throw MissingPluginException();
});
expect(await hello('world'), 'Platform says: Hello, world');
});
From the last section of https://medium.com/flutter-io/flutter-platform-channels-ce7f540a104e
This will check the current firebase user null or not. Still I'm writing the tests. Will update a test for signInAnonymously if possible.
test('API current Firebase user', () async {
MethodChannel channel = MethodChannel(
'plugins.flutter.io/firebase_auth',
);
channel.setMockMethodCallHandler((MethodCall call) async {
if (call.method == 'currentUser') {
return ;
}
;
throw MissingPluginException();
});
FirebaseUser user = await FirebaseAuth.instance.currentUser();
expect(user, null);
});

Resources