How to make new "Future" instance based on function parameter in Dart? - flutter

In my code, "getResponse" is executed only once. How can I fix it?
I don't want to put "getResponse" inside "retry".
import "dart:math";
Future getResponse(int sec) async {
return Future.delayed(Duration(seconds: sec), () {
int rand = Random().nextInt(10);
print(rand);
if (rand < 5) {
return "success";
} else {
throw "rejected";
}
});
}
Future retry(Future f, [int count = 0]) async {
try {
return (await f);
} catch (e) {
if (count < 5) {
print(e);
retry(f, count + 1); // I think here is wrong.
}
}
}
void main() async => await retry(getResponse(1));
Function "retry" should execute getResponse until it successed

You cannot "retry" a future. Once it's done, it's done. You can however, create a new one every time, by passing a "future factory" (a function producing the relevant future) instead of the future:
import "dart:math";
Future getResponse(int sec) async {
return Future.delayed(Duration(seconds: sec), () {
int rand = Random().nextInt(10);
print(rand);
if (rand < 5) {
return "success";
} else {
throw "rejected";
}
});
}
// pass the factory function here
Future retry(Future Function() f, [int count = 0]) async {
try {
// call the function here to get a future to await
return (await f());
} catch (e) {
if (count < 5) {
print(e);
retry(f, count + 1);
}
}
}
// here, a function returning a future, instead of the future itself is passed
void main() async => await retry(() => getResponse(1));
The comment suggesting a loop instead of recursion is probably spot on too.

Related

Futures passed by reference have different hashcode, compare to false

I am having an hard time comparing Futures correctly in Dart. I hope I can be clear enough. I am doing two experiments:
This first test works. I create a future and then assign the same future to another variable. The Future is passed by reference so the variable is pointing at the same memory area and the objects are the same. Indeed the hashcode is the same and the comparison is true.
void main() {
Future f1 = Future.delayed(const Duration(seconds: 1));
Future f2 = f1;
print(f1.hashCode);
print(f2.hashCode);
print(f1 == f2);
}
Now, in this second test, I am doing the same operation but this time inside a method. I am trying to avoid to sending multiple calls to an API. I do one call and save the Future in a variable. When a method tries to send the request again, I pass him the pending Future instead of creating a new one.
In my mind, since the Futures are passed as a reference, they should still be the same and compare to true. Instead, I get false, and the Hashcode are completely different.
void main() {
Test();
}
class Test {
Future? _future = null;
Test() {
init();
}
void init() {
print("CALL 1");
Future d1 = processFuture();
print("ISNULL? "+(_future == null).toString());
print("CALL 2");
Future d2 = processFuture();
print("ISNULL? "+(_future == null).toString());
print(d1.hashCode);
print(d2.hashCode);
print(d1 == d2);
}
Future processFuture() async {
print("CALLED processFuture()");
if (_future != null){
print("FUTURE IS ALREADY THERE");
return _future;
}
print("NEW FUTURE REQUESTED");
_future = Future.delayed(const Duration(seconds: 1));
print("NEW FUTURE CREATED");
await _future;
print("FUTURE COMPLETE");
_future = null;
print("FUTURE IS NOW NULL");
return;
}
}
Am I missing something? Can someone explain me what I am misunderstanding and what am I doing wrong?
Thank you very much
Refactored the function to not be an async function, thanks #pskink
void main() {
Test();
}
class Test {
Future<dynamic>? _future = null;
Test() {
init();
}
void init() {
print("CALL 1");
Future<dynamic>? d1 = processFuture();
print("ISNULL? "+(_future == null).toString());
print("CALL 2");
Future<dynamic>? d2 = processFuture();
print("ISNULL? "+(_future == null).toString());
print(d1.hashCode);
print(d2.hashCode);
print(d1 == d2);
}
Future<dynamic>? processFuture() {
print("CALLED processFuture()");
if (_future != null){
print("FUTURE IS THERE");
return _future;
}
print("NEW FUTURE REQUESTED");
_future = Future.delayed(const Duration(seconds: 1))
.then((res) {
print("FUTURE COMPLETE");
_future = null;
print("FUTURE IS NOW NULL");
return res;
});
print("NEW FUTURE CREATED");
return _future;
}
}
A function marked with async implicitly returns new Future instances:
Future<int> foo() async => 42;
is implicitly equivalent to:
Future<int> foo() => Future.value(42);
If an async function internally returns a Future, then that Future is implicitly awaited, and its result is returned in a new Future. That is:
Future<int> bar() async => foo();
is implicitly equivalent to:
Future<int> bar() async => await foo();
so bar() returns a different Future than what foo() returned.

