I am really confused about how Flutter manages multiple navigation stacks at once, and switches between them with the BottomNavigationBar. So far I have managed to swap the body of my root Scaffold with Widgets, and can also change state on the Scaffold to change the AppBar title and set the index of the BottomNavigationBarItem. I can also see how setting routes on the MaterialApp will allow me to push and pop other screens. The problem is that when you have a tab bar navigation pattern, you want to be able to manage separate stacks. If I am 3 levels deep into a section and switch tabs, I expect to be in the stack for that particular section, and be able to jump between them.
How is this managed with Flutter? Ideally, the app should have a Main App Scaffold to define the BottomNavigationBar, the Drawer, and the main logic for switching between the different root level screens. These Screens should then manage their own AppBar and related actions for that section.
I'd really like to understand this before I go any further with Flutter, because right now, the thought of having to manage everything with one Scaffold fills me with dread.
I'm guessing that when you use Navigator it uses the Context to keep the stacks within the context of the screen that initiated the push, but I still don't understand how it keeps the Scaffold separate.
Any insight greatly appreciated.
This wonderful article gives me the answer: https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386
You need to use an IndexedStack for your main section switcher, and then use a Widget that returns a Navigator for each of the main sections. This Navigator sets the routes for that section (if known), but could just be the root.
I don't know if there is already a navigation pattern, or naming convention used by the Flutter community for this, but I have used $(Section)Navigator. Here's an example of a StatelessWidget I created for my test:
class DashboardNavigator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
settings: settings,
builder: (BuildContext context) {
switch (settings.name) {
case '/':
return DashboardRoot();
case '/login':
return ScreenLogin(destination: allDestinations[0]);
default:
return DashboardRoot();
}
},
);
},
);
}
}
This was mainly taken from Hans Muller's article which has been a great help.
Related
Is it possible to use a separate Navigator in a pop up window? Separate BuildContext?
I have a working app (APP1) and I would like to show it in a new one (APP2) (e.g. on a button press to open the existing app). APP1 has multiple pages.
I was able add the APP1 as an dependency to APP2 and load the main page in a popup dialog (Alert dialog).
The issue happens when I try to navigate through the APP1. If I click on the button in the APP1 it changes the page in the whole new app.
I would like to have separate navigation in the pop up dialog, so that the included app Navigator works only in the pop-up dialog.
What would be a preferred/possible way to achieve this?
Here is a small example:
Soure code
The example consists of 2 apps (APP1 and APP2). APP2 includes APP1 and shows it in a pop up dialog.
You can add a nested Navigator (if you're using Navigator 1.0 in Flutter) by adding the Navigator widget inside your dialog component, that way you manage the navigation of pages within that dialog itself, create nested routes and navigate only within that navigation stack.
You can do something like:
class NestedDialog extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: <ADD_UNIQUE_KEY>, // add a unique key to refer to this navigator programmatically
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
// here you return your own page widgets wrapped
// inside a PageRouteBuilder
}
)
);
}
}
Then you can even use Flutter's own showDialog and load your custom dialog widget like so:
showDialog(context: context,
barrierDismissible: true,
builder: (BuildContext cxt) {
return AlertDialog(
content: NestedDialog()
);
});
Something along those lines. Give that a shot. Here's a Github Gist you can run on DartPad.dev so you can see what I mean. You don't have to create two apps; just distribute your app into two main widgets, and the child pages go under each main widget's child pages; See gist https://gist.github.com/romanejaquez/d769e6e766fbacb2f5c166dd3bceab51. Run it on DartPad.dev.
So this is the case, I have two MaterialApps in a Stack widget, I made it like that so they have separate navigation routes. The problem is, I need to navigate to a page which is on MaterialApp 1 from MaterialApp2.
Even clearer example: Button is on MaterialApp 2, I need to press that button and page on MaterialApp 1 shows up. How do I do this? Whenever I use Navigator.of(context).push() on MaterialApp 2, it of course changes the page on that MaterialApp...
It's not necessary that a Navigator.push() called in a specific MaterialApp widget will be doing push operation on that app's stack. Which MaterialApp's stack to choose depend on the Buildcontext you pass in the push function.
You can do that by saving the BuildContext of both materialApps globally and using whichever you want in Navigator.push().
Wrap the children of both MaterialApps in Builder widget to extract the BuildContext of both. Say of app1 it's context1 and app2 it's context2.
Save both of them globally using static variables in a different class say "Data".
Now while calling Navigator.push() anywhere in your app, instead of simply putting that widget's context in the context parameter use the contexts of the materialApps you saved earliers.
Say you want to push a page on MaterialApp1's stack, do
Navigator.push(Data.context1,/*Rest of the builder function as it is*/);
Also instead of using two different MaterialApps just for getting two seperate navigators, you can try using the Navigator widget.
I have a Tabbar with two tabs. The Tabbar has its own Appbar which is used by both tabs. How do I have the sort button run custom (set state) functions inside each tab's stateful widget? Alternatively, is it possible for each tab to have its own unique Appbar? I am not sure what best approach is here.
Example method inside one of the tabs:
class _TabAState extends State<TabA> {
void sort() {
setState(() {
myList.sort((a, b) => b.dueDate.compareTo(a.dueDate));
});
}
...
Sort button:
IconButton(
icon: Icon(Icons.sort),
onPressed: () => DefaultTabController.of(context).index == 0
? TabA.sort() // Does not work
: TabB.sort(), // Does not work
),
Regarding your concern if this approach is the best approach:
From the documentation
The Scaffold was designed to be the single top level container for a MaterialApp and it's typically not necessary to nest scaffolds.
Based from this statement, I suggest that it's probably not a good idea to nest multiple Scaffold inside another Scaffold. This is related to your concern, whether to have nested AppBar inside a Scaffold, given that you can only have one AppBar per Scaffold.
I personally do not recommend the calling static or non static method from other Flutter Widget classes (eg. widget that serves as a standalone screen for your app) for executing the business logic of your application. Perhaps, you can continue implementing the business logic of your application using widgets such as ScopedModel and architectural patterns such as BloC, Redux or Mobx.
Further reading:
https://api.flutter.dev/flutter/material/Scaffold-class.html
https://www.didierboelens.com/2019/04/bloc---scopedmodel---redux---comparison/
How to call method from another class in Flutter(Dart)?
I want the button to display on top of the GraphQL provider widget, and still be functional as it is going to be used for routing. I want both to run simultaneously(or as close as possible so it doesn't ruin the UI when loading).
I've tried running both of the widgets at the top but they do not run as I would like them to.
void main() {
runApp(LoginPage());
runApp(Login());
class LoginPage extends StatelessWidget
class Login extends StatelessWidget
I thought the output would be the Login widget layered on top of LoginPage but it was just the Login widget expanded without the LoginPage widget running in the background.
From the runApp documentation:
Calling runApp again will detach the previous root widget from the screen and attach the given widget in its place.
And:
If you wish to align your widget to one side of the screen (e.g., the top), consider using the Align widget. If you wish to center your widget, you can also use the Center widget
So, as #Nae said in the comments, you can use Stack for that. Like:
Stack(
children: <Widget>[
LoginPage(),
Login(),
]
)
Let's say I have a container on one side of the screen which I want to be present at all times even during navigation through different screens.
Is there any way by which I can achieve this? If yes, how?
This is an interesting question. I can give you a solution but don't know if its the correct way to do that.
In the MaterialApp widget there is a builder parameter which gives whole screen view as a parameter and can be wrapped up by another Widget. Here is an example.
MaterialApp(
....
builder: (context, widget) {
return Stack(
children: [
_myPersistantWidget(), // you can add your widget. May be inside Positioned widget?
widget //this is the whole screen that we are wrapping in the stack
],
);
},
....
);