Flutter - navigate back to specific tab on a page with Navigator.pop(context) - flutter

In my app, I have a homepage that has 5 tabs on the bottom. On each tabbed page, there is an app bar that has a '+' symbol as an action, which navigates you to a different page. The navigation with that '+' button to the new page is done with the following code, alongside the Flutter Platform Widgets package:
Navigator.of(context, rootNavigator: true)
.push(
platformPageRoute(
context: context,
builder: (context) => Page1(),
),
);
I use the platformPageRoute feature as an easy way to navigate with a native feel. Now, that works fine to navigate to a new page, but the issue comes when I use
Navigator.pop(context);
to navigate back to the original page. When I use that to navigate back to that original page, it pays no attention to the tab that was selected originally. For example, if I were originally on the second tab on the homepage and then use the '+' button on that tab and then finally use
Navigator.pop(context);
on that new page, it returns the first tab of the homepage. Is there any way of ensuring when I use the above command, it goes to the right tab? I have tried something along the lines of:
Navigator.popUntil(context, '/homepageTab2');
alongside a named route, to return to the correct tab on the homepage, although that returns a black screen. Why might that be? I have also tried using:
Navigator.pushAndRemoveUntil(
context,
platformPageRoute(
context: context,
builder: (context) =>
HomePage(selectedPage: 1),
),
(route) => false,
);
This does not work either, since it returns the selected/correct page tab content, but with the first tab selected. In addition, the other
'problem' for me is that the animation is a 'push' one and that doesn't 'match' with the animation when I have more often used
Navigator.pop(context);
to navigate back to a screen. Is there a way to maybe use pushAndRemoveUntil but then change the animation to match a pop animation?
Thanks!
EDIT:
I have just noticed that with the situation I have described above, it is actually returning the correct screen content when I use Navigator.pop(context); but the tab in the tab bar at the bottom is showing as the first tab, in the second tab's position, essentially duplicating the first tab, until I navigate to a new tab and back, at which time it shows the correct tab in the correct position. I hope that makes sense!

