How to use multiple navigators - dart

I currently have a MaterialApp in my flutter application which makes the use of the Navigator extremely easy, which is great. But, I'm now trying to figure out how to create more navigators for particular views/widgets. For example I've got a custom tab bar with another widget/view in. I'd now like that widget/view to have it's own navigation stack. So the goal is to keep the tab bar at the top while I navigate to other pages from within my widget/view.
This question is almost exactly this: Permanent view with navigation bar in Flutter but in that code, there is no MaterialApp yet. Nesting MaterialApps give funky results and I don't believe that that would be a solution.
Any ideas?

You can create new Navigator for each page. As a reference check CupertinoTabView
Or simple example:
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
Navigator _getNavigator(BuildContext context) {
return new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(builder: (context) {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text(settings.name),
new FlatButton(
onPressed: () =>
Navigator.pushNamed(context, "${settings.name}/next"),
child: new Text('Next'),
),
new FlatButton(
onPressed: () =>
Navigator.pop(context),
child: new Text('Back'),
),
],
),
);
});
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
child: _getNavigator(context),
),
new Expanded(
child: _getNavigator(context),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
home: new Home(),
));
}

you could give first material app's context to secondary material app;
and in secondary material app ,when you need to go back first material app,
you could check whether the secondary app can pop, with this line:
Navigator.of(secondaryContext).canPop()
if true, then you could keep using,else use first material app's context,
like this:
Navigator.of(parentContext).pop();
otherwise

Related

Flutter - Navigator doesn't return back (Black screen)

I'm new on flutter, I have a Homepage where I have a Drawer menu and body list content.
DRAWER MENU => On tap item list of drawer menu I'm loading a PAGE web URL and on tap BACK it returns to my homepage. So it works very well.
BODY LIST CONTENT => On tap item list it loads the page web URL well BUT when I won't return back to my homepage it returns a black screen :(
Homepage.dart
class HomePage extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _HomePage();
}
}
class _HomePage extends State<HomePage>{
#override
Widget build(BuildContext context) {
// TODO: implement build
var globalContext = context;
return Scaffold(
appBar: AppBar(
title: Text(
'Benvenuto',
style: TextStyle(color: Colors.white)
),
backgroundColor: Color(0xFF4035b1),
),
drawer: Drawer(
child: new Column(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: Text('VIA ALBERTO POLIO 54'),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF4268D3),
Color(0xFF584CD1)
],
begin: FractionalOffset(0.2, 0.0),
end: FractionalOffset(1.0, 0.6),
stops: [0.0, 0.6],
tileMode: TileMode.clamp
)
),
accountEmail: Text('ORARI: LUNEDI - VENERDI 9:30 / 19:30'),
currentAccountPicture: new CircleAvatar(
radius: 50.0,
backgroundColor: const Color(0xFF778899),
backgroundImage: AssetImage("assets/img/icon_logo.jpg"),
)
),
// This list work well!
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
],
),
),
// The menu on my body load well the page web url but doesn't return back to my homepage.
body: new Column(
children: <Widget>[
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
])
);
}
}
Page.dart
class Page extends StatelessWidget{
final String titleText;
final String urlSource;
Page(this.titleText, this.urlSource);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new WebviewScaffold(
url: urlSource,
appBar: new AppBar(
title: Text(titleText),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
);
}
}
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: HomePage()
);
}
}
Thank you for your help guys!
You shouldn't be using Navigator.pop() just before Navigator.push().
If replacing the current page with a new one is what you want, you can use Navigator.of(context).pushReplacement().
If you only want to navigate to a new route delete the pop method and only use push
The real problem here is that when you're using Navigator.pop() you're removing it from the "pages stack". When you're using Navigator.pop() at the Drawer(), the ".pop" function removes the Drawer and keeps the main page.
But at the time you use it with the ListTile(), which is part of the "main body" of the page, you just remove it.
Whatever collapses the main page when pressed, such a Drawer, Dialog or even a Keyboard, will be removed using Navigator.pop(), any other thing that is at the page which implements the "Navigator.pop()" will remove the page instead.
Navigator.of(context).pop();
this one is popping the screen in the flutter.
you can refer this doc as well https://api.flutter.dev/flutter/widgets/Navigator/pop.html
your home page doesn't have any stack behind it so when you have written Navigator.of(context).pop(); then it will pop the home page where there is not anything and it always shows the blank screen.
when you have tried Navigator.of(context).pop(); in the drawer then it has home page as a stack in the flutter which is the home page in your case and it will pop to the home page and show the blank page.

