Flutter async programming - asynchronous

After using a dataset I want to check how many datasets are unused. If I'm above the threshold I want to fetch new data.
useQuestion(Question question) async {
print("using question $question");
question.used=1;
final db = await database;
int count = await db.rawUpdate(
'UPDATE Question SET used = ? WHERE question = ?',
[question.used,question.question]);
print(question);
print("Made $count changes");
var questions = await _checkQuestionThreshold();
print(questions);
for (var q in questions) {
newQuestion(Question.fromJson(q));
}
}
Check Threshold
_checkQuestionThreshold() async {
print("count questions...");
final db = await database;
var res = await db.query("Question");
int count = Sqflite.firstIntValue(
await db.rawQuery('SELECT COUNT(*) FROM Question'));
int countUsed = Sqflite.firstIntValue(
await db.rawQuery('SELECT COUNT(*) FROM Question where used="1"'));
int i = 0;
if (count < 1 || (countUsed / count) < 0.5) {
print("Okay... we fetch new...");
return await _fetchFromFirebase();
}
Fetching from DB:
_fetchFromFirebase() async {
var questionJson;
databaseReference.once().then((DataSnapshot snapshot) async {
questionJson = await snapshot.value;
}).catchError((e) {
print(e);
});
return questionJson;
}
However I get the following error when calling for (var q in questions) {
newQuestion(Question.fromJson(q));
} and I'm wondering what exactly I am missing.
I/flutter ( 5150): count questions...
I/flutter ( 5150): Okay... we fetch new...
I/flutter ( 5150): null
E/flutter ( 5150): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: NoSuchMethodError: The getter 'iterator' was called on null.
E/flutter ( 5150): Receiver: null

Your issue is that questions is null, so trying to iterate over it is going to throw an error.
Looking at your code, the root of the error seems to come from your _fetchFromFirebase method. In this case, you call databaseReference.once(), and in the then part you assign the result to questionJson. However, you never await on this call, so the _fetchFromFirebase method ends up returning the value of questionJson immediately after the call is made without waiting for it to finish. At that point, questionJson will be null, so that's what gets returned.
In general, I advise to not mix the Future.then.catchError pattern with the async/await pattern, as it can result in confusing logic that hides what is actually happening. As such, I'd recommend sticking just to async/await like so:
_fetchFromFirebase() async {
try {
final snapshot = await databaseReference.once();
final questionJson = await snapshot.value;
return questionJson;
} catch (e) {
print(e);
return null;
}
}

Related

How to make your code stop executing next line until a function finishes its work in flutter?

I have a cloud storage where i store the data by uploading it from my app. And I also have a cloud firestore database where i store the file names and urls of those uploaded files. The problem which I am facing is my code is trying to collect the url before the datas/files are uploaded, therefore it crashes. So I want my code to stop there finish the task and go to next lines. I even used async/await in my code but still it doesn't help.
Here is my code:-
Future savingfile(List<int> asset, String name) async {
StorageReference reference = FirebaseStorage.instance.ref().child(name);
StorageUploadTask uploadTask = reference.putData(asset);
String url = await (await uploadTask.onComplete).ref.getDownloadURL();
//print(url);
//documentFileUpload(url);
return url;
}
getfilelink(String s) async {
StorageReference ref = FirebaseStorage.instance.ref().child('$s');
String url = (await ref.getDownloadURL()).toString();
return url;
}
saveeverything(List filechosen) async {
for (int i = 0; i < filechosen.length; i++) {
await savingfile(filechosen[i].readAsBytesSync(), filenames[i]);
}
}
void updatedata() async {
..... //some more codes are here.
await saveeverything(filechosen); //i want my code to wait and finish the task over here and go to next line.
String fileurl = "", filedetails = "";
if (filenames.length == 0) {
filedetails = "none";
fileurl = "none";
} else {
for (int i = 0; i < filenames.length; i++) {
filedetails += filenames[i].toString() + " ";
}
for (int i = 0; i < filenames.length; i++) {
String t = getfilelink(filenames[i]) + " ";
fileurl += t;
}
}
await notices.add({
"event": notice,
"date": date,
"description": detail,
"index": ds.docs.length + 1,
"files": filedetails,
"url": fileurl
});
}
That's my code, I hope you understand it.
Here's the error which I am getting
E/flutter ( 4499): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: NoSuchMethodError: Class 'Future<dynamic>' has no instance method '+'.
E/flutter ( 4499): Receiver: Instance of 'Future<dynamic>'
E/flutter ( 4499): Tried calling: +(" ")
Thank you!
your updatedata function must be of type Future<void> (not sure if this works or if to has to be Future<bool> to return some result.
In your stateful/less class you finally wait for the future to come, so something like
Widget build(BuildContext context) {
return FutureBuilder(
future: _yourFuture,
builder: (context, snapshot) {

Read list of items from firebase database flutter

I'm trying to build a list of items from this database:
but i'm getting this error:
_TypeError (type 'List<dynamic>' is not a subtype of type 'List<DataSnapshot>')
i can get the item values just using snap2.value but i need to get the element key because it's my item id, to build my list.
import 'package:firebase_database/firebase_database.dart';
final DatabaseReference _refdata =
FirebaseDatabase.instance.reference().child('host');
getItems() async {
String _refOnline;
await _refdata.child("ref").once().then((value) => _refOnline = value.value);
if (dataRef != _refOnline) {
await _refdata.child("values/valores").once().then((DataSnapshot snap2) {
List<DataSnapshot> result = snap2.value;
lista = result
.map((element) => Item(
element.key,
element.value["nome"],
element.value["preco"].toDouble(),
element.value["precoantes"] ?? "",
element.value["tipo"],
element.value["disponivel"],
element.value["descricao"],
element.value["calorias"]))
.toList();
});
edit:
with this change im able to return values:
List<Map<dynamic, dynamic>> result =
List<Map<dynamic, dynamic>>.from(snap2.value);
result.forEach((element) {
if (element != null) {
print(element);
}
});
but i cant return the keys (1,2,3,4,5)
and it's the same as doing this(suposing query ordered by keys):
List<dynamic> result = snap2.value;
int _i = 1;
result.forEach((value) {
if (value != null) {
lista.add(Item(
_i.toString(),
value["nome"],
value["preco"].toDouble(),
value["precoantes"] ?? "",
value["tipo"],
value["disponivel"],
value["descricao"],
value["calorias"]));
_i += 1;
print(value["nome"]);
print(value);
print(lista.length);
}
and now im getting this error:
NoSuchMethodError (NoSuchMethodError: The method 'add' was called on null.
Receiver: null
Tried calling: add(Instance of 'Item'))
There is no way in FlutterFire to get the child nodes as a list of DataSnapshot objects.
The closest I got was:
currentRoundListener = dbRoot.child('rounds/$currentRoundKey').once.then((snapshot) {
currentRound = List<String>.from(snapshot.value as List<dynamic>);
});
You could give this a try with:
List<DataSnapshot> result = List<DataSnashot>.from(snap2.value as List<dynamic>);
But more likely the values under the snapshot will only be available as a map:
Map<String, dynamic> result = Map<String, dynamic>.from(snap2.value as Map<dynamic, dynamic>);
If you need to maintain the order of the child nodes, have a look here: Flutter: Firebase Real-Time database orderByChild has no impact on query result and here: Flutter Firebase Database wrong timestamp order
I've managed to solve it using this implementation:
getItems() async {
String _refOnline;
List<dynamic> result;
await _refdata.child("ref").once().then((value) => _refOnline = value.value);
if (dataRef != _refOnline) {
await _refdata.child("values/valores").once().then((DataSnapshot snap2) {
result = snap2.value;
int _i = 1;
result.forEach((value) {
if (value != null) {
Item _a = Item(
_i.toString(),
value["nome"],
value["preco"].toDouble(),
value["precoantes"] ?? "",
value["tipo"],
value["disponivel"],
value["descricao"],
value["calorias"]);
lista.add(_a);
_i += 1;
}
});
savepref("ref_key", _refOnline);
});
} else {
lista = [oi, oi2, oi3, oi4, oi5, oi6, oi7, oi8, oi9, oi10, oi11, oi12];
//readIt();
}
}
there was a problem on lista definition blocking it to receive new items in lista.add function.
I've now defined lista as:
List<Item> lista = [];
And with the above function, everything now works.
thx Frank van Puffelen !

async-await does not run well on Flutter

I updated sqlite database in using code,(this method working correctly)
updateSubmitRequest(int id, String status) async {
var db = await db1;
await db.rawUpdate('''
UPDATE submitRequestTable
SET status = ?
WHERE id = ?
''', [status, id]);
}
update.dart
I updated status like this,
print('before update ${submitRequest[i].status}');
await HelperDatabase1()
.updateSubmitRequest(submitRequest[i].id, 'Pending');
print('after update ${submitRequest[i].status}');
display
List submitRequest = (await HelperDatabase1().displaySubmitRequest());
method
displaySubmitRequest() async {
var db = await db1;
final List<Map<String, dynamic>> maps =
await db.query('submitRequestTable');
return List.generate(maps.length, (i) {
return SubmitRequestModel(
id: maps[i]['id'],
equipmentId: maps[i]['equipmentId'],
woDescriptionId: maps[i]['woDescriptionId'],
details: maps[i]['details'],
priorityId: maps[i]['priorityId'],
workTypeId: maps[i]['workTypeId'],
sourceId: maps[i]['sourceId'],
fileName: maps[i]['fileName'],
filepath: maps[i]['filepath'],
isOffline: maps[i]['isOffline'],
systemDate: maps[i]['systemDate'],
username: maps[i]['username'],
subItemId: maps[i]['subItemId'],
status: maps[i]['status']);
});
}
in my LogCat output like this,
I/flutter (15908): before update success
I/flutter (15908): after update success
Why doesn't it change to Pending?
My update method runs correctly.

awaiting on an async function does not execute correctly in dart

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

Flutter Return Length of Documents from Firebase

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);
}
}

Resources