As it turns out, the issue wasn't related to Navigator.pop(context); being used. It was the way I was controlling the selected tab. I'm posting this as an answer incase it helps someone else.
Initially, I created late values for a tab controller and the current selected page, like so:
late TabController _tabController;
late ScrollController _scrollController;
late int _selectedPage;
Then, I created a list of widgets that represented the actual page to display for each selected tab:
List<Widget> _pageWidgets = <Widget>[
Page1();
Page2();
Page3();
Page4();
Page5();
];
Then (and I think this was the bit that wasn't working) I used initState() as follows:
void initState() {
super.initState();
// Initialising a value that allows the 'final' page selector to be changed
_selectedPage = widget.selectedPage;
// Initialising the tab controller
_tabController = TabController(
length: 5,
vsync: this,
initialIndex: _selectedPage,
);
// updating the tab index when a new item is selected
_tabController.addListener(() {
setState(() {
_selectedPage = _tabController.index;
//_tabIndex = _tabController.index;
});
});
// Creating the scroll controller
_scrollViewController = ScrollController();
// Scrolling view to top when a new tab is selected
_tabController.addListener(() {
setState(() {
_scrollViewController
.jumpTo(_scrollViewController.position.minScrollExtent);
});
});
}
I then controlled the page content like this:
body: _pageWidgets.elementAt(_selectedPage),
I'm not 100% sure why this wasn't working, although I believe it would have something to do with the fact that initState() would only be called during the build and therefore placing the functionality inside there would mean changes wouldn't be detected. Either way, my new method, which works perfectly, is:
/// Controls the screen to display first
int _index = 0;
/// Creating a navigation key to control tab bar navigation
final _navigationKey = GlobalKey<CurvedNavigationBarState>();
Then, within the Scaffold() I show the page content like this:
body: _pageWidgets.elementAt(_index),
And finally, within the navigation bar (which is the CurvedNavigationBar() package from pub.dev) I give it a key and the index:
key: _navigationKey,
index: _index,
And this controls it perfectly, showing the correct tab.

Sub-pages of a TabBarView cannot be navigated using Navigator.
You can use TabController to go to your desired tab page after awaiting Navigator.push():
await Navigator.of(context, rootNavigator: true)
.push(
platformPageRoute(
context: context,
builder: (context) => Page1(),
),
);
tabController.animateTo(<index of tab>);

Related

Flutter: push a new page and scroll down to certain widget

I have a web app in Flutter. My app bar has 5 buttons. Four of them scroll within the first page. The 5th one pushes a new page.
There are three different widgets on stage:
page A (HomeView)
page B (ContactView)
the AppBar
When the user is on page A, the buttons of page A should scroll down or up, and the button of page B should push the new view. When the user is on page B (contact) and presses any of the buttons of page A, it should push the home page and scroll to the required position.
I tried to do this with ScrollController.animateTo and Riverpod for state management, but I couldn't do it. The page scrolled down but, for some reason, it scrolled up again and didn't maintain position. I couldn't find any related post on the Internet and keepScrollOffset was not the answer. I also didn't know how to scroll to the desired position when the user is not on 0 (it scrolled down from current position).
After that, I tried to do it with GlobalKeys. It worked, up to a certain point. On page A, everything worked fine. However, from page B to page A, it said the keys are duplicated. I used pop instead of pushNamed as suggested in one of the answers of this post (the only answer that worked for me). The behavior was finally as expected, but I can't use pop since I have more pages on the website, so I can't ensure that the user is coming from page A. I tried with pushReplacement as the same answer gives that for an alternative option. It doesn't work now. When trying to go from page B (contact) to page A, it throws error Unexpected null value.. Furthermore, I need to keep the back button functionality since it is a website. I would prefer, then, to not pop the current page from the stack.
The page A (HomeView) is built like this inside of a StatefulWidget:
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
HomeHeader(key: HomeView.homeKey),
WhatWeOffer(key: HomeView.servicesKey),
WhatWeDo(key: HomeView.referencesKey),
WhoWeAre(key: HomeView.aboutKey),
const LetsGetInTouch(),
],
),
);
}
The GlobalKeys are defined like this inside of the Page A (HomeView) widget:
static GlobalKey homeKey = GlobalKey(debugLabel: 'homeHeaderKey');
static GlobalKey servicesKey = GlobalKey(debugLabel: 'whatWeOfferKey');
static GlobalKey referencesKey = GlobalKey(debugLabel: 'whatWeDoKey');
static GlobalKey aboutKey = GlobalKey(debugLabel: 'whoWeAreKey');
This is how I implement the navigation to page A:
if (ModalRoute.of(context)?.settings.name != '/') {
Navigator.pushReplacementNamed(
context,
Navigation(context).routes["/"]!,
);
}
Scrollable.ensureVisible(
HomeView.homeKey.currentContext!,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
And this is how I implement the navigation to page B:
Navigator.pushReplacementNamed(
context,
Navigation(context).routes["/contact"]!,
),
Maybe GlobalKey is not the right approach to my problem? Is there any easier way to push a new page and scroll down to a widget?
EDIT:
I think the Unexpected null value problem was caused because the view was not built yet when I was trying to call it. I solved it by making the functions async and waiting before using the keys:
TextButton(
child: Text('home', style: style),
onPressed: () async {
if (ModalRoute.of(context)?.settings.name != '/') {
Navigator.pushReplacementNamed(
context,
Navigation(context).routes['/']!,
);
await Future.delayed(const Duration(milliseconds: 500));
}
Scrollable.ensureVisible(
HomeView.homeKey.currentContext!,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
),
However, this does not solve my problem of the back button. As I use pushReplacementNamed instead of pushNamed, I can't go back. Is there a more optimal way to do this, where I can keep the back button functionality?

How to programmatically dismiss a Showcase view in flutter?

I am using showcaseview package for displaying a message in a screen. It is dismissed when we click anywhere on the screen. But if the user came to that page and use the backbutton for going back, the showcase is not getting dismissed. Is there any way to dismiss the showcaseview other than touching the screen.
I highly think from what I got from your question that the show case view starts every time you go to that page, so you can pass a parameter to choose to show case view or not when open the page like :
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (_) => const Detail(showCaseView:false),
),
);
and in Details screen where you use show case view :
#override
void initState() {
super.initState();
if(isShowCaseView){
WidgetsBinding.instance
ambiguate(WidgetsBinding.instance)?.addPostFrameCallback(
(_) => ShowCaseWidget.of(context)
.startShowCase([_one, _two, _three, _four, _five]),
);
}

Navigate to another tab from within MaterialPageRoute

I expected this issue to have a simple solution but I didn't find yet any...
I have few tabs in my app, in one of them I open another screen using
Navigator.push(context, MaterialPageRoute(...
Once user clicks on a button in that screen I want to pop it and navigate to another tab.
I tried to pass TabController to the relevant tab and its child screen, but this doesn't seem like the simplest solution, and also not easy to accomplish since the controller is not yet defined:
tabController = DefaultTabController(
body: TabBarView(
children: [
FirstTab(
tabController: tabController // <- tabController is not defined yet at this point:(
Is there any "global" function to reset the app's "entire" route so it will both pop MaterialPageRoute and navigate to specific tab ?
You can use Navigator.of(context).pushReplacement
The solution I found is to call Navigator's push synchronously and check for its returned value. Then when I want to navigate to another tab I simply send true indication in Navigator's pop.
This is how my navigation method looks like, notice I had to add a short delay before navigating to another tab, not sure why, but it didn't work without it:
_navigateToDetailsScreen() async {
bool shouldNavigateToHomeTab = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailsScreen()),
));
if (shouldNavigateToHomeTab) {
Future.delayed(const Duration(milliseconds: 500), () {
DefaultTabController.of(context)!.animateTo(0);
});
}
}
And this is how I call pop:
Navigator.of(context).pop(true);
This looks like the simplest solution for me, and so far I didn't find any issues with it.

How to navigate to previous page using device back button - Flutter

When I press the device back button, it navigate to the homepage but not to the previous page.
I would like to know how to navigate to the previous page using the the device back button.
Thank you
You might have used Navigator.pushReplacement which destroys the previous page and creates the new page.However if you use Navigator.push pressing the back button will navigate you to the previous screen.
Example:
Container(
child: Center(
child: TextButton(child: Text("Next page"),onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => Page2(),)); // back button will navigate to previous screen
//Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Page2(),)); // back button will not navigate to previous page
},),),),
It was a Materialapp widget problem.
I solved the problem by deleting the Materialapp widget that was in one of the pages.
This occurs because the previous navigation was not saved in the route stack.
What you should do is this:
Widget A
Navigator.push(context, WidgetB());
Inside Widget B, press the device button and you will return to widget A.
It is important that in widget A you are not using replacement or another that removes widget A from the stack.
You can use:
Navigator.pop(context);
or
Navigator.pushNamed(context, '/routeName');

How to route file to a different page in flutter

I'm making an app with Dart/ flutter now and having an issue with routing pages.
The app is basically the matching app, and I want to add the functionality that a user can edit their profile. Currently, we have 4 stateful widgets: Match(), Message(), MyProfile() and EditProfile(). In the bottom navigation bar, I put three widgets, Match(), Message(), and MyProfile(); when the user wants to change the profile information, the person goes to MyProfile() and click "edit profile" button, which takes the user to EditProfile. After the user changes the information, I want to rout the page to MyProfile() for allow the user to check the profile info.
The code below is some part of the Navigation bar.
class _NavigationHomeState extends State<NavigationHome> {
int _currentIndex = 0;
final List<Widget> _children = [
Match(),
Messages(),
MyProfile(),
];
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
and I put the code below in one of the button of the EditProfile().
Navigator.of(context).pushNamed('/myProfile');
Then gave me an error saying
Error: Could not find the correct Provider above this
MyProfile Widget
This likely happens because you used a BuildContext that does not
include the provider of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route,
then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that MyProfile is under your
MultiProvider/Provider. This usually happen when you are
creating a provider and trying to read it immediatly.
I guess I'm getting this error because we set the _currentIndex in the bottom navigation bar as 0, which is Match(), so Navigator.of(context).pushNamed('/myProfile'); trying to take the user to the Match() page (?)
How can we take the user to the MyProfile page after the person save and click the button on EditProfile()?
Try using MaterialPageRoute instead of named routes.
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
If you going to EditProfile from MyProfile widget then I suggest to just pop the screen. As this will not reload complete screen, you can always pass data while doing pop.
RaisedButton(
onPressed: () {
// The Yep button returns "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
);
Another option is to use pushNamedAndRemoveUntil method.
navigator.pushNamedAndRemoveUntil('/MyProfile', ModalRoute.withName('/MyProfile'));
I guess I'm getting this error because we set the _currentIndex in the
bottom navigation bar as 0, which is Match(), so
Navigator.of(context).pushNamed('/myProfile'); trying to take the user
to the Match() page (?)
Not sure about your error but you can pass data as argument while doing pushNamed.
RaisedButton(
child: Text("Navigate to MyProfile"),
onPressed: () {
Navigator.pushNamed(
context,
'/myProfile',
arguments: [
'Extract Arguments Screen',
'This message is extracted in the build method.',
],
);
},
),