task.result?.data returning null in HTTP Cloud Function call - firebase

I'm calling a Cloud Function from my app with the following code:
private fun search(g: String, l: GeoPoint): Task<String> {
val data = hashMapOf(
"uid" to user.uid,
"first_name" to user.first_name,
"age" to user.age,
"gender" to user.gender,
"bio" to user.bio,
"img1" to user.img1,
"img2" to user.img2,
"latitude" to l.latitude.toString(),
"longitude" to l.longitude.toString(),
"g" to g,
"fcmToken" to fcmToken,
"pronoun" to if (user.gender == "male") "him" else "her"
)
log("data: $data") // successfully logs all values as non-null
return functions
.getHttpsCallable("searchNearby")
.call(data)
.continueWith { task ->
log("result: ${task.result?.data}")
// This continuation runs on either success or failure, but if the task
// has failed then result will throw an Exception which will be
// propagated down.
val result = task.result?.data as String
result
}
.addOnSuccessListener { result ->
log("search(): success $result")
}
.addOnFailureListener { exception ->
log("search(): exception $exception") // exception kotlin.TypeCastException: null cannot be cast to non-null type kotlin.String
log("search(): localMsg ${exception.localizedMessage}") // localMsg null cannot be cast to non-null type kotlin.String
log("search(): cause ${exception.cause}") // cause null
log("search(): msg ${exception.message}") // msg null cannot be cast to non-null type kotlin.String
}
}
Why is task.result?.data returning null if every item in data is non-null?
EDIT
.addOnFailureListener() now returns another weird error: HashMap cannot be cast to java.lang.String.
Here is my cloud function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const geofirestore = require('geofirestore');
const { GeoCollectionReference, GeoFirestore, GeoQuery, GeoQuerySnapshot } = require('geofirestore');
const user = {};
exports.searchNearby = functions.https.onCall((data, context) => {
return admin.firestore().collection('blocks').add({
user1name: "1142",
user1bio: data.bio,
user1img1: data.img1,
user1img2: data.img2,
user1fcmToken: data.fcmToken,
user1pronoun: data.pronoun
}).then(result => {
console.log("Successfully created document");
return { result: "Successfully created document" };
}).catch(error => {
console.log("Error creating document " + error);
return { errorCreatingDocument: error };
});
});
The document is successfully created, and { result: "Successfully created document" } successfully gets sent back to the client, but it's still firing an exception java.util.HashMap cannot be cast to java.lang.String. Any idea what the problem is? It's also especially hard to debug because it doesn't tell you what like in my Cloud Functions the error is.

You are returning a JavaScript object to the client from the callable function:
return { result: "Successfully created document" };
The object contains a single property named result. Since your function returns an object, your client needs to assume the result from the callable is a Map. The Map will have a single entry with key also named result. This should be pretty easy to verify if you just log the value of result here:
val result = task.result?.data
Instead of casting this to a String, cast it to Map<String, *>, and fetch the entry named result out of it:
val data = task.result?.data as Map<String, *>
val result = data["result"]
Or, you can change your function to return a string instead of a JavaScript object:
return "Successfully created document";

Related

TypeScript Generics: Create a Generic Error Handler Function

I am having an error handler which gets two parameters, error which is of type unknown. It can be an object, a string, etc. and then return value which is of generic type. It can be an empty string, empty array or an empty object.
Here's how my function looks:
const genericErrorHandler = <T>(error: any, returnValue: T): T => {
console.log('Error = ', error);
return returnValue;
};
But I am not sure about calling this function. Here's how I expect it to behave:
genericErrorHandler({statusCode:404},"Not Found") should return "Not Found"
genericErrorHandler("Internal Server Error", {}) should return {}
genericErrorHandler({message:"Invalid TOken"},[]) should return []
with their respective logging. How do I call this function for above 3 cases and do I need to modify my actual function too?

How can I read the value of a field in Firestore (Swift)

