Flutter : Question about Futures and async function - asynchronous

I have several questions regarding the use of Futures in dart. Let's say I am working with a firestore and I have a function like this to update users's info :
void updateOldUser(User oldUser,String newInfo){
DocumentReference userToUpdateRef = userRef.document(oldUser.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, userToUpdateSnapshot.data[newInfo] + 1
).catchError((e)=> print(e));
}
});
}
My question is : does it need to return a future since runTransaction is Future. It seems to work fine without it but to me it feels like it should return a "Future void" in order to be able to await updateOldUser when I use it. But when I turn it into a "Future void" and then end the function body with 'return;' I have an error saying 'expected a value after return'.
But what I really don't understand is that with another similar code :
Future<void> updateUserPhoto(User user,File userPhoto) async {
String photoUrl = await uploadImage(user.id,userPhoto);
DocumentReference userToUpdateRef = userRef.document(user.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, {
'photoUrl' : photoUrl
}
).catchError((e)=> print(e));
}
});
return;
}
I don't get this error and It work fine also. Why ? Thanks in advance.

does it need to return a future
If you want the caller to be able to await the completion of the function, then the return type should be Future<...> (like Future<void> if there is no concrete return value, or Future<int> if the result is an integer value, ...)
For fire-and-forget async functions you can use void, but that is rather uncommon.
If the return type is Future<...> the caller can still decide not to wait for completion anyway.
But when I turn it into a "Future void" and then end the function body with 'return;'
If you use async, a Future is returned implicitly at the end of the method.
If you do not use async, you need to return a Future like
return doSomething.then((v) => doSomethingElse());

Related

Flutter Firestore Update Where

I'm trying to run a query that retrieves a single row given a where clause and updates it. I understand that Firebase doesn't support an UpdateWhere operations so I'm trying to use a Transaction instead.
I'm having difficulty making it work, maybe I'm too used to sql dbs... Here's my broken code
try {
final whereQuery = _db
.doc(userPath(user))
.collection("someInnerCollection")
.where("active", isEqualTo: true)
.limit(1);
await _db.runTransaction((transaction) async {
final entry = await transaction.get(whereQuery); // This doesn't compile as .get doesn't take in a query
await transaction.update(entry, {
"someValue": "newValue",
});
});
} catch (e) {
...
}
From the test I’ve made, I would suggest the following to achieve what you mention:
Based on the following answer:
As you can see from the API documentation, where() returns a Query object. It's not a DocumentReference.
Even if you think that a query will only return one document, you still have to write code to deal with the fact that it could return zero or more documents in a QuerySnapshot object. I suggest reviewing the documentation on queries to see examples.
After doing the query consult, you have to get the DocumentReference for that given result.
Then, you can use that reference to update the field inside a Batched writes
try {
final post = await firestore
.collection('someInnerCollection')
.where('active', isEqualTo: true)
.limit(1)
.get()
.then((QuerySnapshot snapshot) {
//Here we get the document reference and return to the post variable.
return snapshot.docs[0].reference;
});
var batch = firestore.batch();
//Updates the field value, using post as document reference
batch.update(post, { 'someValue': 'newValue' });
batch.commit();
} catch (e) {
print(e);
}
You are passing the DocumentSnapshot back in the update() operation instead of DocumentReference itself. Try refactoring the like this:
final docRefToUpdate = _db.collection("colName").doc("docId");
await _db.runTransaction((transaction) async {
final entry = await transaction.get() // <-- DocRef of document to update in get() here
await transaction.update(docRefToUpdate, {
// Pass the DocumentReference here ^^
"someValue": "newValue",
});
});
You can use a collection reference and then update single fields using .update().
final CollectionReference collectionReference = FirebaseFirestore.instance.collection('users');
await collectionReference.doc(user.uid).collection('yourNewCollection').doc('yourDocumentInsideNestedCollection').update({
'singleField': 'whatever you want,
});
Same code using "where"
collectionReference.doc(user.uid).collection('yourNewCollection').doc().where('singleField', isEqualTo: yourValue).update({
'singleField': 'whatever you want,
});

Future Function Returning Null Firestore

I am trying to make a function in order to see which method the user has used to sign in eg. Google. I made a function which was called getProvider(). Here is the code for the function.
Future<String> getProvider (FirebaseAuth _auth)
async {
var user = await _auth.currentUser();
var provider;
Firestore firestore = Firestore.instance;
await firestore
.collection('Users')
.document('${user.uid}')
.get()
.then((value) {
provider =
value.data['Authentication Provider'];
return provider;
});
}
However, when I print the output value of the function outside the function itself, I get null.
print(await getProvider(_auth)) //This prints null
However, when I print the value of provider inside the function, it is not null and In this case, I get google. When I removed this function and called all of the code wherever I needed it, it worked as expected.
This shows that there is a problem with the return value of this getProvider() function. Can someone please let me know how I can fix this so that it actually returns the correct value?
Try this:
Future<String> getProvider(FirebaseAuth _auth) async {
var user = await _auth.currentUser();
Firestore firestore = Firestore.instance;
return firestore
.collection('Users')
.document('${user.uid}')
.get()
.then((value) => value.data['Authentication Provider']);
}
then() returns a Future which you can later await on to get the value. Since your method are already returning Future<String> you should be able to just return the Future generated by then().