Dart: Why is an async error not caught when it is thrown in the constructor body?

main() async {
try {
final t = Test();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
void willThrow() async {
throw "error";
}
class Test {
Test() {
willThrow();
}
}
If the "async" keyword is removed from willThrow everything works as expected.
Is it because you can't await a constructor? If so is there anyway to catch async errors in a constructor body?
Have this a go:
void main() async {
try {
final t = Test();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
Future<void> willThrow() async {
throw "error";
}
class Test {
Test() {
willThrow().catchError((e){print('Error is caught here with msg: $e');});
}
}
As to the 'why':
You use a normal try/catch to catch the failures of awaited asynchronous computations. But since you cannot await the constructor, you have to register the callback that handles the exception in another way. I think :)
Since you never awaited the Future that was returned from willThrow(), and you never used the result of the Future, any exception thrown by the function is discarded.
There is no way to write an asynchronous constructor. So you are stuck with using old-school callbacks to handle errors, or simulate an async constructor with a static method:
void main() async {
try {
final t = await Test.create();
await Future.delayed(Duration(seconds: 1));
} catch (e) {
// Never printed
print("caught");
}
}
Future<void> willThrow() async {
throw "error";
}
class Test {
Test._syncCreate() {}
Future<void> _init() async {
await willThrow();
}
static Test create() async {
Test result = Test._syncCreate();
await result._init();
return result;
}
}

Flutter : Avoid using `forEach` with a function literal

Hi everyone this is my whole method :
Future<void> init() async {
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.limit(3)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
snapshot.docs.forEach((document) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
});
notifyListeners();
});
} else {
_loginState = ApplicationLoginState.loggedOut;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
}
notifyListeners();
});
}
the part that dart complains about is this one :
snapshot.docs.forEach((document) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
});
how can I change this method without ruining the whole functionality ?
Im just looking for a way that makes dart happy .
I appreciate your help in advance.
AVOID using forEach with a function literal.
BAD:
snapshot.docs.forEach((document) {
...
});
GOOD:
for (var document in snapshot.docs) {
// Rest of your code
}
Using like data.forEach(function);
example:
void main() async {
List<int> list = [1, 2, 3, 4, 5];
//recommended
list.forEach(showSquareNumbers);
//not recommended
list.forEach((int number) => showSquareNumbers(number));
list.forEach((int number) {
showSquareNumbers(number);
});
}
void showSquareNumbers(int data) {
print("$data * $data = ${data * data}");
}
This is my opinion.
I think the forEach seems more complicated than the for loop and the forEach can't use continue and break (return is available but not a thing happens when using return).
void test(List data) {
data.forEach((element) {
print(element);
if(element) return;
});
for (var element in data) {
print(element);
if(element) continue;
if(element) break;
if(element) return;
}
}
I think we should use the for instead of the forEach loop when your forEach loop seems like this code below because the for loop have many options more than forEach as I said.
data.forEach((element) {
print(element)
});
//or
data.forEach((element) => print(element));
I think the forEach loop is used for short code (easy to understand) and when you want to do something you don't care about the result like this code (using with Function(dynamic)).
void test(List data) {
void showInTerminal(e) {
print("data is $e");
}
data.forEach(showInTerminal);
Function(dynamic) function = showInTerminal;
data.forEach(function);
}
Make sure the data type and function(type) are the same.
//error
void test(List<Map> data) {
void showInTerminal(String e) {
print("data is $e");
}
data.forEach(showInTerminal);
}
//success
void test(List<Map> data) {
void showInTerminal(Map e) {
print("data is $e");
}
data.forEach(showInTerminal);
}
I code like this. I think it's easy to read.
void test() {
dataList.forEach(removeData);
fileList.forEach(removeFile);
}
Use await for:
await for (final document in snapshot.docs) {
// Your code...
}

