Future returns list before being populated - flutter

I have the following method:
Future<List<Job>> getUserJobs() async {
Query query = _firebaseDatabase
.reference()
.child("jobs")
.child(_firebaseAuth.currentUser.uid)
.orderByKey();
List<Job> userJobs = [];
if (query == null) {
return userJobs;
}
query.onValue.listen((event) {
Map<dynamic, dynamic> values = event.snapshot.value;
values.forEach((key, value) {
userJobs.add(Job.fromJson(key, Map.from(value)));
});
});
return userJobs;
}
I want to get this response in another class, however, the list returned by the above method is always []. I checked and the userJobs list is indeed populated but the return statement is executed before.
The structure of the database is:
Job collection has user IDs and for each user ID I have several job keys (each with its job data).

Try this:
Future<List<Job>> getUserJobs() async {
List<Job> userJobs = [];
// Query query =
await _firebaseDatabase
.reference()
.child("jobs")
.child(_firebaseAuth.currentUser.uid)
.once()
.orderByKey().then((result) async {
if (result.value != null) {
result.value.forEach((key, childSnapshot) {
userJobs.add(Job.fromJson(key, Map.from(childSnapshot)));
});
} else {
print(
'getUserJobs() no jobs found');
}
}).catchError((e) {
print(
'getUserJobs() error: $e');
});
// if (query == null) {
// return userJobs;
// }
// query.onValue.listen((event) {
// Map<dynamic, dynamic> values = event.snapshot.value;
// values.forEach((key, value) {
// userJobs.add(Job.fromJson(key, Map.from(value)));
// });
// });
return userJobs;
}
your loop also needs to be async..otherwise the method will return before the loop finishes, returning the empty List.. been there and got quite frustrated by this..
also always use .catchError callback.. it tells you what's going wrong ;)

Related

How to pass a `where` clause to a function in Dart?