Run async code in synchronous way in Dart

Having the following code:
Future<String> checkPrinter() async {
await new Future.delayed(const Duration(seconds: 3));
return Future.value("Ok");
}
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_) {
return "Printer does not respond!";
});
}
void main() {
print(getPrinterStatus());
}
The output is "null" because the function getPrinterStatus() returns without waiting for checkPrinter to complete (correctly i have a warning telling me that getPrinterStatus does not return a string).
What should i do to make getPrinterStatus to wait for checkPrinter()?
Future<String> getPrinterStatus() async {
await checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
There are no such way to make async code to run in a synchronous way in Dart. So you need to make it so async methods returns a Future so it is possible to await the answer.
The problem you have in your code is your are using the then method but does not take into account that this method returns a Future you should return. So e.g.:
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
The two return statements in this example are used for the method your are giving as argument for then() and are therefore not used to return from getPrinterStatus().
Instead, then() returns a Future<String> which will complete with the value.
So you need to carry the async through your program. Your code could make use of some language features you gets from marking a method async so I have tried to rewrite your code so it works as expected and make use of this features:
Future<String> checkPrinter() async {
await Future.delayed(const Duration(seconds: 3));
return "Ok";
}
// Just an example that the method could be written like this.
// Future.delayed takes a computation as argument.
Future<String> checkPrinterShorter() =>
Future.delayed(const Duration(seconds: 3), () => 'Ok');
Future<String> getPrinterStatus() async {
try {
return 'The printer answered: ${await checkPrinter()}';
} catch (_) {
return "Printer does not respond!";
}
}
Future<void> main() async {
print(await getPrinterStatus());
}
There are some changes I think you should notice:
If a method is marked as async the returned value will automatically be packed into a Future<Type> so you don't need to return Future.value("Ok") but can just return Ok.
If a method is marked as async you can use await instead of using then(). Also, you can use normal exception handling so I have rewritten getPrinterStatus to do that.
Your getPrinterStatus() is not async. main() function will never wait for it's execution.
Make it async. Refactor your main() for something like
void main() {
getPrinterStatus().then((value) {
print(value);
}).catchError((_){
return "Printer does not respond!";});
}

How can I correctly yield from a stream that uses Futures/async/await?

I've encountered a weird issue where if I yield* from my provider in my flutter app, the rest of the code in the function doesn't complete.
I'm using the BLoC pattern, so my _mapEventToState function looks like this:
Stream<WizardState> _mapJoiningCongregationToState(
int identifier, int password) async* {
_subscription?.cancel();
_subscription= (_provider.doThings(
id: identifier, password: password))
.listen((progress) => {
dispatch(Event(
progressMessage: progress.progressText))
}, onError: (error){
print(error);
}, onDone: (){
print('done joiining');
});
}
Then in the provider/service... this is the first attempt.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
yield* _progressStream.stream;
}
The yield statement returns, but only after both awaited functions have completed. This makes complete sense to me, obviously I wouldn't expect the code to complete out of order and somehow run the yield* before waiting for the 'await's to complete.
In order to "subscribe" to the progress of this service though, I need to yield the stream back up to the caller, to write updates on the UI etc. In my mind, this is as simple as moving the yield* to before the first await. Like this.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
yield* _progressStream.stream;
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}
But, then setting breakpoints on the later _progressStream.add calls show that these never get called. I'm stuck on this, any idea what it could be? I know it has something to do with how I have mixed Futures and Streams.
The yield* awaits the completion of the stream it returns.
In this case, you want to return a stream immediately, then asynchronously feed some data into that stream.
Is anything else adding events to the stream controller? If not, you should be able to just do:
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake1...");
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake5!!!...");
}
No stream controller is needed.
If other functions also add to the stream controller, then you do need it. You then have to splut your stream creation into an async part which updates the stream controller, and a synchronous part which returns the stream. Maybe:
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) {
() async {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}(); // Spin off async background task to update stream controller.
return _progressStream.stream;
}

Function has a return type of 'Future<String>' but doesn't end with a return statement

I am writing a Future method in Dart (flutter). It simply runs a query on Firebase and returns the result. But even before writing my business logic, I am getting a warning message says:
[dart] This function has a return type of 'Future', but
doesn't end with a return statement. [missing_return]
Below is my Future function:
Future<String> getLikeCount(documentID) async {
Firestore.instance.collection('favorites').where(documentID).getDocuments().then((data){
return 'test';
});
}
I get the basic idea of why the error is happening, I assume that because there is a 'then' inside, till it happens the function returns nothing. How to overcome this issue?
Use await instead of then because your method is async
final snapshot = await Firestore.instance.collection('favorites').where(documentID).getDocuments();
return "test";
Change this :
_getLikes() async
To this :
Future<String> _getLikes() async
Because you expect a String Future.
try this without async
Future<String> getLikeCount(documentID) {
return Firestore.instance.collection('favorites').where(documentID).getDocuments().then((data){
return 'test';
});
}

Resources