Dart - returning a result from an async function

I was experimenting with asynchronous programming in dart when I stumbled upon a problem in which when I put a return statement inside a Future.delayed function it doesn't seem to return a value.
void main() {
perform();
}
void perform() async {
String result = await firstTask();
finalTask(result);
}
Future firstTask() async {
Duration duration = Duration(seconds: 4);
String result = 'task 2 data';
await Future.delayed(duration, () {
print('First Task Completed');
return result;
});
}
void finalTask(String result) {
print('final task completed and returned $result');
}
but if I put the return result; statement outside the Future.delayed function it returns its value to task 3. like,
void main() {
perform();
}
void perform() async {
String result = await firstTask();
finalTask(result);
}
Future firstTask() async {
Duration duration = Duration(seconds: 4);
String result = 'task 2 data';
await Future.delayed(duration, () {
print('First Task Completed');
});
return result;
}
void finalTask(String result) {
print('final task completed and returned $result');
}
Your first task doesn't have any return statement. IDE should be warning you about it. To fix it you have to do
Future firstTask() async {
Duration duration = Duration(seconds: 4);
String result = 'task 2 data';
return await Future.delayed(duration, () {
print('First Task Completed');
return result;
});
}
Or
Future firstTask() { // No async here
Duration duration = Duration(seconds: 4);
String result = 'task 2 data';
return Future.delayed(duration, () {
print('First Task Completed');
return result;
});
}

How can I retry a Future in Dart/Flutter?

I have a method that does some async processing and want it to retry X times. How can I achieve that in Dart/Flutter?
Use this function:
typedef Future<T> FutureGenerator<T>();
Future<T> retry<T>(int retries, FutureGenerator aFuture) async {
try {
return await aFuture();
} catch (e) {
if (retries > 1) {
return retry(retries - 1, aFuture);
}
rethrow;
}
}
And to use it:
main(List<String> arguments) {
retry(2, doSometing);
}
Future doSometing() async {
print("Doing something...");
await Future.delayed(Duration(milliseconds: 500));
return "Something";
}
I added an optional delay to Daniel Oliveira's answer:
typedef Future<T> FutureGenerator<T>();
Future<T> retry<T>(int retries, FutureGenerator aFuture, {Duration delay}) async {
try {
return await aFuture();
} catch (e) {
if (retries > 1) {
if (delay != null) {
await Future.delayed(delay);
}
return retry(retries - 1, aFuture);
}
rethrow;
}
}
You can use it as follows:
retry(2, doSometing, delay: const Duration(seconds: 1));
Retry from Dart Neat is a good API and, unofficially, it's from Google:
https://pub.dev/packages/retry
This is how I implemented it:
Future retry<T>(
{Future<T> Function() function,
int numberOfRetries = 3,
Duration delayToRetry = const Duration(milliseconds: 500),
String message = ''}) async {
int retry = numberOfRetries;
List<Exception> exceptions = [];
while (retry-- > 0) {
try {
return await function();
} catch (e) {
exceptions.add(e);
}
if (message != null) print('$message: retry - ${numberOfRetries - retry}');
await Future.delayed(delayToRetry);
}
AggregatedException exception = AggregatedException(message, exceptions);
throw exception;
}
class AggregatedException implements Exception {
final String message;
AggregatedException(this.message, this.exceptions)
: lastException = exceptions.last,
numberOfExceptions = exceptions.length;
final List<Exception> exceptions;
final Exception lastException;
final int numberOfExceptions;
String toString() {
String result = '';
exceptions.forEach((e) => result += e.toString() + '\\');
return result;
}
}
This is how I use it:
try {
await retry(
function: () async {
_connection = await BluetoothConnection.toAddress(device.address);
},
message: 'Bluetooth Connect');
} catch (e) {
_log.finest('Bluetooth init failed ${e.toString()}');
}