ListView does not refresh whereas attached list does (Flutter)

I'm trying to get familiar with flutter and I'm facing some weird case. I want to build a dynamic ListView where a + button allows to add elements. I wrote the following State code:
class MyWidgetListState extends State<MyWidgetList> {
List<Widget> _objectList = <Widget>[
new Text('test'),
new Text('test')
];
void _addOne() {
setState(() {
_objectList.add(new Text('test'));
});
}
void _removeOne() {
setState(() {
_objectList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new ListView(
shrinkWrap: true,
children: _objectList
),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.remove_circle),
iconSize: 36.0,
tooltip: 'Remove',
onPressed: _objectList.length > 2 ? _removeOne : null,
),
new IconButton(
icon: new Icon(Icons.add_circle),
iconSize: 36.0,
tooltip: 'Add',
onPressed: _addOne,
)
],
),
new Text(_objectList.length.toString())
],
);
}
}
My problem here is that the ListView is visually stuck with the 2 elements I initialized it with.
Internally the _objectList is well managed. For testing purpose I added a simple Text widget at the bottom that shows the size of the list. This one works fine when I click the Add/Remove buttons and it gets properly refreshed. Am I missing something?
Flutter is based around immutable data. Meaning that if the reference to an object didn't change, the content didn't either.
The problem is, in your case you always send to ListView the same array, and instead mutate its content. But this leads to ListView assuming the list didn't change and therefore prevent useless render.
You can change your setState to keep that in mind :
setState(() {
_objectList = List.from(_objectList)
..add(Text("foo"));
});
Another Solution!!
Replace ListView with ListView.builder
Code:
ListView.builder(
itemBuilder: (ctx, item) {
return _objectList[item];
},
shrinkWrap: true,
itemCount: _objectList.length,
),
Output:

Flutter Trying to fire a showModalBottomSheet mystery

I question myself sometimes, whether I'm dumb, or Dart (Flutter) is weird.
How does this not work?
I'm using https://github.com/apptreesoftware/flutter_google_map_view
I show a map, and have added markers.
Since the package supports listeners, when a marker is tapped, I want to show a modal.
Does the listener work? Yep, because the print statement happens.
Does the modal work? I don't know. No error shows, nothing!
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
Please, what is the magic bullet?
Edit
Lemme thrown in more flesh. This is my Scaffold widget.
MapView mapView = new MapView();
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: new Text('Map View Example'),
),
body: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
),
),
);
And showMap(...) looks like this:
showMap(context) {
mapView.show(
new MapOptions(
mapViewType: MapViewType.normal,
showUserLocation: true,
showMyLocationButton: true,
showCompassButton: true,
initialCameraPosition:
new CameraPosition(new Location(5.6404963, -0.2285315), 15.0),
hideToolbar: false,
title: "Dashboard"),
// toolbarActions: [new ToolbarAction("Close", 1)],
);
mapView.onMapReady.listen((_) {
mapView.setMarkers(_markers);
});
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
}
The reason you've having issues is that your context doesn't contain a scaffold. If you look what you're doing in your code, your context actually comes from the widget enclosing your scaffold.
YourWidget <------------ context
MaterialApp
Scaffold
AppBar
Column
showMap....
There are a couple of ways to get around this. You can use a Builder widget something like this:
body: new WidgetBuilder(
builder: (context) => new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
)
),
in which case the context is actually rooted below the scaffold.
YourWidget
MaterialApp
Scaffold
AppBar
Builder <------------ context
Column
showMap....
However, what I would actually recommend is breaking your class into multiple classes. If your build function gets large enough you have to separate it out into another function (that's only used once), there's a good chance you need a new widget!
You could either make a body widget (probably Stateless), or a widget just for showing the map (Stateless or Stateful depending on your needs)... or more likely both!
Now as to why you're not seeing any errors... are you running in debug or release mode (if you're in debug mode there should be a little banner in the top right of the screen)? If you're in release mode it might ignore the fact that there is no scaffold in the context and fail silently, whereas in debug mode it should throw an assertion error. Running from the IDE or with flutter run generally runs in debug mode, but you may have changed it.

