How to handle back button of android in flutter - flutter

I have navigate from one page to another page like from a button which is on the page
1st Page <---> 2nd Page <---> 3rd Page <---> 4th Page
If i am on the any page after clicking on the back button of android i want to navigate to the homepage instead of going to the previous page.
Like if i am on page 2 if i press back button i go to the page 1 but i want to pop all the elements on the stack and go to the main home page same for all.
Future<bool> pushPage(BuildContext context) {
// return Navigator.of(context).pop(true);
return Navigator.of(context).pushNamedAndRemoveUntil('/quiz2',
ModalRoute.withName("/landingPage"));
}
class _Quiz1State extends State<Quiz1> {
bool hasSolved = false;
int solvedOption = 0;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
pushPage(context);
return false;
},
child: Scaffold(
body: Stack(
alignment: Alignment.topCenter,
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Color(0xffBD00FF),
Color(0xffF1509E),
],
),
),
),
mainQuiz(),
Container(
child: uiElements(context),
margin: EdgeInsets.only(top: 16),
),
],
),
),
);
}
mainquiz() function is the 2nd Page Function

We know that Navigator.pop() removes to topmost page available on the stack. There is another function named Navigator.popUntil() which will keep on popping out the pages from the stack until the desired page is reached.
For that first (If you have not done this yet) you need to give names to all your pages. These will act like an ID card for the page when you reference them in you functions. For that on the topmost part of your tree where the widget MaterialApp is located, do like this :
MaterialApp(
home: new Screen1(),
routes: <String, WidgetBuilder> {
'/screen1': (BuildContext context) => new Screen1(),
'/screen2' : (BuildContext context) => new Screen2(),
'/screen3' : (BuildContext context) => new Screen3(),
'/screen4' : (BuildContext context) => new Screen4()
},
)
Basically what you do here is you pre-define the names for all the routes you will be taking in your app.
Now say the above example's navigation works like this :
Screen1 -> Screen2 -> Screen3 -> Screen4
Now say I am at Screen4() ..... then my current stack will be [Screen4,Screen3,Screen2,Screen1] .....(latest first, oldest last), and on hitting the back button I need to directly pop out to Screen1().... (Home page in your case). Then instead of out simple Navtigator.pop() I will do something like this :
Navigator.popUntil(context, ModalRoute.withName('/screen1'));
This will keep popping out the widgets until it reaches screen1.
Hope this solves your porblem.
NOTE:
Once you have preDefined all your routes at the start, instead of calling Navigator.push(context,MaterialPageRoute((context) => Screen1());
You can do this :
Navigator.pushNamed(context, '/screen1');
Edit :
Say if your HomePage was not already present in stack and you want to show it after popping out all the screens. You can do :
Navigator.of(context).pushNamedAndRemoveUntil('/homepage', (Route<dynamic> route) => false);
This will add Homepage() after removing all the other screens present in the stack. [" (Route route) => false " will pop out all the screens from the stack. ].
Then if you want to pop out the screen only till a given screen is reached(say till we reach screen1) you can do something like:
Navigator.of(context).pushNamedAndRemoveUntil('/homepage','/screen1');

After 1st page use Navigator.pushReplacement instead Navigator.push
You can also use this to go first page:
Navigator.of(context).popUntil((route) => route.isFirst);

Related

Two scaffolds into one page because of navigation in flutter

I have an issue with the navigation process in my Flutter app.
First, I have a home page that has a button. When I press search, I go to the search page, obviously, and the results will appear on the home page again.
If no results were found, a dialog would appear that says "Try again" and has an OK button.
I want to press the OK button to navigate to the search page again. But there is a problem with the scaffolds like the image below. How can I fix this?
And my home page code, for alert dialog is like this:
Widget build(BuildContext context) {
print(widget.selectedURL);
return MaterialApp(
home: Center(
child: FutureBuilder<List<Pet>>(
future: API.get_pets(widget.selectedURL),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Pet>? pet_data = snapshot.data;
print(pet_data?.length);
return Padding(
padding: const EdgeInsets.all(8.0),
child:(pet_data?.length == 0)
? AlertDialog(
title: const Text("Failed"),
alignment: Alignment.topCenter,
content: const Text(
'No Pets Found. Please try different search options.'),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const Search())),
)
],
)
: ListView.builder(
//showing the list of results
)
The problem is you are wrapping everything inside a MaterialApp.
MaterialApp defines a Navigator property, which means it should be at the top-level of your application and it should be unique. If you have two different MaterialApp they will use their own Navigator, hence they will not share routes. See more informations here MaterialApp.
The usual design is:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: YourWidgets(
[...]
)//YourWidgets
)//Scaffold
);//MaterialApp
}
Try removing MaterialApp, or changing your MaterialApp to something else, like Material if you really need additional customization.

