Persistent BottomNavigationBar with Routing in Flutter - flutter

I have a hard time implementing a persistent BottomNavigationBar in Flutter. My goal is to create a app with several screens and therefore several routes (minimal example):
I found this medium article and after struggling a bit with the implementation, I thought that I found the perfect solution. BUT as I wanted to implement a logout function that sends the user back to the LoginScreen the routing doesn't work as expected...
As you see in the gif, the problem occours after clicking on the logout button. Instead of navigating back to the LoginScreen, the LoginScreen get's embedded into the MainScreen with the BottomNavigationBar.
How can I change this behaviour? I thought I would remove all routes with pushAndRemoveUntil...
// Navigate back to the LoginScreen (this doesn't work as expected...)
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
(Route<dynamic> route) => false);
Here is a minimal reproducable example: https://github.com/klasenma/persistent_bottomnavigationbar

After several attempts, I managed to solve the problem. I needed to save the context of the MainScreen (index.dart -> holds the BottomNavigationBar).
class ContextKeeper {
static BuildContext buildContext;
void init(BuildContext context) {
buildContext = context;
}
}
lib/screens/main/index.dart:
#override
void initState() {
super.initState();
ContextKeeper().init(context); // Save the context
}
Then change
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => LoginScreen(),),(Route<dynamic> route) => false);
to
Navigator.of(ContextKeeper.buildContext).pushNamedAndRemoveUntil(LoginScreen.id, (route) => false);
and it work's.

Related

Flutter: How to pass data between screens?

How can I change the visibility of a button on screen "X" from a button on screen "Y".
One popular approach (using the provider architecture) would be something like this:
Define a provider that handles all the logic and holds your data:
class MyProvider extends ChangeNotifier {
bool showMyButton = false;
MyProvider() {}
void showButton() {
showMyButton = true;
// This line notifies all consumers
notifyListeners();
}
void refresh() {
notifyListeners();
}
}
To access the provider everywhere you need to register it:
void main() => runApp(
// You can wrap multiple providers like this
MultiProvider(
providers: [
ChangeNotifierProvider<MyProvider>(create: (_) => MyProvider()),
],
child: const MyApp(),
),
);
On the button that you want to control you can use a Consumer to listen to the providers values:
Consumer<MyProvider>(builder: (_, model, __) {
return Visibility(
visible: model.showMyButton,
child: MaterialButton(...),
);
})
Now in your second screen you can access that provider with:
Provider.of<MyProvider>(context, listen: false)
.showButton();
However you might have to call notifyListener one more time when returning from screen Y to screen X:
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ScreenY()));
Provider.of<MyProvider>(context, listen: false).refresh();
Keep in mind that there is a lot more to provider so please have a look at their official docs.
Also be aware of the fact that there are easier ways to just pass data between screens but you will often arrive at a point where you will need a better way of managing state and provider provides just that ;)
You can pass the data via push and pop of navigation. Or else use ChangeNotifier class to notify the state of button easily.

Flutter: How to perform an actions after Navigator.pushNamedAndRemoveUntil() has completed

In my Flutter app, I need to clear the Navigator stack and get back to the Home page when a certain button is pressed. To achieve that I've used Navigator.pushNamedAndRemoveUntil(context, "/", (r) => false);. I also need to call a function after the navigation has been completed, meaning that I'm now on the Home page.
I've tried calling the .whenComplete() method on Navigator.pushNamedAndRemoveUntil(), but it doesn't seem to work.
Thanks in advance.
I'd say use your function inside dipose(), so it's called when the widget is removed from the tree as you navigate to another screen.
class _MyPageState extends State<MyPage> {
// ...
// Some code
#override
void dispose() {
// Insert your function here
super.dispose();
}
// ...
}
Using didPush() method
This can be used to call your function after navigating to another screen because it returns when the push transition is complete. However, you have to do it with pushAndRemoveUntil() instead of pushNamedAndRemoveUntil(). So, you can create a PageRoute which provides didPush() method.
// Create your route
MaterialPageRoute route = MaterialPageRoute(
builder: (context) => HomePage(),
);
// Push the route onto the navigator, and then remove all the previous routes
Navigator.pushAndRemoveUntil(context, route, (r) => false);
// This returns when the push transition is complete.
route.didPush().whenComplete(() {
// Insert your function here
});
You can try as well as this code And please let me know.
gotoNewPage(context) async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
NewPage()));
/// Your logic is here.
}