There is a function, getItems, and I would like to be able to have multiple where to modify the resulting list. I am new to Dart and cannot find the syntax for passing in a where.
I tried creating functions with custom where to call getItems, but cannot due to the async nature of getItems.
Future<List<IioMenuItem>> getItems() async {
// ...
final db = await openDatabase(path, readOnly: true);
final List<Map<String, dynamic>> maps = await db.query('menu_items');
final dbFilteredItems = maps.map((item) => IioMenuItem(
// assign values code removed
)).where((element) { // <-- make 'where' replaceable
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return List.generate(dbFilteredItems.length, (i) {
return dbFilteredItems[i];
});
}
The failed attempt
Future<List<IioMenuItem>> menuItems(FilterState filterState) async {
final dbFilteredItems = getItems().where((element) { // The method 'where' isn't defined for the type 'Future'.
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return List.generate(dbFilteredItems.length, (i) {
return dbFilteredItems[i];
});
}
Can I please get help?
The term you're looking for is a "closure" or "first class function".
See Functions as first-class objects on the Language guide.
"A where" isn't a thing. It's not a noun. Iterable.where is just the name of a function, and that function happens to take a function as a parameter, and uses it to determine what things to keep.
In this specific case, you want a function that takes a IioMenuItem, and returns a boolean that determins where or not to keep it. The type of that is a bool Function(IioMenuItem) (see Function).
I called it "predicate":
Future<List<IioMenuItem>> menuItems(
FilterState filterState,
bool Function(IioMenuItem) predicate // <- Take it in as a parameter
) async {
return (await getItems())
.where(predicate) // <- pass it along as an argument to `where`
.toList(growable: false);
}
You can pass any test inside a where.
filterItems(Map<String,dynamic> element) {
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}
final dbFilteredItems = maps.map((item) => IioMenuItem(
// assign values code removed
)).where(filterItems).toList(growable: false);
Use then in future
getItems().then((value) => value.where((element ...
Use await to call async functions.
Future<List<IioMenuItem>> menuItems(FilterState filterState) async {
final dbFilteredItems = (await getItems()).where((element) { // await has to be used here.
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return dbFilteredItems;
}

How do I ensure that the list is populated before checking what it contains?

In the code snippet below, how do I ensure that the senderID list gets populated before checking what the list contains?
I'm pulling data from firestore, storing it in a list, and then checking if it contains a particular key.
Stream<List<Group>> getChatGroups(String grpId) {
return firestore.collection('groups').snapshots().map((event) {
List<Group> groups = [];
List<String> senderIds = [];
for (var document in event.docs) {
var group = Group.fromMap(document.data());
firestore.collection('groups').doc(group.groupId).collection('chats').get().then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot doc) {
var messageData = doc.data() as Map<String, dynamic>;
var messages = Message.fromMap(messageData);
var grId = doc.reference.parent.parent?.id;
//The values in SenderIds should be set before the function below is initiaited
senderIds.add(messages.senderId);
});
});
//This if function should initiate after getting set above
if (senderIds.contains(auth.currentUser!.uid)) {
groups.add(group);
}
}
return groups;
});
}
If you want senderIds.contains to be called only after all of your Futures have completed, build a List of those Futures and use Future.wait on that List. Something like:
var futures = <Future<void>>[];
for (var document in event.docs) {
// ...
futures.add(
firestore
.collection('groups')
.doc(group.groupId)
.collection('chats')
.get()
.then((QuerySnapshot snapshot) {
// ...
}),
);
}
await Future.wait(futures);
if (senderIds.contains(auth.currentUser!.uid)) {
// ...
}
Note that since the above code is asynchronous, you should also be using asyncMap instead of map.

How to pass a List or specific looped list to firebase doc in flutter

I am trying to achieve a task in which I have a List<dynamic>and its giving me multiple values on its indexes e.g. ['Me','Admin', so on....] something like this.
I cannot pass the List directly to Document ID it gives index error and I don't if it will still give error or not If the List give data in string List<String>
I want to loop around the indexes of this list and pass it to Firebase collection's document id to get multiple data's of the users. For example on list's index 0 there's Me coming for myself and on index 1 there's Admin coming. Both have their respective data stored in Firestore collection with their own document id's Me and Admin. I want it to be checked on the runtime the app will check if its Me or Admin or Some other index value
Here's my code of the list and the firestore I'm trying to achieve.
List<dynamic> clientcodes = [];
void getclientcodes() async {
final clientcode = await FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.email)
.get()
.then((clientcode) {
return clientcode.data()!["clientcode"];
});
setState(() {
if (clientcode != null) {
clientcodes = clientcode;
} else if (clientcode == null) {
setState(() {
const SpinKitSpinningLines(size: 100, color: Color(0xFF25315B));
});
}
});
}
Firestore:
Future getdatastatus() async {
DocumentSnapshot result = await FirebaseFirestore.instance
.collection("Statements")
// .doc("If I hardcode it the value of index 0 or 1 it works fine")
.doc(portfolionames.toString()) // This is area of issue
.get();
if (result.exists) {
print("Yes");
} else {
print("No");
}
}
You can insert getdatastatus() inside a loop, and let it get the index automatically by comparing it with any value you want it, see this:
Future getdatastatus() async {
for (var item in clientcodes) {
String docId = item.id;
if (docId == 'X' || docId == 'Y') {
DocumentSnapshot result = await FirebaseFirestore.instance
.collection("Statements")
.doc(docId)
.get();
if (result.exists) {
print("Yes");
} else {
print("No");
}
}
}
}
Hope that work with you!!
Update
In the first section of your code, I think there is a problem..
You can create the list out of the firestore streaming, then add the coming data to the list of model, after that you can loop it to take the value you want.
Class Database{
List<TestModel> clientcodes = [];
getclientcodes() {
return FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.email)
.snapshots()
.listen((event) {
clientcodes.add(TestModel.fromMap(event));
setState(() {
if (clientcode != null) {
clientcodes = clientcode;
} else if (clientcode == null) {
setState(() {
const SpinKitSpinningLines(size: 100, color: Color(0xFF25315B));
});
}
});
});
}
}
class TestModel {
late String name;
late String description;
TestModel({
required this.name,
required this.description,
});
TestModel.fromMap(DocumentSnapshot data) {
name = data['name'];
description = data['description'];
}
}

Flutter The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type?

I am returning a data from an API using flutter and I have a problem telling me that
The body might complete normally, causing 'null' to be returned, but the
return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
This is my method:
Future<void> getDoctorsFromApi() async {
List<int> ids = await findAllDoctor().then((list) {
return list.map((e) => e.syncedId).toList();
});
doctors = await DoctorApi.getDoctors(ids).then((response) { // this is the line where error occurs
if (response.statusCode == 200) {
Iterable list = json.decode(response.body);
return list.map((model) => Doctor.fromJson(model)).toList();
} else {
_showMyDialog();
}
});
setState(() {
insertDoctors(database!);
});
}
What will be the value of doctors if response.statusCode is not 200? Handle that
by creating a nullable local variable:
final List<Doctor>? result = await DoctorApi.getDoctors(ids).then((response) {
if (response.statusCode == 200) {
Iterable list = json.decode(response.body);
return list.map((model) => Doctor.fromJson(model)).toList();
}
return null;
});
if (result == null) {
_showMyDialog();
} else {
doctors = result;
setState(() => insertDoctors(database!));
}
Just add some return or throw statement at the end of your function.
setState(() {
insertDoctors(database!);
});
throw ''; # or return something
}

Dart return Future.value is always null

I am trying to build a URL from a Firebase Storage file but the Future<String> I have built always seems to return null. This is the Future I am calling:
Future<String> getUrlFromStorageRefFromDocumentRef(
DocumentReference docRef) async {
try {
docRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
String filename = documentSnapshot.get('file');
firebase_storage.Reference ref = firebase_storage
.FirebaseStorage.instance
.ref()
.child('/flamelink/media/$filename');
if (ref == null) {
return Future.error("Storage Reference is null");
} else {
print(ref.fullPath);
return Future.value(
'https://storage.googleapis.com/xxxxxxxxx.appspot.com/${ref.fullPath}');
}
} else {
return Future.error('No Snapshot for DocumentReference ${docRef.id}');
}
});
} catch (e) {
print(e);
return Future.error('No DocumentReference for ID ${docRef.id}');
}
}
The line in question is :
return Future.value(
'https://storage.googleapis.com/xxxxxxxxx.appspot.com/${ref.fullPath}');
It's worth noting that the String is generated from the Firebase Storage path and everything looks perfect until it comes to return the value.
It should return the String value back to my calling code which at the moment looks like this:
DocButtonCallback docCallback = () async {
bool isKidsDoc = item.screenId == StringsManager.instance.screenIdKids;
try {
// first we need to get the URL for the document ...
var url = await AssetManager.instance
.getUrlFromStorageRefFromDocumentRef(isKidsDoc
? feature.relatedDocumentKidsRef
: feature.relatedDocumentRef);
String urlString = url.toString();
canLaunch(urlString).then((value) {
launch(urlString);
}).catchError((error) {
// TODO: open alert to tell user
});
} catch (error) {
print(error);
}
};
I have tried many different ways to get that String including:
DocButtonCallback docCallback = () async {
bool isKidsDoc = item.screenId == StringsManager.instance.screenIdKids;
await AssetManager.instance
.getUrlFromStorageRefFromDocumentRef(isKidsDoc
? feature.relatedDocumentKidsRef
: feature.relatedDocumentRef)
.then((urlString) {
canLaunch(urlString).then((value) {
launch(urlString);
}).catchError((error) {
// TODO: open alert to tell user
});
}).catchError((error) {
// TODO: open alert to tell user
});
};
For some reason, the Future always returns null. What am I doing wrong here?
You are returning the Future value inside the then() callback, which essentially returns this value from the callback itself rather than from your getUrlFromStorageRefFromDocumentRef() function. There you should only need to add a return statement before that:
Current:
docRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
...
After:
/// Adding the return statement here to return the actual value
/// returned internally by the then callback
return docRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
...
If you hover over the then() callback, your IDE should show you that this callback will return Future<T> (or whatever generic type placeholder) which need to be returned as well in order to make it available