Persistent Bottom Navigation Bar does not work with named routes

I'm new to flutter and I'm trying to implement a persistent bottom navigation bar with the "persistent_bottom_nav_bar 4.0.2" plugin.
The issue I'm running into is, that I have to declare my AppBar in the page that implements the bottom bar, since that is where my scaffold is and the screens are just widgets loaded by the plugin into this scaffold. Now, if I'm navigating to a different page from one of the tabs, I want the AppBar to show a back button. For that I need to add an AppBar in the page I'm navigating to, but then I have two AppBars stacked. Thus, I need to hide the AppBar from the bottom bar page when I push pages into the navigator stack of the active tab.
Currently I'm trying to get the name of the route with ModalRoute.of(context)?.settings.name and compare it with the name of the route of the page that holds the bottom bar, and if they're not equal I can pass an argument back and hide the app bar. (I'm open for better solutions, that's the first I came up with). The problem is, that the named route does not work on the navigators of the different tabs. I'm getting something like this as the name: /9f580fc5-c252-45d0-af25-9429992db112.
My HomePage looks like this:
#override
Widget build(BuildContext context) {
print(ModalRoute.of(context)?.settings.name); // this gives me the correct route name
return Scaffold(
appBar: _showAppBar ? MyAppBar("") : null,
body: PersistentTabView(
context,
controller: _controller,
screens: _buildScreens(),
items: _navBarsItems(),
confineInSafeArea: true,
popAllScreensOnTapOfSelectedTab: true,
popActionScreens: PopActionScreensType.all,
navBarStyle: NavBarStyle.style6,
));
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
PersistentBottomNavBarItem(
routeAndNavigatorSettings: const RouteAndNavigatorSettings(onGenerateRoute:
router.generateRoute),
icon: const Icon(Icons.add_circle_outline),
title: ("Add"))
];
}
List<Widget> _buildScreens() {
return [
const AddPage(),
];
}
On the AddPage (which is one of the tab views and only a widget without scaffold) I want a button, that navigates to a different screen. If that happens I want to hide the AppBar from the home_page and show the AppBar of this page to have the back navigation.
I do the navigation like this:
child: IconButton(
icon: const Icon(Icons.add),
color: Colors.white,
onPressed: () {
Navigator.pushNamed(context, AddCollectionRoute, arguments: 'test');
RouteSettings? routeSettings = ModalRoute.of(context)?.settings;
},
),
But here I now have "/9f580fc5-c252-45d0-af25-9429992db112" as name and null as argument.
Routes are generated like this:
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case AddCollectionRoute:
return MaterialPageRoute(builder: (context) => const AddCollectionPage());
case HomeRoute:
return MaterialPageRoute(builder: (context) => const HomePage());
}
}
So my questions are:
Why are the routes in the tabs anonymous and why are the arguments gone? Up until the HomePage everything works as expected.
Is there a better approach to hide the AppBar on navigation?

Flutter: Remove stored values from a page when navigate to

I have a listview builder with a list in the home page and when an item of the list is pressed a second page will open with details of that list. The details are basically another list of cards on each card I have a counter starts with zero value, when user press on each card the counter value will increase.
Now the issue I face is when user go back to home page and then again click on a the same item to go to the second page, they will find the counter values as there were (i.e not zeros), although I did not include any mechanism to store these values (i.e no sharedpreferance or so).
These values of the counter will not go back to zeros, unless the app is closed and open again.
Is there a way to force the app to reset these values to zeros as they were originally once the user go back to home page.
This is the code of the listview.builder of the second page:
child: ListView.builder(
itemCount: widget.cities.attractions.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
setState(() {
widget.cities.attractions[index].localrank++;
});
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(widget.cities.attractions[index].name),
Center(
child: Text(
'${widget.cities.attractions[index].localrank}')),
],
),
)),
);
},
),
You have to main a list of size equal to no of cards you have on your screen. This can be bool list (List) and initialise the List like
int noOfCards; // This variable will have the no of cards on your screen
List<bool> isClicked = new List.filled(noOfCards, false);
Now make the value to true when any item is clicked
For Example :- if item on some index is clicked then
if(!isClicked[index]){
isClicked[index] = true;
// your code to increment the value
}
// navigate to your second page
I got it resolved by others. The solution is basically to use the WillPopScope widget to set all "localranks" in widget.cities.attractions to zero when the back button is pressed. For that, we have to define the method in the Widget class and then to use forEach to access each attraction and set the localrank to zero from there.
This is the code:
Future<bool> moveToLastScreen() async {
setState(() {
widget.cities.attractions.forEach((attraction) => attraction.localrank = 0);
});
Navigator.of(context).pop();
return true;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => moveToLastScreen(),
child: Scaffold(...),
);
}

