Flutter check is page on top of the screen stack - flutter

I have a page A that does some tasks for every time interval. I only want to perform these tasks only if page A is active and showing on the screen.
If the screen is showing page B, it will NOT perform the tasks.
How should I approach this problem?

It's fairly simple to do without checking the page stack. Since if it's on top of the page stack is only the case when that page is currently ACTIVe, meaning all other pages are removed (popped) from the stack. If you called for a new page with Navigator.of(context).push...., in order to 'pause' the previous page, you could await that action. The following example will a periodic timer (remember, you have to have it outside of the scope of the function, for example, in the state) and assign it to already existing Timer variable.
Timer t; //a variable in a Stateful widget
#override
void initState() {
super.initState();
//it's initialized somewhere and is already working
t = Timer.periodic(
Duration(milliseconds: 500),
(t) => print(
'CALL YOUR FUNCTION HERE ${t.tick}',
),
);
}
_someMethodInvokingPageB() async {
// Cancel the timer before the opening the second page
// no setState((){}) is needed here
t.cancel();
// Your calling of Page B. Key is the word AWAIT
// AWAIT will pause here until you are on the Page B
var result = await Navigator.of(context).pushNamed('/pageB');
// reassign a timer to your timer
// you don't need setState((){}) here
t = Timer.periodic(
Duration(milliseconds: 500),
(t) => print('CALL YOUR FUNCTION HERE ${t.tick}'),
);
}
That's how you have a timer, you have a method where you open Page B and before opening Page B you cancel that timer, await the opening of Page B and after you finish stuff on Page B, you reassign a new timer to your Timer t variable.
P.S. Don't forget to call t.cancel() in your dispose() method!

Related

Why is my function preventing the dialog dismiss/pop in Flutter?

I am trying to execute a function after a dialog is dismissed/popped. I read this article How to run code after showDialog is dismissed in Flutter? and tried to do it as recommended but it wouldn't work for me.
This is how I call my dialog:
Future<void> onDeleteEventData(BuildContext context) async {
final title = context.messages.settings.offline.deleteEventData;
final subTitle = context.messages.settings.offline.deleteEventDataDesc;
final res = await showDeleteDialog(context,
title: title,
subTitle: subTitle);
if (res == true){
context.read<EventDownloadTileController>().deleteEventRelatedData();
}
}
The showDeleteDialog function just calls a custom Dialog which is basically just the Flutter Dialog with some style changes.
Future<bool?> showDeleteDialog(BuildContext context,
{required String title, String? subTitle}) async {
return await showDialog(
context: context,
builder: (_) => DeleteDialog(title: title,subTitle: subTitle,)
);
}
In the dialog I press on a button and do this:
onPressed: () => Navigator.of(context).pop(true),
So looking at the first function I wait for my res which evaluates to true. At this point I thought the dialog should be popped. But it is not.
The problem is this call:
context.read().deleteEventRelatedData();
Because when I replace this call with e.g. Future.delayed(duration(seconds:5)); the dialog pops right away as expected.
This is the function:
Future<void> deleteEventRelatedData() async {
_ticketLoader.stop();
_ticketStorage.deleteScanTicketsForEvent(event.eventId);
_eventStorage.deleteEventPermissions(event.eventId);
_eventStorage.deleteEventData(event.eventId);
_ticketStorage.deleteCachedTicketsForEvent(event.eventId);
_ticketStorage.deleteCachedUnknownTicketsForEvent(event.eventId);
_ticketLoader.updateLastSync(null);
_ticketLoader.reset();
checkLocalStatus();
}
A function with some async and synchronous functions. The execution takes up to 3 seconds which is the time it takes to dismiss/pop my dialog. But I want to pop the dialog right away and let it work in the back. What could my function possibly do for this behavior?
Thanks in advance
The dialog window isn't going to disappear until the app can manage to do a rebuild. If your function call takes a while, it could be hogging the main thread until it's complete, disallowing other code (including widget code) from running.
Try wrapping your function call in a microtask so it doesn't run until the next available task window which will give the app time to clean up the dialog window:
await Future.microtask(deleteEventRelatedData);
It's also worth mentioning the body of the deleteEventRelatedData is marked as async but it never awaits anything. That means all of the synchronous calls can happen in a sequence that wasn't intended and the asynchronous calls won't get executed until a later time and in no guaranteed order.

Firestore requests not working properly in Time.periodic

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.

