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

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

Related

How to await a listener being called and return a Future value

I have a Future function which adds a listener to a ValueNotifier. How can I return a value that I retrieve when the listener is called?
Map<String,ValueNotifier<String>> _data = {};
Future<String> getAnswer(String text) async {
if (_data["answer"] == null || _data["answer"]!.value.isEmpty) {
_data["answer"] = ValueNotifier<String>("");
_data["answer"]!.addListener(() {
if (_data["answer"]!.value.isNotEmpty) {
// somehow return _data["answer"]!.value
} else {
// continue waiting for next listener call
}
});
} else {
return _data["answer"]!.value;
}
return await //not sure what to put here.
}
Ok I found a solution that works in this case is to use Completer() as follows:
Map<String,ValueNotifier<String>> _data = {};
Future<String> getAnswer(String text) async {
var completer = Completer<String>();
if (_data["answer"] == null || _data["answer"]!.value.isEmpty) {
_data["answer"] = ValueNotifier<String>("");
_data["answer"]!.addListener(() {
if (_data["answer"]!.value.isNotEmpty) {
completer.complete(_data["answer"]!.value);
_data["answer"]!.dispose();
}
});
} else {
return _data["answer"]!.value;
}
return completer.future;
}
Seems like you always want to use the value of _data['answer'] as soon as it gets updated. In this case you should try using Riverpod library: https://pub.dev/packages/flutter_riverpod This library will help you in watching a notifier and then rebuild stateful widgets when the data is updated.

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

Dart, I can't return anything inside of a foreach

So, as the title says, I can't return anything inside of a foreach in this function. It is not a problem of the conditional because I have tried it and it works, I can enter in it, I can even print a text, but I cannot return anything, which is weird, maybe forEach in dart works different than I know?
Future<String> getEnrollment(idU) async {
final querySnapshot =
await FirebaseFirestore.instance.collection('Enrollment').get();
querySnapshot.docs.forEach((doc) {
if (doc['infoCar']['idUser'] == idU) {
return doc.id; // I can't return this one.
}
});
return null;
}
This is not a problem with Dart. You are facing this problem because the function inside of forEach is a callback function. So, when your return inside that function, you are returning doc.id from that function, instead of getEnrollment() function. You can see the same behavior in other languages that support callbacks e.g JavaScript.
However, you can solve your problem using firstWhere() instead.
Future<String> getEnrollment(idU) async {
final querySnapshot =
await FirebaseFirestore.instance.collection('Enrollment').get();
final enrollment = querySnapshot.docs.firstWhere(
(doc) => doc['infoCar']['idUser'] == idU,
orElse: () => null,
);
if (enrollment == null) return null;
return enrollment.id;
}
Note: orElse runs when the firstWhere doesn't return true for any item in the list. Here we are returning null in that case.
Replace your iterable.forEach... with for (e in iterable) { ... if ([something about e]) { return [something] } }
That'll work because the return now means the enclosing subroutine.

Is it possible to filter a List with a function that returns Future?

I have a list List<Item> list and a function Future<bool> myFilter(Item).
Is there a way to filter my list using the Future returning function myFilter()?
The idea is to be able to do something like this:
final result = list.where((item) => myFilter(item)).toList();
But this is not possible since where expects bool and not Future<bool>
Since the iteration involves async operation, you need to use a Future to perform the iteration.
final result = <Item>[];
await Future.forEach(list, (Item item) async {
if (await myFilter(item)) {
result.add(item);
}
});
You can iterate over your collection and asynchronously map your value to the nullable version of itself. In asyncMap method of Stream class you can call async methods and get an unwrapped Future value downstream.
final filteredList = await Stream.fromIterable(list).asyncMap((item) async {
if (await myFilter(item)) {
return item;
} else {
return null;
}
}).where((item) => item != null).toList()
You can try bellow:
1, Convert List => Stream:
example:
Stream.fromIterable([12, 23, 45, 40])
2, Create Future List with this function
Future<List<int>> whereAsync(Stream<int> stream) async {
List<int> results = [];
await for (var data in stream) {
bool valid = await myFilter(data);
if (valid) {
results.add(data);
}
}
return results;
}
Here's a complete solution to create a whereAsync() extension function using ideas from the accepted answer above. No need to convert to streams.
extension IterableExtension<E> on Iterable<E> {
Future<Iterable<E>> whereAsync(Future<bool> Function(E element) test) async {
final result = <E>[];
await Future.forEach(this, (E item) async {
if (await test(item)) {
result.add(item);
}
});
return result;
}
}
You can now use it in fluent-style on any iterable type. (Assume the function validate() is an async function defined elsewhere):
final validItems = await [1, 2, 3]
.map((i) => 'Test $i')
.whereAsync((s) async => await validate(s));
Try this:
final result = turnOffTime.map((item) {
if(myFilter(item)) {
return item;
}
}).toList();