In the following code I can't find a solution to await for the inner asynchronous code. The result is that the data is always returned empty. Any ideas?
Future<Map> resolveLinks(links) async {
var data = {};
(links as Map).forEach((k, v) async {
switch (v["type"]) {
case "address":
data[k] = await getAddress(v["value"]);
break;
case "person":
data[k] = await getPerson(v["value"], v["subtype"]);
break;
}
});
return data;
}
The reason why this won't work is because you inner function are returning Future objects you are never awaiting.
It should work if you modify your code to the following:
Future<Map> resolveLinks(links) async {
var data = {};
await Future.wait((links as Map).entries.map((entry) async {
final k = entry.key;
final v = entry.value;
switch (v["type"]) {
case "address":
data[k] = await getAddress(v["value"]);
break;
case "person":
data[k] = await getPerson(v["value"], v["subtype"]);
break;
}
}));
return data;
}
So instead of using forEach, I am using the map method on the entries iterable to map each entry into a Future object. When I takes all this Future objects and puts them into Future.wait which returns a Future there completes when all the other Future objects has returned its value.
Related
This function always returning null but it is loading data but it is not waiting for Firestore reading. How to solve this issue?
Future<HomePage> read({ String pageName,String mAppId})async{
await Firestore.instance
.collection('tablePages')
.where('projectId', isEqualTo: mAppId)
.where("page.title", isEqualTo:pageName)
.snapshots()
.listen((data) async{
if (data.documents != null
? data.documents.length > 0
? data.documents[0].data != null
: false
: false) {
return HomePage.fromJson(
data.documents[0].data['page']);
} else {
return null;
}
//break;
}).onError((error){
print(error);
return null;
});
}
}
Try the following:
Future<HomePage> read({String pageName, String mAppId}) async {
Stream<QuerySnapshot> snap = Firestore.instance
.collection('tablePages')
.where('projectId', isEqualTo: mAppId)
.where("page.title", isEqualTo: pageName)
.snapshots();
await for (var data in snap) {
if (data.documents != null
? data.documents.length > 0 ? data.documents[0].data != null : false
: false) {
return HomePage.fromJson(data.documents[0].data['page']);
} else {
return null;
}
}
}
From the docs:
Streams can be created in many ways, which is a topic for another article, but they can all be used in the same way: the asynchronous for loop (commonly just called await for) iterates over the events of a stream like the for loop iterates over an Iterable. For example:
Future<int> sumStream(Stream<int> stream) async {
var sum = 0;
await for (var value in stream) {
sum += value;
}
return sum;
}
This code simply receives each event of a stream of integer events, adds them up, and returns (a future of) the sum. When the loop body ends, the function is paused until the next event arrives or the stream is done.
https://dart.dev/tutorials/language/streams
I am rather new to Firebase/Firestore/Cloud functions and been trying a little project where a client app calls a Firebase Cloud Function to generate some random keys (random numbers), adds them to Firestore, and when successfully written, returns these keys to the client app. Kinda like a random number generator.
The function is called correctly by the client (according to Firebase Console), does generate the keys, checks if they exist in the Firestore, and if not adds them. All works up to the part where it should return the result to the client. Here the client never gets the result (the keys array). In fact, the callback in the client app (iOS/Swift) is never called.
I am suspecting the problem lies in the promise being returned? According to the Firebase documentation here, async callables should return a Promise although I am not entirely sure what I am doing is correct https://firebase.google.com/docs/functions/callable
Here is the code for the cloud function:
export const generateRandomKeys = functions.https.onCall(async (data, context) => {
// Read data passed from client
const numberOfKeys = data.numberOfKeys
console.log("Number of keys to generate: ", numberOfKeys)
// Generate some keys
const generatedKeys = KeyMaker.newKeys(numberOfKeys)
try {
const randomkeys = []
// Write keys to DB
for (const key of generatedKeys) {
const addedKey = await writeKeyToDB(key)
randomkeys.push(addedKey)
}
return Promise.resolve(JSON.stringify(randomkeys))
} catch (error) {
console.log("Error has occured: ", error)
throw new Error("An Error has occured: " + error)
}
})
async function writeKeyToDB(key: string){
try {
// Check if a document with the same key already exists in the DB
const docRef = db.collection("randomKeys").doc(key)
const doc = await docRef.get()
// Document with same key found!
if (doc.exists) {
// Generate a new key and try again
const newKey = KeyMaker.newKey()
console.log("Will generate a new key and try again!. New key: ", newKey)
await writeKeyToDB(newKey)
}
const keyDoc = {
somefield: somevalue,
}
// Write to DB then return result
await docRef.set(keyDoc)
return Promise.resolve(key)
} catch (error) {
return Promise.reject(error)
}
}
Client (Swift)
public static func generateNewRandomNumbers(numberOfKeys: Int) {
FirebaseApp.configure()
let functions = Functions.functions(region: FIRESTORE_REGION)
functions.httpsCallable("generateRandomKeys").call(["numberOfKeys": numberOfKeys]) { (result, error) in
// Nothing here executes
print("----------got reply---------")
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
print("Error \(String(describing: code)): " + message)
}
}
if let keys = (result?.data as? [String]) {
dump(keys)
}
}
}
Dont combine Async/Await and Promise. Async functions as itself returning Promise.
First change return of your cloud function to :
return JSON.stringify(randomkeys);
Also in writeKeyToDb change return to:
return key;
and catch part to:
throw Error(error);
I see also problem that in cloud function you calling your writeKeyToDb function with 2 parameters, but that function have only one. But that code probably is in progress
Finally found the issue, thanks for Doug and Dominik for guiding me in the right direction. I removed the promises and returned directly the values but more importantly, I didn't need to convert the array to JSON. I came across HTTPSCallableResult documentation
I simply changed this
return JSON.stringify(randomkeys);
to
return randomkeys
and on the client, instead of
if let keys = (result?.data as? [String]) {
dump(keys)
}
I do
if let keys = (result?.data as? NSArray) {
dump(keys)
}
I have a few async functions in my dart program which interact with SQLite database. I use await expression to invoke those functions and mostly the functions are executed when they're awaited on but one function does not execute and the calling function continues execution without awaiting on the called function. Here's the code:
Future<int> addShoppingList(String listName) async {
var dbClient = await db;
String now = new DateTime.now().toString();
await dbClient.transaction((txn) async {
int res = await txn.rawInsert("insert into lists(list_name,list_created_at) values(\'$listName\',\'$now\')");
print('result of adding a new shopping list: $res');
return res;
});
List<Map> resList = await dbClient.rawQuery("select list_id from lists where list_name=\'$listName\'");
if (resList.length > 0) {
return resList[0]['list_id'];
}
return 0;
//await dbClient.rawInsert("insert into lists(list_name,list_created_at) values(\'$listName\',\'$now\')");
}
Future<int> addShoppingListItems(int listId, Map<String,String> listItems) async {
var dbClient = await db;
int res = 0;
listItems.forEach((itemName, quantity) async{
int itemId = await getItemId(itemName);
print('adding item $itemName with id $itemId');
await dbClient.transaction((txn) async {
res = await txn.rawInsert("insert into list_items values($listId,$itemId,\'$quantity\')");
print('result of adding item in list_items: $res');
});
return res;
});
return 0;
}
Future<int> addItemsToShoppingList(String listName, Map<String,String> listItems) async {
int listId = await getListId(listName);
if (listId == 0) {
listId = await addShoppingList(listName);
print('got list id of $listId after adding new list');
}
print('in additemstoshoppinglist list id: $listId');
print('in additemstoshoppinglist ${listItems.toString()}');
int res = await addShoppingListItems(listId, listItems);
print('result after adding item in addItemsToShoppingList: $res');
return res;
}
In my external class I await on addItemsToShoppingList function to start the whole chain of functions. When I run the code, I see that the functions getListId(), addShoppingList() are awaited correctly(the execution of addItemsToShoppingList does not proceed until the awaited functions are executed) but when I await on addShoppingListItems, the calling function's execution does not wait until addShoppingListItems returns. What am I missing here and how can I make sure addShoppingListItems returns before the execution of calling function can proceed?
I figured out that the problem was with the way async functions are awaited upon in the for each loop of the map being used. I solved this problem with the solution given here: Map forEach function exits with an await inside
This is my function
Future<Address> readAddress() async {
database = FirebaseDatabase(app: app);
await database
.reference()
.child(table_name)
.child(uid)
.once()
.then((DataSnapshot snapshot) {
print("address_start");
if (snapshot.value == null) {
print("address_start_value_null");
return null;
} else {
print("address_start_value_not_null");
print(snapshot.value);
Address a = Address().map(snapshot.value);
return a;
// return a;
}
}).catchError((onError) {
print(onError);
return onError;
});
}
This is my function call
readAddress().then((address) {
if (address != null) {
print("address read seucssfully " + address.firstname);
} else {
print(
"address read faield result is null $address"); // + address.toString());
}
}).catchError((onError) {
print("error on read address");
});
But here always it returns null.
What is wrong here?
message from readAddress() function
[dart] This function has a return type of 'Future', but
doesn't end with a return statement. [missing_return]
I don't know to explain more StackOverflow showing this error message when in try to post this question "t looks like your post is mostly code; please add some more details.
"
The problem with your function is that it's not returning a Future, but the Address object instead. I would rewrite your function like this to just return the Address object
Future<Address> readAddress() async {
try{
database = FirebaseDatabase(app: app);
DataSnapshot snapshot = await database
.reference()
.child(table_name)
.child(uid)
.once();
return Address().map(snapshot.value);
}catch(e) {
print(e);
return(e);
}
}
With this, your function call can be just this:
Address address = readAddress();
Simple, isn't it? I have taken care all of the error handling inside the function.
Im trying to return the length of a list of documents with this function:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
respectsQuery.getDocuments().then((data) {
var totalEquals = data.documents.length;
return totalEquals;
});
}
I'm initialize this in the void init state (with another function call:
void initState() {
totalLikes(postID).then((result) {
setState(() {
_totalRespects = result;
});
});
}
However, when this runs, it initially returns a null value since it doesn't have time to to fully complete. I have tried to out an "await" before the Firestore call within the Future function but get the compile error of "Await only futures."
Can anyone help me understand how I can wait for this function to fully return a non-null value before setting the state of "_totalRespsects"?
Thanks!
I think you're looking for this:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
var querySnapshot = await respectsQuery.getDocuments();
var totalEquals = querySnapshot.documents.length;
return totalEquals;
}
Note that this loads all documents, just to determine the number of documents, which is incredibly wasteful (especially as you get more documents). Consider keeping a document where you maintain the count as a field, so that you only have to read a single document to get the count. See aggregation queries and distributed counters in the Firestore documentation.
Perfect code for your problem:
int? total;
getLength() async {
var getDocuments = await DatabaseHelper.registerUserCollection
.where("register", isEqualTo: "yes")
.get();
setState(() {
total = getDocuments.docs.length;
});
}
#override
void initState() {
super.initState();
getLength();
if (kDebugMode) {
print(total);
}
}