Flutter - re-run previous page code after execution returns to it

I'm trying to figure out the best way to implement the proper navigation flow of a flutter app I'm building that involves a 3rd party authentication page (Azure AD B2C). Currently I have a page that serves simply as a "navigate to 3rd party auth login" page which is set as the initialRoute for my app. The first time through, it runs exactly the way I want it to, but I'm not able to figure out how to get that 'navigate to auth' page to re-run when navigated back to (after logout) so that the user ends up back at the 3rd party auth login page.
Basically what I'd like to do is, on logout - have the app navigate back to that page specified as the initialRoute page, and upon that page being navigated back to, have it re-launch the 3rd party auth login page just like it did the first time it executed.
I tried just awaiting the call to Navigator.push() and then calling setState((){}) afterwards, and that does re-display the page, but it just leaves that initial page sitting there, and doesn't end up triggering the execution the way it did the first time. initState() does not fire again, so neither does any of my code that's in there.
I've tried various methods off the Navigator object trying to reload the page or navigate to itself again, or just calling goToLogin() again after the await Navigator.push() call, nothing works.
Here's what I'm currently doing :
User launches the app, the initialRoute is LoginRedirect
class LoginRedirect extends StatefulWidget {
#override
_LoginRedirectState createState() => _LoginRedirectState();
}
class _LoginRedirectState extends State<LoginRedirect> {
#override
void initState() {
Utility.getConfig().then((value) {
config = value;
oauth = AadOAuth(config);
goToLogin(context);
});
super.initState();
}
void goToLogin(BuildContext context) async {
setState(() {
loading = true;
});
try {
await oauth.login(); // this launches the 3rd party auth screen which returns here after user signs in
String accessToken = await oauth.getAccessToken();
navigateToDashboard();
setState(() {
loading = false;
});
} on Exception catch (error) {
setState(() {
loading = false;
});
}
}
void navigateToDashboard() {
await navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => Dashboard()));
// right here is where I'd like to call goToLogin() again after I Navigator.popUntil() back to this
// page, but if I try that I get an error page about how 'The specified child already
// has a parent. You must call removeView() on the child's parent first., java.lang
// .IllegalStateException and something about the bottom overflowed by 1063 pixels
}
}
After getting some config values and calling oauth.login() then I call a navigateToDashboard() method that pushes the Dashboard page on to the navigation stack.
Elsewhere in the code I have a logout button that ends up calling this code:
oauth.logout();
Navigator.popUntil(context, ModalRoute.withName('/LoginRedirect'));
which returns execution to where I called await Navigator.push() previously. But I can't figure out what I need to do there to have that LoginRedirect page execute again. I can't call goToLogin() again or it errors/crashes. I can't call initState() again, calling setState() doesn't do anything. I'm kinda stumped here, I thought this would be easy.
When logging out try: Navigator.pushReplacementNamed(context, "/LoginRedirect"); instead of Navigator.popUntil(context, ModalRoute.withName('/LoginRedirect'));

What is best way to call a function if there no response from showDialog after x seconds in Futter

I'm kinda new to flutter. I searched but couldn't find a suitable answer to this... What would be the best way to call a function if the user doesn't respond to a showDialog alert after x seconds? If the user presses a button, then I don't want the function to execute.
You can start a Timer for x seconds as soon as you display your dialog & then execute your function. If the user clicks your button, you can stop your timer.
Timer _timer;
bool userResponded = false;
You will need a StatefulWidget & wherever you show your dialog, you need to start the timer.
showDialog(...); // Your showDialog method
// You have to update userResponded to true if user clicks on your dialog or whatever
// It should look something like this: setState(() => userResponded = true);
_timer = Timer(const Duration(seconds: 10), () { // Start your timer for x seconds
if (!userResponded) { // If user didn't respond
// execute your function
}
});
Also, you need to override the onDispose method & stop the timer:
#override
void onDispose() {
_timer?.cancel();
super.onDispose();
}
For this, you would use a Timer widget.
Timer example:
Timer _timer = Timer(
const Duration(milliseconds: 500),
() {
// Call some function after delay of 500ms
},
);
To cancel the timer, use _timer.cancel();
So, most likely in the initState method you'll want to set timer object, then when the user presses the button, you can cancel this timer which means the callback in it won't run after a delay you've specified.

How to start/stop inactivity timer in Flutter

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