I want to display a snackbar that says "your internet connection is slow" when an api request takes more than 4 seconds even while the request is still running in the background, just to notify the user that his connection is too slow. I've tried wrapping the request with a stopwatch but that only works after the connection gets a response, which is too late. As the Future request is running, i'd like to know how many seconds its taking so i can run the snackbar display to view. I'd appreciate any help please.
Hie there Moyosola.
1. If you want it to just check the 4 second period
To achieve this you can use a timer then cancel it once the time has passed, and notify the user. Check the code below. Call the method startTimer() when you start calling you api.
Timer _timer; // set as global variable
int _timeElapsed = 0;
void startTimer() {
const period = const Duration(seconds: 4);
_timer = new Timer.periodic(
period,
(Timer timer) {
if (_timeElapsed == 5) {
_timer.cancel();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Your Internet Connection Is Slow')));
} else {
setState(() {
_timeElapsed++;
});
}
},
);
}
2. If you want it to timeout
To achieve that you need to wrap your code or whatever you want to run in a try-catch block on throw an onTimeoutException error after your set time has expired. See the code below:
try {
//put whatever you are running here and add timeout to http request
await http.get(url).timeout(Duration(seconds: 4));
} on TimeoutException catch (e, s) {
//show any error after timeout here
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Your Internet Connection Is Slow')));
}
}
I debug and answer people's questions also on my youtube channel, Mufungo Geeks Programming, which might help you in other related queries. Good Day!
Related
I want to write firestore database every 15 seconds. This is the code I have right now:
void startTimer() {
const oneSec = const Duration(seconds: 15);
_timer = new Timer.periodic(
oneSec,
(Timer timer) async {
await games.doc(documentNumber).update({
'players': FieldValue.arrayUnion([player.toString()]),
})
},
);
}
Then I call startTimer() in my build function. It writes data to the database, but the performance is weird. Instead of writing the database every 15 seconds, it does it almost every second, and then the number of writes is unpredictable (more than one), which then leads to a memory leak. I don't know what and where to troubleshoot, because I don't see any problem in my code
How can I make it write exactly 1 entry to the database every 15 seconds?
I can't say for sure, but from the context you've given in your question I think I have a good idea of what's going on.
you're calling the timer from your build function
you used to have const oneSec = const Duration(seconds: 1); - either that or your variable naming needs some work
you're never cancelling the timer
So there's a few things going on here. The most obvious issue is that the widget's build function can and will run more than once in almost all applications. You should never rely on it not running more than once - that's not what it is meant for.
Because you initially started your widget with a timer running once a second, that will continue to run indefinitely. If you then do multiple build updates, that would then create more timers which also run indefinitely - which is exactly why you were seeing the number of writes grow as well as the unexpected timing.
Instead, you should be making your widget into a StatefulWidget and initiating the timer in an overridden initState() method (which will only run once per widget but should not access the context, which is fine in your case).
This would look like this:
class MyWidgetState extends State<MyWidget> {
late final Timer _timer;
#override
initState() {
super.initState();
startTimer();
}
#override
dispose() {
stopTimer();
super.dispose();
}
void startTimer() {
const oneSec = const Duration(seconds: 15);
_timer = new Timer.periodic(
oneSec,
(Timer timer) async {
await games.doc(documentNumber).update({
'players': FieldValue.arrayUnion([player.toString()]),
})
},
);
}
void stopTimer() {
_timer.cancel();
}
}
If you did need to access the context, you'd instead need to use an overridden didChangeDependencies() method which can run more than once but likely won't unless you're using inherited widgets. In this case you would need to put in some protections to make sure the timer wasn't created more than once i.e. make it nullable and check if it is set before creating a new one.
Or if you really do need to call the startTimer() function from your build function, you could do it the same way as mentioned above - make _timer nullable and check if it is set before making a new one. But I'd recommend avoiding this - if you always keep things that are started in initState and then always stop them in dispose, it will be a lot easier to figure out what's going on in the class.
I would say that using an asynchronous method inside a Timer could be the problem, However since you want to execute your method every 15 seconds, think about making a recursion method, with Future.dalayed like this:
int updateCount = 0; // this will allow us to track how many times the method is executed
recursionTimerUpdate() async {
await Future.delayed(Duration(seconds: 15)); //
updateCount += 1;
await games.doc(documentNumber).update({
'players': FieldValue.arrayUnion([player.toString()]),
});
bool shouldContinueWorking = updateCount < 10;
if (shouldContinueWorking) {
await recursionTimerUpdate();
}
}
this is a recursion method, that will execute your update() method every 15 seconds, and will stop from continuing after 10 times it's run, If you want it to not stop in your app, just set shouldContinueWorking = true.
I am developing an app by combining riverpod and stream. However, I receive the same event twice. To avoid duplicate reception, the stream is listened to in initState. However, a duplicate event occurred.
I checked it by taking breakpoints in debug mode, and I saw that two identical events were raised in streamController almost at the same time.
//This is the code that listens to the stream.
#override
void initState() {
super.initState();
ToiletListViewModel toiletListViewModel =
ref.read(toiletListViewModelProvider.notifier);
TextViewModel textViewModel = ref.read(textViewModelProvider.notifier);
textViewModel.setTexts();
toiletListViewModel.uiEventStream.listen((event) {
event.when(
onLoading: _onLoading,
onError: _onError,
onSuccess: _onSuccess,
);
});
toiletListViewModel.getToiletListLocal();
toiletListViewModel.getToiletListFromRemote();
}
//This is the code that sends an event to the stream
Future getToiletListFromRemote() async {
_uiEventController.add(const ToiletListUiEvent.onLoading());//This event occurs twice at a time.
try {
List<Toilet> results =
await getToiletListFromRemoteUseCase(toiletListPage);
state = [...state, ...results];
_uiEventController.add(const ToiletListUiEvent.onSuccess());
saveToiletList(state);
} catch (e) {
e as DioError;
_uiEventController.add(ToiletListUiEvent.onError(e.message));
}
return state;
}
If I make a mistake and fire the event twice, shouldn't success or fail occur twice as well as loading? In the code above, only the loading event is fired twice with no difference of 1ms.
What could be the cause? I don't know at all. Thank you for your help.
sorry. This was a stupid mistake. toiletListViewModel.getToiletListLocal();
I was calling the same event here.
Lets say that in Dart/Flutter you have the following code:
void myOperation() {
// could be anything
print('you didn't cancel me!');
}
Notice that the operation itself is not asynchronous and is void -- does not return anything.
We want it to execute at some point in the future, but we also want to be able to cancel it (because a new operation has been requested that supersedes it).
I've started by doing this:
Future.delayed(Duration(seconds: 2), myOperation())
... but this is not cancellable.
How exactly could you schedule that "operation," but also make it cancelable?
I'm thinking... we could modify the code like so:
Future.delayed(Duration(seconds: 2), () {
if (youStillWantThisToExecute) {
print('you didn't cancel me!');
}
});
But that's not really very good because it depends on a "global" boolean... and so if the boolean gets flipped to false, no operations will complete, even the most recently requested, which is the one we want to complete.
It would be nicer if there were a way to create any number of instances of the operation and cancel them on an individual basis... or to have a unique id assigned to each operation, and then instead of having a boolean control whether or not to execute... to have a "mostRecentId" int or something which is checked prior to execution.
Anyways...
CancelableOperation seemed promising just from its name.
So, I looked at its documentation:
CancelableOperation.fromFuture(Future inner, {FutureOr onCancel()})
Creates a CancelableOperation wrapping inner. [...] factory
But honestly that just makes my poor head hurt oh so much.
I've consulted other articles, questions, and answers, but they are all part of some specific (and complex) context and there isn't a dirt simple example anywhere to be found.
Is there a way to make a delayed future cancellable by wrapping it in some other class?
Can someone more experienced please provide at least one simple, complete, verified example that compiles in DartPad?
Thanks.
Use Timer:
var t = Timer(Duration(seconds: 400), () async {
client.close(force: true);
});
...
t.cancel();
Using CancalableOperation will not stop print('hello'); from executing even if you cancel. What it does is canceling(discarding) the result(void in your case). I will give you 2 examples using CancalableOperation and CancalableFuture.
CancelableOperation example
final delayedFuture = Future.delayed(
Duration(seconds: 2),
() {
return 'hello';
},
);
final cancellableOperation = CancelableOperation.fromFuture(
delayedFuture,
onCancel: () => {print('onCancel')},
);
cancellableOperation.value.then((value) => {
// Handle the future completion here
print('then: $value'),
});
cancellableOperation.value.whenComplete(() => {
print('onDone'),
});
cancellableOperation.cancel(); // <- commment this if you want to complete
CancelableFuture example
final delayedFuture = ...;
final cancalableFuture = CancelableFuture<String>(
future: delayedFuture,
onComplete: (result) {
// Use the result from the future to do stuff
print(result);
},
);
cancalableFuture.cancel(); // <- commment this if you want to complete
And the CancelableFuture implementation
class CancelableFuture<T> {
bool _cancelled = false;
CancelableFuture({
#required Future<dynamic> future,
#required void Function(T) onComplete,
}) {
future.then((value) {
if (!_cancelled) onComplete(value);
});
}
void cancel() {
_cancelled = true;
}
}
You cannot cancel an existing Future. If you do:
Future.delayed(
Duration(seconds: 2),
() {
print('hello');
},
);
as long as the process runs (and is processing its event queue) for at least 2 seconds, the Future eventually will execute and print 'hello'.
At best you can cause one of the Future's completion callbacks to fire prematurely so that callers can treat the operation as cancelled or failed, which is what CancelableOperation, et al. do.
Edit:
Based on your updated question, which now asks specifically about delayed Futures, you instead should consider using a Timer, which is cancelable. (However, unlike a Future, callers cannot directly wait on a Timer. If that matters to you, you would need to create a Completer, have callers wait on the Completer's Future, and let the Timer's callback complete it.)
Is there a way to start an inactivity timer for 5 seconds or X seconds WHILST a function executes?
Here is what I'm doing:
I'm creating a screenshot:
//todo: I need an activity timer [start] here
screenshotController.capture(
//delay: Duration(seconds: 5),
pixelRatio: 2,
path: newPath
).then((io.File image) {
//Capture Done
_imageFile = image;
}).catchError((onError) {
print(onError);
});
// todo: I need an activity timer [stop] here.
The user cannot interrupt the creation of the PNG file. I need some kind of progress timer to start/stop. I do not wish to use Progress_HUD. This is very ugly. I've tried it. I have to change my entire code to accommodate how this app works.
I'm inclined to use CircularProgress..().. but how can I make it start? and how can I make it stop??
How can I know when the screenshotController is still active?
I don't know a lot about screenshot plugin but if you just want to run a function every x seconds you can use Timer
In initState()
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (_) {
//Write here your function and logic
});
}
if your function is async put the timer in didChangeDependencies(),
you can stop the timer by writing some logic
I like to know the differences between Future.delayed and Timer method for delaying code execution. Both seem to do the same thing.
Future.delayed
Future.delayed(const Duration(milliseconds: 500), () { /*code*/ });
VS
Timer
Timer _timer = new Timer(const Duration(milliseconds: 500), () { /*code*/ });
A couple of differences for me.
Future.of returns a Future.
Timer does not return anything.
So if your delayed code returns anything that you need to continue your working, Future is the way to go.
Other difference is that the Timer class provides a way to fire repeatedly.
This quote is from the Timer Class Reference documentation itself
A count-down timer that can be configured to fire once or repeatedly
And example to use Timer with the repeat could be
Timer.periodic(Duration(seconds: 5), (timer) {
print(DateTime.now());
});
Other frecuent example is to create a stopwatch, to measure timings in your code, it's usually seen using a Timer.
GL!!
Timer:
Timer() creates a Timer object, that runs your computation after a delay. Since you get the reference to that Timer object, you can choose to cancel it before it's fired, by calling cancel.
Timer t = Timer(Duration(seconds: 1), () => print("1 sec later"));
t.cancel(); // nothing will be printed out
Future:
Future.delayed creates a Future that runs its computation after a delay. Internally, it's still using a Timer to do that. It does not expose the timer to you, so you cannot control or cancel it. On the bright side, you get to do your normal Future stuff, like await on it.
await Future.delayed(Duration(seconds: 1);
print("1 sec later");
Use Timer if:
You want the ability to cancel it. With Timer.cancel() you cancel the timer unlike Future where you'll have to make use of CancelableCompleter to cancel the Future.
If you don't want to return anything in your callback method.
Example:
// Prints 'Hello' after 1s.
var timer = Timer(Duration(seconds: 1), () => print('Hello'));
And in case you decide to cancel it, use:
timer.cancel();
Use Future if:
Your code can throw errors and you want to catch them. Had you used Timer and any uncaught exceptions occurs, the app will exit.
You want to return something from your callback method.
Example:
// Return 'Hello' after 1s and if there is any error, it will be caught.
Future
.delayed(Duration(seconds: 1), () => 'Hello')
.catchError((err) {});
The timer runs its job after the given duration, but flutter not waiting for it to complete its execution, it performs below statements.
Example:
Timer(Duration(seconds: 2), () {
print("Execute this code afer 2 seconds");
});
print("Other code");
Output:
Other code
Execute this code after 2 seconds
So as you can see code below timer will execute first and then the timer will be performed.
Also, Timer can be stopped at any given point before its execution, if we crate the object of it.
Timer timer = Timer(Duration(seconds: 2), () {
print("Execute this code afer 2 seconds");
});
timer.cancel();
The future also runs its job after the given duration, but its return future object means we can use await to get its execution first, and then below statements will be going to execute.
await Future.delayed(Duration(seconds: 2), () {
print("Execute this code afer 2 seconds");
});
print("My Code");
print("Other code");
Output:
Execute this code after 2 seconds
Other code
The main disadvantage of the future is that we can't cancel it in between.