How can I execute a function when a user hits the back button or swipes to close a screen in flutter? - flutter

I want to always execute a function when a screen is closed, either when a user presses the back button or does a swipe to close the page.
What I have tried is to use WillPopScope
return WillPopScope(
onWillPop: _onWillPop,
child: CupertinoScaffold( ... the rest of my tree
Future<bool> _onWillPop() async {
myFunction();
return true;
}
While this does successfully execute my function whenever the page is closed via the back button, it seems to have disabled being able to swipe to go back on iOS. Swipe to go back on android still seems to function.
Is there another way of doing this so I can retain the swipe to go back functionality on iOS?
edit:
Just incase it matters, myFunction() involves a provider and context and throws an error when I try to call it from dispose.
In actuality myFunction() is:
context.read(myVisibilityProvider).changeVisibility(false);

I successfully managed to run a function calling it from dispose retaining the iOS swipe to back gesture.
class ScrondScreen extends StatefulWidget {
const ScrondScreen({Key? key}) : super(key: key);
#override
_ScrondScreenState createState() => _ScrondScreenState();
}
class _ScrondScreenState extends State<ScrondScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
void myFunction() {
print('I run');
}
#override
void dispose() {
myFunction();
super.dispose();
}
}
console:
flutter: I run

Related

WillPopScope indiscriminately blocking all navigation, whether onWillPop returns true or false

I'm building a login screen. On that screen, I have a "login" button. After this button is pressed, my app connects to the internet in order to check if the user can be logged in. I want navigating backwards (physical button on Android, swipe-back on iOS) to be disabled while this loading is happening.
To achieve this, I should be able to wrap my screen in a WillPopScope widget, and have its onWillPop parameter look like this:
return WillPopScope(
onWillPop: () async => isLoading ? false : true,
child: child,
);
(isLoading is whether or loading is happening, and if navigation should be blocked)
However, this just universally blocks all navigation no matter if isLoading is true or false.
I've also tried this:
return isLoading
? WillPopScope(
onWillPop: () async => false,
child: child,
)
: child;
This works, however, doing it this way will block all animations in the child, which basically renders the solution useless.
Is there anyway to get the first method to work? Or, is there another way all together?
Thanks.
Figured it out. Use this package: https://pub.dev/packages/back_button_interceptor/example.
Create a widget that wraps your screen (Scaffold), using it like this:
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:flutter/material.dart';
class NavBlocker extends StatefulWidget {
const NavBlocker({
super.key,
required this.blocking,
required this.child,
});
final bool blocking;
final Widget child;
#override
State<NavBlocker> createState() => _NavBlockerState();
}
class _NavBlockerState extends State<NavBlocker> {
#override
void initState() {
super.initState();
BackButtonInterceptor.add(myInterceptor);
}
#override
void dispose() {
BackButtonInterceptor.remove(myInterceptor);
super.dispose();
}
bool myInterceptor(bool stopDefaultButtonEvent, RouteInfo info) {
return widget.blocking;
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
Where blocking specifies whether or not navigation should be blocked or not.
This solution enables animations to work, too!

Android back button action

I'm dealing with back actions. I'm not able to achieve good results with WillPopScope() (it's only called with top app return button but not called with android back button).
In my app, I have several pages and when android back button is pressed, I don't see the previous page.
For example, I have main-page1-page2-page3
If I'm in page 3 and press Android back button I return to page1, not to page2. In other cases it returns to main...How it is possible? Is there a way to define de pages "order"?
EDIT
This is my code that I shared in a previous question.
class Calendario1 extends StatelessWidget {
final List listaini;
Calendario1(this.listaini);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "ATGapp",
home:Cal1(listaini: listaini),
);
}
}
class Cal1 extends StatefulWidget {
final List listaini;
Cal1({Key? key,required this.listaini}) : super(key: key);
#override
///
_Cal1State createState() => _Cal1State();
}
class _Cal1State extends State<Cal1> {
#override
void initState() {
getImage(path1);
super.initState();
}
String url_1 = '';
getImage(String path1) async {
//String url='';
final ref1 = FirebaseStorage.instance.ref().child(path1);
var url1 = await ref1.getDownloadURL();
setState(() => url_1 = url1);
}
final FirebaseStorage storage =
FirebaseStorage.instance;
String path1 = 'fondos/mons.jpeg';
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async{
print('Bck button pressed');
return false;
},
child: Scaffold(
body: Column(...//and so on
add Navigator.pop() inside onWillPop and return true.
onWillPop: () async {
// do something here
return true;
},
return new WillPopScope(
onWillPop: _willPopCallback,
child: new Scaffold(
//then the rest of your code...
Thank your for all your help.
The final solution has been a new structure of the app pages. Until now, every page was a different dart file and with this structure, the behaviour of willpopscope and any other solutions didn't work for me.
Now, all pages are in the same file ordered by routes definitions in MaterialApp. Now, I'm able to catch the android back button click and work with it.
I hope this can be helpful for others.

How to detect tap on the scrim of the modal bottom sheet?

I am using a modal sheet, when I tap somewhere outside the modal bottom sheet (In the transparent area), it closes the bottom sheet, so I need a callback of this tap gesture so that I can perform certain actions before closing the bottom sheet.
There is property isDismissible in the showModalBottomSheet, which disables the tap on the scrim, but I don't want it to disable it, just need a callback so that certain actions can be performed before closing.
Not sure if this is exactly what you're looking for but you could return a StatefulWidget in the showModalBottomSheet builder and in that widget trigger a callback with the deactivate or dispose overrides. Deactivate is triggered first.
To trigger a callback you'd need to pass that function into the StatefulWidget's constructor.
e.g.
void callback() {
debugPrint('>>> my callback triggered');
}
void showMyModalBottomSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return MyBottomSheetWidget(
callback: callback,
);
},
);
}
class MyBottomSheetWidget extends StatefulWidget {
final VoidCallback callback;
const MyBottomSheetWidget({
Key key,
this.callback,
}) : super(key: key);
#override
State<MyBottomSheetWidget> createState() => _MyBottomSheetWidgetState();
}
class _MyBottomSheetWidgetState extends State<MyBottomSheetWidget> {
#override
void deactivate() {
debugPrint('>>> bottom sheet closing');
widget.callback(); // This will be trigger when the bottom sheet finishes closing
super.deactivate();
}
#override
Widget build(BuildContext context) {
return Container();
}
}

Difference between onPopPage and popRoute

I´m using Navigation 2.0 for flutter navigation development, however, there´s only one topic for me to understand it properly. I´m using a custom RootBackButtonDispatcher:
class AppBackButtonDispatcher extends RootBackButtonDispatcher {
final AppRouteDelegate routerDelegate;
AppBackButtonDispatcher({
#required this.routerDelegate,
}) : super();
#override
Future<bool> didPopRoute() {
return routerDelegate.popRoute();
}
}
When pressing the back button, the function that gets called is (a function that is inside my AppRouteDelegate):
#override
Future<bool> popRoute() {
if (_canPop()) {
_removePage(viewModel.pages.last);
viewModel.rebuild;
return SynchronousFuture(true);
}
return SynchronousFuture(false); // This will exit the application
}
The previous code handles properly the back button functionality, but the Navigator´s onPopPage never gets called:
#override
Widget build(BuildContext context) => Navigator(
key: navigatorKey,
onPopPage: _onPopPage, => WHY DON´T YOU GET CALLED!
pages: _buildPages(),
);
Am I handling this code in a wrong way, or by adding the custon back button dispatcher, the forementioned function never gets called?

Splash screen appears with delay

I'm trying to implement the flutter Getx plugin and made some splash screen like this one:
class SplashScreen extends StatelessWidget {
const SplashScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetBuilder(
init: SplashController(),
builder: (_) => Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
)
);
}
}
Then I wrote a splashcontroller like this one:
class SplashController extends GetxController {
#override
void onReady() {
// TODO: implement onReady
super.onReady();
myFunctionCalculations();
final Controller _controller = Get.put(controller());
List items = _controller.items;
if (items > 0) {
Get.off(OnePlayerScreen());
}
}
}
Now from my HomeScreen I tap a button to navigate to this splash screen. It does nothing more only
onPressed: () {
Get.to(SplashScreen());
}
The thing is that I want to show the circular indicator while my function is running and when it populates the List items to go to the Items Screen, but when I tap the button to get the splash screen it gets a while until the splash screen appear and then immediately goes to the Items screen, because meanwhile it populated the List. When I don't initialize the function it get immediately to the splash screen and I see the indicator.
Why I get this functionality? I thought it's supposed to show the loading indicator and meanwhile populate the List. But it seems when I tap the button from HomeScreen the function is initialized and when its donde the splashcreen appears.
What am I doing wrong?
Didn't you forget to call update() in your calculation function?
And it seems that your function is async, so you should await it before populating the list