Is there a simple way to make android back button act exactly like app bar back button?

I want the hardware back button act exactly like my app bar back button always. I want it for all my screens. WillPopScope seems not easy to use because my appbar will be always the child of the WillPopScope and therefore I would need to pass data to parent for many different cases.
Edit(more details):
In registration page of my app, if the registration process is on going I am blocking the user interaction with the app by using IgnorePointer but of course this does not work for hardware back button.
Also I designed my appbar back button according to the state of the registration page. So there are at least 3 cases for appbar back button. I want all to apply to the hardware back button too. The problem is I am in a child widget therefore I don't want to call function for each case and update the state of the parent widget. It is not useful and It is very painful to do.
If I understood your problem correctly, I have something that might help you. I've implemented this code on one of my projects. However, I have used Provider and Bloc for state management.
signup_page.dart
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: ChangeNotifierProvider<SignupProvider>(
create: (context) => SignupProvider(),
child: BlocProvider<SignupBloc>(
create: (context) => _signupBloc,
child: BlocListener<SignupBloc, SignupState>(
listener: (context, state) {
if (state is SignupCancelled) {
_closeSignup();
} else if (state is SignupSuccessful) {
_closeSignup(delay: 1000);
} else if (state is SignupInitial) {
_returnToCredentials();
} else if (state is SignupProceededToDetails) {
_proceedToDetails();
}
},
child: Scaffold(
appBar: AppBar(
title: const Text("Signup"),
),
body: SafeArea(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: <Widget>[
const SignupCredentialsForm(),
const SignupDetailsForm(),
],
),
),
),
),
),
),
);
}
Future<bool> _onWillPop() async {
int page = _pageController.page.toInt();
_signupBloc.add(
BackButtonPressed(page: page),
);
return false;
}
_closeSignup({int delay = 0}) async {
await Future.delayed(Duration(milliseconds: delay));
Navigator.of(context).pop();
}
_returnToCredentials() {
_pageController.animateToPage(0,
duration: const Duration(milliseconds: 250), curve: Curves.easeIn);
}
_proceedToDetails() {
_pageController.animateToPage(1,
duration: const Duration(milliseconds: 250), curve: Curves.easeIn);
}
If you look at my code, I have a PageView consisting of 2 pages. The PageView pages both use AppBar, and contains the leading back button on the top left.
Normally, pressing the back button on the appbar, or the hardware back button will lead to closing the page, and navigating back to the previous page.
However, since I have WillPopScope, on signup_page, that is overriden. Hence, I'm able to control what I want to happen when the back button (appbar or hardware) is pressed. Which you can see, I can move to the next page view using _proceedToDetails(), or return to the first form using _returnToCredentials(), and close the page if I'm in the first form and back button is pressed, using _closeSignup(), similarly, closing the page even when I'm at the last form if the signup is successful.

Navigator gets the wrong context

I have a flutter application which shows a camera, then scans a qr-code using git://github.com/arashbi/flutter_qrcode_reader and on the call back, I want to navigate to another screen to show the information I get from some query. The problem is, it seems Navigator get a wrong context, so instead of full page navigation, I see the second page pushed inside my first page as a widget.
The build of first page is as follow :
#override
Widget build(BuildContext context) {
Widget main =
new Scaffold(
appBar: _buildAppBar(),
body: Column(
children: <Widget>[
_eventButton ?? new Text(""),
new Center(
child: new Text("Hi"))
],
),
floatingActionButton: new FloatingActionButton(
onPressed:
() {
new QRCodeReader()
.setAutoFocusIntervalInMs(200)
.setForceAutoFocus(true)
.setTorchEnabled(true)
.setHandlePermissions(true)
.setExecuteAfterPermissionGranted(true)
.scan().then((barcode) => _showTicketPage(barcode));
}
,
child: new Icon(Icons.check),
),
);
if (!_loading) {
return main;
} else {
return new Stack(
children: [main,
new Container(
decoration: new BoxDecoration(
color: Color.fromRGBO(220, 220, 220,
0.3) // Specifies the background color and the opacity
),
child: new Center(child: new CircularProgressIndicator(),))
]
);
}
}
The 'Hi' Widget is for debugging, and the navigate method is
void _showTicketPage(var barcode) {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => TicketScreen(barcode)));
}
After getting the barcode, the second screen - TicketScreen - is pushed under 'Hi' Widget. I didn't even know this was possible to do.
I tried to get the context of the screen, save it to a field var, but that didn't help either.
How can I fix it? I ran out of ideas.