I want to read out the Value of an Field of my document (in Firebase Firestore with SwiftUI).
What I already have I this:
let value = myDataBase
// My Database instance
//
value.collection("My Collection").whereField("Code", isEqualTo: codeTextInput)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
(This Code works fine)
And now I want to store the Value of all Documents, which are in my collection and have for the key "Code" the value, which is typed in. But I want to store the Data for the key "Wert"
When I've saved it, I want to use it as an User-Default...
Btw. I don’t want collect more then 1 item with this code, I just want that this item which I collect is the right.
Let sum it up:
You want all documents in your collection with a certain value to be fetched
You want to save all of these values and be able to access them.
I can only recommend working with objects in this scenario. Let's make an example:
Lets import all modules
import Foundation
import Firebase
import FirebaseFirestoreSwift
import FirebaseStorage
import Combine
First we declare the structure:
https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects
public struct MyObject: Codable {
let id: String
let code: String?
// Needed to identify them in Firestore
enum CodingKeys: String, CodingKey {
case id
case code = "code"
}
}
Now we access it and generate an Object for each document we can fetch that contains your desired value:
https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
var myArray: Array<MyObject> = [] // Empty array where we will store all objects in
var codeTextInput = "Test"
// Fetch only desired documents
let db = Firestore.firestore()
let docRef = db.collection("My Collection").whereField("Code", isEqualTo: codeTextInput)
func getDocumentsAsObjects() { // docRef.getDocuments Needs to be in function or else: Expressions are not allowed at the top level
docRef.getDocuments { (querySnapshot, err) in //getDocuments (s) as in multiple
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents { // iterate them and add them to your array
let result = Result {
try document.data(as: MyObject.self)
}
switch result {
case .success(let myObject):
if let myObject = myObject {
myObject.id = document!.documentID // Get the ID of the Document as we might need it later
myArray.append(myObject) // Save the document into your array
} else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
print("Document does not exist")
}
case .failure(let error):
// A `MyObject` value could not be initialized from the DocumentSnapshot.
print("Error decoding city: \(error)")
}
}
}
}
}
Now you have your Objects in your array and can access them

Flutter - Dart : wait a forEach ends