Dispose is not being called

I've got a stateful Widget that's on the navigation stack; HomeScreen()
For some reason, when it should be removed from the stack, it doesn't appear to be calling dispose().
I remove HomeScreen from the navigation stack in two ways:
1) Using Navigator.pushReplacement()
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (context, a1, a2) => GroupScreen()));
2) Using Navigator.of(context).pushAndRemoveUntil()
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LandingScreen()),
(Route<dynamic> route) => false);
The dispose method in HomeScreen() is overridden with the following:
#override
void dispose() {
print("dispose called");
_timer.cancel();
print('Timer has been disposed');
super.dispose();
}
and it's apparent that it's not being called as the print statements are not output to the console.
It may be worth mentioning that the HomeScreen has a StreamBuilder, but I'm not too sure if that would impact things.
Any help would be much appreciated, thank you.
Nevermind, it turns out that I was using "MaterialApp" for one of the widgets in the build method rather than "Material", which was causing the issue.

Dart & Flutter - Passing data across screens. NoSuchMethodError being caused by widget within MaterialPageRoute()

I tried passing data from a filter page to the home page, but keep getting the following error.
Error message on console - NoSuchMethodError being caused by widget within MaterialPageRoute()
//Radio button values to select user's gender on Filter Page
enum PrayditatorGender { Female, Male }
PrayditatorGender pGender;
//Radio button values to select Prayditation category on Filter Page
enum PrayditationFilter {
All,
Family,
Fellowship,
GodlyWisdom,
GoodSuccess,
HealthAndSafety,
}
PrayditationFilter pFilter = PrayditationFilter.All;
//Code to push the data from Filter Page to Home Page
Navigator.push(context, MaterialPageRoute(
builder: (context) {
PrayditatorHomePage(
pGender: pGender,
pFilter: pFilter
)
));
//Code to handle the data on Home Page
class PrayditatorHomePage extends StatefulWidget {
final PrayditatorGender pGender;
final PrayditationFilter pFilter;
PrayditatorHomePage({this.pGender, this.pFilter});
#override
_PrayditatorHomePageState createState() => _PrayditatorHomePageState();
}
class _PrayditatorHomePageState extends State<PrayditatorHomePage> {
#override
Widget build(BuildContext context) {}
Your syntax is wrong, you're not supposed to be having this issue, this code worked with no issues:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PrayditatorHomePage(
pGender: pGender,
pFilter: pFilter,
),
),
);
Thank you all for taking time to view/comment. Bug has been busted and code working effectively!
Syntax was all correct, however, a static parameter was inappropriately put in the place meant for a dynamic parameter. After all, lessons learned.

Wait for page transition to finish

How can I detect if a page transition was finished in flutter?
Background: When I pop to a page, I want to start a function after the page transition has finished.
You can register a callback when you push your new route that can also contain data from the popped route, if you need, for example, to pass data from it.
Navigator.of(context).push(/*Some route*/).then((data) {
// You can call your function here. data will be null if nothing is passed from the popped route
});
If you want to pass data from the popped route, just do it like so
Navigator.of(context).pop(someData);
Edited
If you want to know when the transition actually ends, you can register a callback listener for it as well
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => RouteWidget())
..completed.then((_) {print('transition completed');},
),
);
Its also possible to wait for the push transition if you keep a reference to the route and using the didPush() TickerFuture
MaterialPageRoute route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
await route.didPush(); // you could also use then instead of await
// ROUTE FINISHED TRANSITION HERE
This can be used if you have multiple stacked navigators and you want smoother transitions between them. Like wait for Navigator 1 to finish the push before you pop the currently active Navigator 2 to show the page of Navigator 1
Another solution
is to await the animation to finish on the second page (this is usefull if you want to update the page after the animation transition)
first page:
...
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(milliseconds: 3000), //optional but useful for testing
pageBuilder: (_, animation, ___) => SecondPage(
pageBuilderAnimation: animation,
)));
...
second page:
class SecondPage extends StatefulWidget {
const SecondPage ({Key? key, this.pageBuilderAnimation});
final Animation<double>? pageBuilderAnimation;
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
bool isTransitioning = true;
#override
void initState() {
super.initState();
if (widget.pageBuilderAnimation != null)
widget.pageBuilderAnimation!.addListener(() {
if (widget.pageBuilderAnimation!.isCompleted)
setState(() => isTransitioning = false);
});
else
isTransitioning = false;
}
...
}