Dart: I made a function async but still can't use await - flutter

I made a function async but still can't use await expression. What am I wrong? My code is like this.
Future<void> _aFunction() async {
DocumentReference docRef = Firestore.instance.collection('collection').document(docId);
docRef.get().then((DocumentSnapshot docSnapShot) {
if (docSnapShot.exists) {
String ip = await Connectivity().getWifiIP();

That's because here is an internal (anonymous) function declaration inside then, which is not async. Actually, await keyword can be thought as a syntactic sugar over then, so it would be convenient to refactor the function like this:
Future<void> _aFunction() async {
final DocumentSnapshot snapshot = await Firestore.instance.collection('collection').document(docId).get();
if (snapshot.exists) {
String ip = await Connectivity().getWifiIP();
// Rest of the `ip` variable handling logic function
}
// Rest of the function
}
Now await keyword corresponds to the _aFunction itself instead of an anonymous function declared inside _aFunction, so it works as expected.

Related

Most elegant way to wait for async initialization in Dart

I have a class that is responsible for all my API/Database queries. All the calls as well as the initialization of the class are async methods.
The contract I'd like to offer is that the caller has to call [initialize] as early as possible, but they don't have to await for it, and then they can call any of the API methods whenever they need later.
What I have looks roughly like this:
class MyApi {
late final ApiConnection _connection;
late final Future<void> _initialized;
void initialize(...) async {
_initialized = Future<void>(() async {
// expensive initialization that sets _connection
});
await _initialized;
}
Future<bool> someQuery(...) async {
await _initialized;
// expensive async query that uses _connection
}
Future<int> someOtherQuery(...) async {
await _initialized;
// expensive async query that uses _connection
}
}
This satisfies the nice contract I want for the caller, but in the implementation having those repeated await _initialized; lines at the start of every method feel very boilerplate-y. Is there a more elegant way to achieve the same result?
Short of using code-generation, I don't think there's a good way to automatically add boilerplate to all of your methods.
However, depending on how _connection is initialized, you perhaps instead could change:
late final ApiConnection _connection;
late final Future<void> _initialized;
to something like:
late final Future<ApiConnection> _connection = _initializeConnection(...);
and get rid of the _initialized flag. That way, your boilerplate would change from:
Future<bool> someQuery(...) async {
await _initialized;
// expensive async query that uses `_connection`
to:
Future<bool> someQuery(...) async {
var connection = await _connection;
// expensive async query that uses `connection`
This might not look like much of an improvement, but it is significantly less error-prone. With your current approach of using await _initialized;, any method that accidentally omits that could fail at runtime with a LateInitializationError when accessing _connection prematurely. Such a failure also could easily go unnoticed since the failure would depend on the order in which your methods are called. For example, if you had:
Future<bool> goodQuery() async {
await _initialized;
return _connection.doSomething();
}
Future<bool> badQuery() async {
// Oops, forgot `await _initialized;`.
return _connection.doSomething();
}
then calling
var result1 = await goodQuery();
var result2 = await badQuery();
would succeed, but
var result2 = await badQuery();
var result1 = await goodQuery();
would fail.
In contrast, if you can use var connection = await _connection; instead, then callers would be naturally forced to include that boilerplate. Any caller that accidentally omits the boilerplate and attempts to use _connection directly would fail at compilation time by trying to use a Future<ApiConnection> as an ApiConnection.

How can I return a Future from a stream listen callback?

I have below code in flutter:
getData() {
final linksStream = getLinksStream().listen((String uri) async {
return uri;
});
}
In getData method, I want to return the value of uri which is from a stream listener. Since this value is generated at a later time, I am thinking to response a Future object in getData method. But I don't know how I can pass the uri as the value of Future.
In javascript, I can simply create a promise and resolve the value uri. How can I achieve it in dart?
In your code 'return uri' is not returning from getData but returning from anonymous function which is parameter of listen.
Correct code is like:
Future<String> getData() {
final Completer<String> c = new Completer<String>();
final linksStream = getLinksStream().listen((String uri) {
c.complete(uri);
});
return c.future;
}
Try this
Future<String> getData() async{
final linksStream = await getLinksStream().toList();
return linksStream[0].toString();
}

Chaing async method on Dart

I have following class.
class Element {
Future<Element> findById(var id)async {
await networkRequest();
return this;
}
Futute<Element> click() async {
await networkRequest();
return this;
}
}
I want to achieve the something like.
main() async {
var element = Element();
await element.findyById("something").click();
}
But I'm not able to do so because element.findById() returns future. How can I chain these async methods.
While there's no special syntax to chain futures, there are two semantically equivalent ways to do what you want:
1) Two separate await calls:
await element.findById("something");
await click();
2) Chaining with then:
await element.findById("something").then(() => click());
Use this,
await (await Element().findById("1")).click();
final el = await element.findyById("something");
await el.click();

List.map returns List<Future>

I got a class for handling SharePreferences
class SharedPreferencesUtils {
static Future<String> getSharedPreference(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
}
I try to use this class from another class to get all my sharedPreferences with this method:
void getAllPrefs() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var keyList = prefs.getKeys().toList();
var valueList = keyList.map((key) async {
String value = await SharedPreferencesUtils.getSharedPreference(key);
return value;
}).toList();
print("KEY LIST IS $keyList");
print("VALUE LIST IS $valueList");
}
And, while the keyList works well, the valueList just returns:
VALUE LIST IS [Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>']
I don't really get why I am not getting the actual String value corresponding to the key, as I understood Futures, in this case, the execution should await until the var value gets the String value that I am asking for.....am I wrong?
Note: there are values stored in SharedPreferences, that is for sure.
This is a good one :)
I will only need to mention one key concept and you will see why this is happening: Any async function returns a Future.
In your case, the map call uses an async callback and hence the values in your lists are Futures.
There is a helper in the Future class: Future.wait
You can simply pass your Iterable to it and it will return a list with resolved futures:
Future<void> getAllPrefs() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final keyList = prefs.getKeys().toList();
final valueList = await Future.wait(keyList.map((key) async {
String value = await SharedPreferencesUtils.getSharedPreference(key);
return value;
}));
print("KEY LIST IS $keyList");
print("VALUE LIST IS $valueList");
}
How do you do it without the helper? Well, not use map because it requires a callback, but you need to stay in the same scope if you want to get rid of Future values as any outside function would need to be async. So here you go:
Future<void> getAllPrefs() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final keyList = prefs.getKeys().toList();
final valueList = List<String>(keyList.length);
for (int i = 0; i < valueList.length; i++)
valueList[i] = await SharedPreferencesUtils.getSharedPreference(keyList[i]);
print("KEY LIST IS $keyList");
print("VALUE LIST IS $valueList");
}
One practice that helps you to remember that all async functions return futures is using Future<void> as the return type instead.
If you use Future.wait, i.e. still use your map call, you can make it a lot more concise like this:
await Future.wait(keyList.map(SharedPreferencesUtils.getSharedPreferences));
You can just use keyList.map(SharedPreferencesUtils.getSharedPreferences) because getSharedPreferences already takes a String and returns a Future<String>, which is equivalent to what you were doing before :)

Flutter- call function after function

I want to call function2 after function1 finished.
To do that I did like that.
this is function 1.
Future _uploadImages() async {
setState(() {isUploading = true;});
images.forEach((image) async {
await image.requestThumbnail(300, 300).then((_) async {
final int date = DateTime.now().millisecondsSinceEpoch;
final String storageId = '$date$uid';
final StorageReference ref =
FirebaseStorage.instance.ref().child('images').child(storageId);
final file = image.thumbData.buffer.asUint8List();
StorageUploadTask uploadTask = ref.putData(file);
Uri downloadUrl = (await uploadTask.future).downloadUrl;
final String url = downloadUrl.toString();
imageUrls.add(url);
});
});
}
this is function 2
Future _writeImageInfo() async {
await _uploadImages().then((_) async {
await Firestore.instance.collection('post').document(uid).setData({
'imageUrls': imageUrls,
}).then((_) {
Navigator.of(context).pop();
});
}
But console says function2's imageUrls called when list length = 0 because it's called before function 1 finished.
I don't know why that function not called after function 1.
How can I make this right?
This happens because of your images.forEach. The .forEach doesn't work with async callback. Therefore it doesn't wait the end of each the foreach to continue the function.
In general, don't use .forEach in dart anyway. Dart did a great job on the for keyword directly.
So ultimately, you should do the following:
for (final image in images) {
...
}