I try to modify a string using data in each node I retrieve from Firebase database, and then to write a file with the modifed string (called "content").
Here is what I tried :
// Retrieve initial content from Firebase storage
var data = await FirebaseStorage.instance.ref().child("...").getData(1048576);
var content = new String.fromCharCodes(data);
// Edit content with each node from Firebase database
final response = await FirebaseDatabase.instance.reference().child('...').once();
response.value.forEach((jsonString) async {
...
// cacheManager.getFile returns a Future<File>
cacheManager.getFile(signedurl).then((file){
// Modify content
content=content.replaceAll('test',file.path);
});
});
// Finally write the file with content
print("test");
final localfile = File('index.html');
await localfile.writeAsString(content);
Result :
"test" is shown before the forEach ends.
I found that we can do in Dart (https://groups.google.com/a/dartlang.org/forum/#!topic/misc/GHm2cKUxUDU) :
await Future.forEach
but in my case if I do : await Future.response.value.forEach (sounds a bit weird)
then I get :
Getter not found: 'response'.
await Future.response.value.forEach((jsonString) async {
How to wait that forEach ends (with "content" modified) before to write the file with new content?
Any idea?
If you use for(... in ) instead of forEach you can use async/await
Future someMethod() async {
...
for(final jsonString in response.value) {
...
// cacheManager.getFile returns a Future<File>
cacheManager.getFile(signedurl).then((file){
// Modify content
content=content.replaceAll('test',file.path);
});
});
}
With forEach the calls fire away for each jsonString immediately and inside it await works, but forEach has return type void and that can't be awaited, only a Future can.
You defined the callback for forEach as async, which means that it runs asynchronously. In other words: the code inside of that callback runs independently of the code outside of the callback. That is exactly why print("test"); runs before the code inside of the callback.
The simplest solution is to move all code that needs information from within the callback into the callback. But there might also be a way to await all of the asynchronous callbacks, similar to how you already await the once call above it.
Update I got working what I think you want to do. With this JSON:
{
"data" : {
"key1" : {
"created" : "20181221T072221",
"name" : "I am key1"
},
"key2" : {
"created" : "20181221T072258",
"name" : "I am key 2"
},
"key3" : {
"created" : "20181221T072310",
"name" : "I am key 3"
}
},
"index" : {
"key1" : true,
"key3" : true
}
}
I can read the index, and then join the data with:
final ref = FirebaseDatabase.instance.reference().child("/53886373");
final index = await ref.child("index").once();
List<Future<DataSnapshot>> futures = [];
index.value.entries.forEach((json) async {
print(json);
futures.add(ref.child("data").child(json.key).once());
});
Future.wait(futures).then((List<DataSnapshot> responses) {
responses.forEach((snapshot) {
print(snapshot.value["name"]);
});
});
Have you tried:
File file = await cacheManager.getFile(signedurl);
content = content.replaceAll('test', file.path);
instead of:
cacheManager.getFile(signedurl).then((file){ ...});
EDIT:
Here's a fuller example trying to replicate what you have. I use a for loop instead of the forEach() method:
void main () async {
List<String> str = await getFuture();
print(str);
var foo;
for (var s in str) {
var b = await Future(() => s);
foo = b;
}
print('finish $foo');
}
getFuture() => Future(() => ['1', '2']);

Sinon stub - Cannot destructure property 'x' of 'undefined' or 'null'

I need to stub the following with Sinon :
const { clientId, dateString } = await parameterParser.prepareInputParameters(con, req);
I have tried using the following:
const retData = {
clientId: 872,
dateString: '1970-01-01',
};
sandbox.stub(parameterParser, 'prepareInputParameters').withArgs(con, req).returns(retData);
but I get the error:
TypeError: Cannot destructure property 'clientId' of 'undefined' or 'null'.
I have successfully stubbed the following elsewhere in my tests:
const { retData } = await sqlFileReader.read('./src/database/sql/getClientIdFromLoanId.sql', [`${req.query.loan_id}`], con, req.logger);
by using:
const retData = {
rowsCount: 1,
rows: [{ client_id: 872 }],
};
sandbox.stub(sqlFileReader, 'read').returns({ retData });
but I cannot get my head round how to stub const { clientId, dateString }
You need to be using resolves instead of returns for these stubs, since you are awaiting their return values, which should actually be Promises that resolve with your retData, and not the retData itself.
In sinon, resolves is a convenience for asynchronous methods. The following two lines are similar:
sinon.stub(foo, 'bar').returns(Promise.resolve('baz'));
sinon.stub(foo, 'bar').resolves('baz');
Your second sample may not be throwing an error, but if you log the value of retData after this line:
const { retData } = await sqlFileReader.read('./src/database/sql/getClientIdFromLoanId.sql', [`${req.query.loan_id}`], con, req.logger);
You'll notice that it is undefined. This is because await causes the result of the method call to be a Promise, which does not have a property called retData.
As for why your first sample is behaving differently, I'm not sure. I suspect that there's something else going on that isn't evident from your samples. Would you mind sharing more code?

Firebase Function Get Single Value From Database

I want to get a single value from Firebase Database within Firebase function. However, the Promise never returns and the chained method never executes. Here is the method that fetches a value from the database
function getFcmToken(username){
return admin.database().ref('tokens/{username}/fcmToken').once('value').then(snap => {
if(snap.exists()){
const token = Object.keys(snap.val());
console.log("FCM Token", token);
return token;
}
return [];
});
}
The above method was supposed to return a token, but I am not sure it is, so the method below does not get executed.
function sendNotification(keyword, username){
return getFcmToken(username).then(token => {
if(token.length > 0){
//Notification Detail
let payload = {
data:{
keyword: keyword
}
};
return admin.messaging().sendToDevice(token, payload);
}
});
}
In the console log, all I see is Promise pending.
How can I update the code above to return a single value, it appears it is returning an array?
Thanks
Your database ref path is wrong. You might wanted to replace username in path, but single quoted won't do that.
Firebase is listening on tokens/{username}/fcmToken, which doesn't exists. Hence on value event will not be triggered and so downline callback will not be executed.
You can use Template literals for building dynamic strings.
Try ref path as
`tokens/${username}/fcmToken`
Code:
function getFcmToken(username){
return admin.database().ref(`tokens/${username}/fcmToken`).once(...)
}

Resources