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()}');
}
Related
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.
I have a simple flutter code to retrieve some data from Firestore. the data is retireved correctly, however passing the data from the future function making the result always null. can you advise how to adapt the code to return the list?
that is the class where the actual query is happening:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
print(businessprofileslist[0]);
});
});
} catch (e) {
print(e.toString());
return null;
}
}
}
here is the page where I am calling the function: (however the result is always null)
class _ProfilesListPageState extends State<ProfilesListPage> {
List businessprofileslist = [];
#override
void initState() {
super.initState();
fetchBusinessProfilesList();
}
fetchBusinessProfilesList() async {
dynamic result = await DatabaseManager().GetBusinessProfilesCollection();
print(result.toString());
if (result == null) {
print('enable to retieve');
} else {
print('success');
setState(() {
businessprofileslist = result;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
You're not returning anything from GetBusinessProfilesCollection but null, so the result seems somewhat expected.
I guess you want to do:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
var QuerySnapshot = await BusinessProfilesCollection.get();
querySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
});
return businessprofileslist;
} catch (e) {
print(e.toString());
return null;
}
}
}
Btw: returning null when the load fails, is just going to lead to a null pointer exception when you then do print(result.toString());. So I recommend not catching the error and just letting it bubble up. With that your code can be simplified to:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
var QuerySnapshot = await BusinessProfilesCollection.get();
return querySnapshot.docs.map((element) => element.data());
}
}
You just need to return the list
return businessprofileslist;
CODE :
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
print(businessprofileslist[0]);
});
// you just need to return the list here after filling it up
return businessprofileslist;
});
} catch (e) {
print(e.toString());
return null;
}
}
}
Code with a little improvement:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.map((doc) => doc.data()).toList();
});
}
}
Try that with calling the function in feching
fetchBusinessProfilesList()
async {
dynamic result ;
await DatabaseManager().GetBusinessProfilesCollection().then((value){
result=value;
print(result.toString());
if (result == null) {
print('enable to retieve');
} else {
print('success');
setState(() {
businessprofileslist = result;
});
}
});
}
I working on error handling of api's. i want if api is crashed then it display a message of "Server is down" something like this, in UI.
I created a class where i'm creating methods of api, here in getBooks method if i modify the api url then it is printing this Exception, and i want it in UI. The problem is getBooks return type is List<Book>> so we can't return this Exception, any solution how to do this?
Exception
E/flutter (12924): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Exception
here is my api code
class BooksApi {
static Future<List<Book>> getBooks(String query) async {
try {
final url = Uri.parse(
'https://gist.githubusercontent.com/JohannesMilke/d53fbbe9a1b7e7ca2645db13b995dc6f/raw/eace0e20f86cdde3352b2d92f699b6e9dedd8c70/books.json');
final response = await http.get(url);
if (response.statusCode == 200) {
final List books = json.decode(response.body);
return books.map((json) => Book.fromJson(json)).where((book) {
final titleLower = book.title.toLowerCase();
final authorLower = book.author.toLowerCase();
final searchLower = query.toLowerCase();
return titleLower.contains(searchLower) ||
authorLower.contains(searchLower);
}).toList();
} else {
throw Exception;
}
} catch (e) {
print("e");
print(e);
}
throw Exception;
}
}
and calling it like
Future init() async {
setState(() {
isLoading = true;
});
var books = await BooksApi.getBooks(query); //this
var response = await obj.getProduct();
print(response);
setState(() => this.books = books);
setState(() {
isLoading = false;
});
}
You could handle errors with then and onError :
await BooksApi.getBooks(query).then((books) async {
setState(() => {
this.books = books;
this.isLoading = false;
})
}, onError: (error) {
// do something with error
});
or a simple try-catch (you can write try-catch clauses the same way you would in synchronous code).
See handling errors.
You can also use catchError id you don't use async/await :
BooksApi.getBooks(query).then((books) {
setState(() => {
this.books = books;
this.isLoading = false;
})
}).catchError((error, stackTrace) {
print("error is: $error");
});
See futures error handling.
Try to wrap 'var books = await BooksApi.getBooks(query)' with try and catch.
...
try {
var books = await BooksApi.getBooks(query);
} catch (e) {
// To do for UI
}
...
For api, you need to make something like this:
APIModel{
final int code;
// or a success flag
// final bool success;
final String message;
final List<Book> data;
APIModel({this.code,this.message,this.data});
}
It means, every api have its own code,message,and data filed.
When you request, you can check your code or success:
var response = await request(params);
isLoading = false;
if(response.code == 0){}
// or
if(response.success){
// do what you want
}
else {
Toast.show(response.message);
}
You can use build_runner and json_serializable.
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;
});
}
In my Flutter app, I have a function returning Future, but I wanna get result as Stream. Here is the function :
Future<bool> isGpsOn() async {
if (await Geolocator().isLocationServiceEnabled()) {
return true;
} else {
return false;
}
}
How to do that?
Read the manual and check my answer:
Stream<bool> gpsStatusStream() async* {
bool enabled;
while (true) {
try {
bool isEnabled = await Geolocator().isLocationServiceEnabled();
if (enabled != isEnabled) {
enabled = isEnabled;
yield enabled;
}
}
catch (error) {}
await Future.delayed(Duration(seconds: 5));
}
}
gpsStatusStream().listen((enabled) {
print(enabled ? 'enabled' : 'disabled');
});
or create convertor:
Stream futureToStream(fn, defaultValue, Duration duration) async* {
var result;
while (true) {
try {
result = await fn();
}
catch (error) {
result = defaultValue;
}
finally {
yield result;
}
await Future.delayed(duration);
}
}
Future<bool> isGpsOn() async {
return await Geolocator().isLocationServiceEnabled();
}
final gpsStatusStream = futureToStream(isGpsOn, false, Duration(seconds: 5));
gpsStatusStream.listen((enabled) {
print(enabled ? 'enabled' : 'disabled');
});
If you don't want to change the return type of your function, you could make callers convert the Future<T> to a Stream<T> by simply calling asStream() on the returned Future.