How can I change or override android/ios back button's action in flutter - flutter

I need to make a change in my page (changing a bool in setState etc...) when the user presses the back button.
I looked it up and I know people use WillPopScope to override the default "back" action, but in all of the examples they were just popping the page, and I need to do a custom function instead of that, and have no Idea how to do so.
my function looks like this, and I need to run in when the user pressed the back button:
Future<void> backToCats() async{
setState(() {
isLoaded=false;
});
if(isFromSearch){
loadCategories();
}
setState(() {
showingBrands=false;
isLoaded=true;
});
}

You can perform custom logic in the onWillPop argument of a WillPopScope widget. Just make sure to return true or false at the end to indicate whether the page is allowed to pop. WillPopScope will activate for the back button, and other actions that automatically navigate back such as swiping from the side of the screen on iOS.
For example, if you need to set a bool to false in your stateful widget, it would look something like this
WillPopScope(
onWillPop: () async {
setState(() => myBool = false);
return true;
}
child: ...
);

Related

How to call a function when returning to view/controller with `Get.back()`?

I am switching from the home view to another using Get.toNamed(Routes.DETAIL). When I want to return from the details view to the home view, I am calling Get.back() (or the user is using the back button of the devices).
Back on the home view, I would like to fetch all data from my database again.
Is there any function that is triggered when I am leaving a few and returning to it, so I can put my logic there?
Thank you
I would suggest you to use Get.offNamed() instead of Get.toNamed() as the offNamed() function will clear the data stored in catch and thus will again call the API declared in onInit() or onReady() lifecycle when returning back to that screen.
In Getx they have a funtion like
Get.back(result:"result");
so in order to trigger some funtion when going back to any page route
try doing this as the document written
final gotoHome = await Get.toNamed(Route.name); // or use the simple one Get.to(()=> Home());
then if you trigger to go back in page you should indicate some result e.g.
from back button in phone using willpopscope or a back button in UI.
Get.back(result:"triggerIt"); // this result will pass to the home.
so in will use
// It depend on you on where you gonna put this
// onInit or onReady or anything that would trigger
someTrigger() async{
final gotoHome = await Get.toNamed(Route.name);
if(gotoHome == "triggerIt"){
anyFuntionYouwantoTrigger();
}
}
for more info about it try to read the documentation.
https://github.com/jonataslaw/getx/blob/master/documentation/en_US/route_management.md
Edited: // Maybe some answer will pop up for better
I do have one but it's not that quite a real practice just a sample
e.g // sample you are now in the current page and this page is also connected to homecontroller or using Get.find() it need to bind the controller to the page;
class BindingHome with Bindings{
#override
void dependencies() {
Get.lazyPut(() => HomeController(), fenix: true);
}
}
then from GetPage add Binding
GetPage(
name: "/currentpage",
binding: BindingHome(),
page:() => HomeView(),
),
so while homecontroller is bind to the current page you are now so
// lets assume this one is put to the CurrentController
final homeController = Get.find<HomeController>();
so while calling back button on ui or willpopscope
when back try to trigger the function from home
gotBackfunction(){
Get.back();
homeController.anyFuntionYouwantoTrigger();
}
No you can't really call a function when doing so.
You should be using callback function just before popping the view.
onBackClick() async {
Get.lazyPut<MainController>(
() => MainController(),
);
controller.allItems.refresh();
Get.back();
}
Here is an example how you can do it without any complications:
WillPopScope(
onWillPop: () async {
await onBackClick();
return Future(() => false);
},
child: Scaffold(
appBar: CustomAppBarWithBack(
title: "Appbar",
OnClickBack: onBackClick,
),
body: Widget(),
),
);

Flutter. How to correctly update state of nested widgets?