What is the right way to navigate using a drawer and change only the content of the body?

I'm creating a basic Material App and i have a drawer for navigation.
With the simple way of pushing a route the whole widget is getting replaced and it's like opening a whole new page that includes a whole new drawer.
My goal is to create the atmosphere of a page and a drawer, and when the user taps a drawer item the drawer will collapse and only the content of the page will be replaced.
I have found these two Questions/Answers:
Replace initial Route in MaterialApp without animation?
Flutter Drawer Widget - change Scaffold.body content
And my question is what is the best/correct way to achieve what i'm trying to do?
The 1st method is just creating the illusion by removing the push/pop animation, although it still actually behaves like the original method i described.
The 2nd method actually replaces the content only, and the solution i thought of is instead of changing the text to create multiple Container widgets and changing between them.
Since i'm still new and learning flutter i would like to know what is the right practice to do so.
EDIT:
I created this and it works pretty well. I still don't know about how effective/efficient it is but for now that's exactly what i wanted to achieve:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blueGrey,
),
home: new TestPage(),
);
}
}
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => new _TestPageState();
}
class _TestPageState extends State<TestPage> {
static final Container info = new Container(
child: new Center(
child: new Text('Info')
),
);
static final Container save = new Container(
child: new Center(
child: new Text('Save')
),
);
static final Container settings = new Container(
child: new Center(
child: new Text('Settings')
),
);
Container activeContainer = info;
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new Container(child: new DrawerHeader(child: new Container())),
new Container (
child: new Column(
children: <Widget>[
new ListTile(leading: new Icon(Icons.info), title: new Text('Info'),
onTap:(){
setState((){
activeContainer = info;
});
Navigator.of(context).pop();
}
),
new ListTile(leading: new Icon(Icons.save), title: new Text('Save'),
onTap:(){
setState((){
activeContainer = save;
});
Navigator.of(context).pop();
}
),
new ListTile(leading: new Icon(Icons.settings), title: new Text('Settings'),
onTap:(){
setState((){
activeContainer = settings;
});
Navigator.of(context).pop();
}
),
]
),
)
],
),
),
appBar: new AppBar(title: new Text("Test Page"),),
body: activeContainer,
);
}
}
Is not what you are trying to achieve is exactly what in the second answer, No ? :/
Here I am illustrating it by switching colors, but you can apply this to the content itself or basically any other widget.
class NavDrawer extends StatefulWidget {
#override
_NavDrawerState createState() => new _NavDrawerState();
}
class _NavDrawerState extends State<NavDrawer> {
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
Color contentColor = Colors.white;
#override
initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: new AppBar(),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(child: new Container()),
new ListTile(title: new Text("Blue"),onTap: (){setState((){contentColor=Colors.blue;Navigator.pop(context);});},),
new ListTile(title: new Text("Red"),onTap: (){setState((){contentColor=Colors.red;Navigator.pop(context);});},),
new ListTile(title: new Text("Green"),onTap: (){setState((){contentColor=Colors.green;Navigator.pop(context);});},),
new ListTile(title: new Text("Yellow"),onTap: (){setState((){contentColor=Colors.yellow;Navigator.pop(context);});},)
],
),
),
body: new Container(
color: contentColor,
),
);
}
}

Replace initial Route in MaterialApp without animation?

Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.
What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?
We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)
What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.
However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.
Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation example',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': return new MyCustomRoute(
builder: (_) => new MyHomePage(),
settings: settings,
);
case '/somewhere': return new MyCustomRoute(
builder: (_) => new Somewhere(),
settings: settings,
);
}
assert(false);
}
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Navigation example'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
new ListTile(
leading: const Icon(Icons.navigate_next),
title: const Text('Navigate somewhere'),
onTap: () {
Navigator.pushNamed(context, '/somewhere');
},
),
],
),
),
body: new Center(
child: new Text(
'This is a home page.',
),
),
);
}
}
class Somewhere extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text(
'Congrats, you did it.',
),
),
appBar: new AppBar(
title: new Text('Somewhere'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
],
),
),
);
}
}
Use PageRouteBuilder like:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Screen2(),
transitionDuration: Duration.zero,
),
);
And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),

Resources