I'm new in flutter and I'm trying to implement something like radio group with custom buttons in Android. For this I created StatefulWidget, which hold list of selectable buttons. For every button I was set press listener where I do something like this:
setState(() {
buttons.forEach((button) => button.isSelected = false);
buttons[selectedButtonIndex].isSelected = true;
});
And then my CustomButtonWidget changes color depending on the parameter isSelected .
All this works well. However, I have an additional requirement. I need my RadioGroupWidget to return the selected button type. For this I created a callback :
final ValueChanged<ButtonType> onChanged;
And now my button press listener looking like this:
onTap: () {
setState(() {
buttons.forEach((button) => button.isSelected = false);
buttons[selectedButtonIndex].isSelected = true;
});
onChanged(buttons[selectedButtonIndex].type);
}
Next I get this type of button in my other widget which is using RadioGroupWidget:
CustomRadioGroup(
onChanged: (value) {
setState(() {
buttonType= value;
});
}),
)
As you can see I call again setState. This is what leads to the problem. But I need to do this, because I need to update the state of another widget (for example let's call it InfoWidget) depending on the selected button.
After all these manipulations, the state of the InfoWidget is updated correctly, but the state of the selected button in the RadioGroupWidget does not change. I tried to debug this and I see that at first the parameter isSelected is set to true for the desired button, but after the state of the button I selected is not updated, because its parameter isSelected becomes false. And I don't understand why this is happening.
Please help, I am completely confused.

Flutter - set result to return when the user navigates back

When calling Navigator.pop() a result can be passed to the previous screen. Is there a way to set the result so that if the user navigates back on their own the result is still returned to the previous page?
I could pass an object to the second page and then modify it so that the first page can check it when the second page returns, but I'd rather use the result returned from the Navigator as it's more readable.
Overriding the back button's tap detector as I've seen suggested elsewhere is not an acceptable solution because the user may navigate back in some other way such as swiping or pressing the Android back button.
Yes, you can pass data between screens both ways.
While popping back, send the data you wish to send like this from the second screen
RaisedButton(
onPressed: () {
// The Nope button returns "data" as the result.
Navigator.pop(context, 'data');
},
child: Text('Nope!'),
);
And catch the result in the your first screen like this
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
source
For the other concern where the user is able to go back to the previous screen by pressing the back button, you can wrap your second screen with WillPopScope widget and provide the onWillPop callback. This will override the popping of the second screen and execute the callback where you can define the value you wish to return from the second screen.
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(), // or your widget
onWillPop: () {
return Future.delayed(Duration(microseconds: 0), () {
return Navigator.pop(context, "return-data");
});
},
);
}
Yes, this is possible, when navigating to Screen B from Screen A, make the onTap() functionasynchronous and await the result from Screen B.
Check the code below: It works perfectly:
On Screen A, put this code:
onTap: () async {
// receive the data you are sending from screen B here
final result = await Navigator.push( context);
},
On Screen B, put this code:
onTap: (){
// pass the data you want to use in screen A as the second paramter
Navigator.pop(context,number);
},
I hope this helps

How can I control what is passed to Navigator.pop() when clicking outside of a showModalBottomSheet?

I launch a modal bottom sheet, then use the data that is returned as its future.
var modalFuture = showModalBottomSheet(
// ...
);
modalFuture.then((data) {
// Use data
});
I return data to it from the modal widget with:
Navigator.pop(context, data);
This is working well when completing the modal interaction with a widget I've set up.
I encounter my issue when clicking outside of the modal. Clicking outside of the modal causes the modal to dismiss automatically (with a Navigator.pop(context) call?). I am okay with this closing interaction, but I would like to send data back with it (with a Navigator.pop(context, data) call). Is there anyway to override the implicit pop when clicking outside of a showModalBottomSheet modal?
You can wrap your ModalWidget with WillPopScope. You can see the example below
WillPopScope(
onWillPop: () async {
Navigator.pop(context, data);
return true; // return true if needs to be popped
},
child: ModelWidget(
…
),
);
This will ensure that Navigator.pop is called when auto popped using the back button.

How to hide alertDialog after one second with Navigator.of(context).pop()

I am trying to auto-hide an alertDialog after one second
This is the code:
Widget popupWidget(BuildContext context, ...) {
Future.delayed(Duration(seconds: 1), () {
Navigator.of(context).pop();
});
return AlertDialog(...);}
What works:
I can click somewhere else on the screen to close the alertDialog
I can wait for one second and it closes automatically
The bug:
If, after (exactly?) one second, I click somewhere else on the screen (which closes the alertDialog), the Future.delayed(...) will not hide the alertDialog, but the whole screen
I unsuccessfully tried making showDialog async, also tried the line
Navigator.of(context, rootNavigator: true).pop();
I think I've found a workaround with:
bool popupIsActive = true;
Future.delayed(Duration(seconds: 1), () {
if (popupIsActive) Navigator.of(context).pop();
});
and
showDialog(...).then((_) {
popupIsActive = false;
});
You might want to ignore any taps outside the dialog in order to prevent it from dismissing and just wait for the future to complete. You can set barrierDismissible to false in showDialog().