Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 680 Vote(s) - 3.44 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Flutter: Keep BottomNavigationBar When Push to New Screen with Navigator

#1
In iOS, we have a [UITabBarController][1] which stays permanently at the bottom of the screen when we push to a new ViewController.

In Flutter, we have a bottomNavigationBar of a Scaffold. However, unlike iOS, when we `Navigator.push` to a new screen, this bottomNavigationBar disappears.

In my app, I want to fulfil this requirement: Home screen has a `bottomNavigationBar` with 2 items (*a* & *b*) presenting screen *A* & *B*. By default, screen *A* is displayed. Inside screen *A*, there is a button. Tap that button, `Navigator.push` to screen *C*. Now in screen *C*, we can still see the `bottomNavigationBar`. Tap item *b*, I go to screen *B*. Now in screen *B*, tap item *a* in the `bottomNavigationBar`, I go back to screen *C* (not *A*, *A* is currently below *C* in the navigation hierarchy).

How can I do this? Thanks, guys.

Edit: I'm including some pictures for demonstration:

Screen A
[Screen A][2]

Tap *Go to C* button, push to screen C
[Screen C][3]

Tap *Right* item inside bottom navigation bar, go to screen B
[Screen B][4]


[1]:

[To see links please register here]

[2]:

[3]:

[4]:
Reply

#2
I think the #right way of doing this would be to have the BottomNavigationBar wrapped in a [Hero][1] in both cases with the same tag. This way, when the animation between pages happens they would be excluded.

This is as brief as an example as I could make, but I'd highly recommend cleaning it up i.e. passing the hero string in, using widgets rather than a huge block of build, making your own widget for BottomNavigationBar.

Note that during the hero transition it does `overflow by 0.0000191 pixels` on my phone at least, but in release mode that shouldn't be an issue I don't think.

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(
home: new Builder(
builder: (context) => new Scaffold(
bottomNavigationBar: new Hero(
tag: "bottomNavigationBar",
child: new BottomNavigationBar(items: [
new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("Home")),
new BottomNavigationBarItem(icon: new Icon(Icons.ac_unit), title: new Text("AC Unit"))
]),
),
body: new SafeArea(
child: new Container(
constraints: new BoxConstraints.expand(),
color: Colors.green,
child: new Column(
children: <Widget>[
new RaisedButton(
child: new Text("Press me"),
onPressed: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new Scaffold(
bottomNavigationBar: new Hero(
tag: "bottomNavigationBar",
child: new BottomNavigationBar(items: [
new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("Home")),
new BottomNavigationBarItem(icon: new Icon(Icons.ac_unit), title: new Text("AC Unit"))
]),
),
body: new SafeArea(
child: new Container(
constraints:
new BoxConstraints.expand(),
color: Colors.red,
child: new Column(
children: <Widget>[
new RaisedButton(
onPressed: () =>
Navigator.pop(context),
child: new Text("Back"),
)
],
),
),
),
)));
})
],
),
),
),
),
),
));

I don't know how well the `hero` system handles multiple heroes etc, and if you say wanted to animate the navigation bar this might not work overly well.

There is another way of doing this which would allow you to animate the bottom navigation bar; it's actually a question that has already been answered though: [Flutter: Hero transition + widget animation at the same time?][2]


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#3
tl;dr: Use [CupertinoTabBar][1] with [CupertinoTabScaffold][2]

The problem is not in Flutter but in UX just like Rémi Rousselet has mentioned.

It turned out Material Design doesn't recommend sub-pages in the hierarchy to access the Bottom navigation bar.

However, iOS Human Interface Guide recommend this. So, to use this feature, I had to adapt Cupertino widgets instead of Material ones. Specifically, in main, return a `WidgetsApp/MaterialApp` which contains a `CupertinoTabScaffold`. Implement the tab bar with a `CupertinoTabBar` and each screen is a `CupertinoTabView`.


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#4
Another way to achieve this (though not good practice) is to nest a material app in the body of your scaffold. And handle all "sub-navigation" there.

So, your hierarchy will look like this

Material App
- home
- Scaffold
- body
- Material App
- Scaffold
- AppBar
- body
...
- routes (internal)
- bottomNavigationBar
- routes (external)

I've tried this and it works perfectly. Unfortunately I can't post the source code now.
Reply

#5
You could actually place a placeholder inside body
so the structure like this

- AppBar
- body (dynamic content from placeholder)
- BottomNavigationBar

Then you would have another class as a placeholder
So each time you tap on the BottomNavigationBar it will refresh content of the body

One example I found is here

[To see links please register here]


and here but a litte too complex and not working for me

[To see links please register here]


and this

[To see links please register here]

Reply

#6
You can create `Navigator` widget in a `Stack` widget to use `BottomNavigationBar` with tabs' inner navigation. You can use `WillPopScope` to handle Android's back button to pop inner screens of tab. Also, double tap bottom navigation item to pop all inner screens of a tab.

I've created a [Sample app]()
for this.

Hope this help!
Reply

#7
Option 1: If you only want to keep `BottomNavigationBar` then try to use [this][1].

Option 2: Use `CupertinoTabBar` as shown below for the static `BottomNavigationBar`.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mqttdemo/Screen2.dart';
import 'package:mqttdemo/Screen3.dart';

import 'Screen1.dart';

class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
int _currentIndex;
List<Widget> _children;

@override
void initState() {
_currentIndex = 0;
_children = [
Screen1(),
Screen2(),
Screen3(),
];
super.initState();
}

@override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: _currentIndex,
onTap: onTabTapped,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Screen 1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Screen 2"),
),
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("Screen 3")),
],

),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: CupertinoApp(
home: CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: _children[_currentIndex],
),
),
);
},
);
}
);
}

void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}


**Navigate to screen4 from Screen3 as shown below:**


class Screen3 extends StatefulWidget {
@override
_Screen3State createState() => _Screen3State();
}

class _Screen3State extends State<Screen3> {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Center(
child: RaisedButton(
child: Text("Click me"),
onPressed: () {
Navigator.of(context, rootNavigator: false).push(MaterialPageRoute(
builder: (context) => Screen4(), maintainState: false));
},
),
),
);
}

}





[1]:

[To see links please register here]

Reply

#8
**Screenshot:**

[![enter image description here][1]][1]


[1]:


---

Starting point:

```dart
void main() => runApp(MaterialApp(home: HomePage()));
```

**`HomePage`** [**`BottomNavigationBar`** + **`Page1`**]

```dart
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.orange,
items: [
BottomNavigationBarItem(icon: Icon(Icons.call), label: 'Call'),
BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Message'),
],
),
body: Navigator(
onGenerateRoute: (settings) {
Widget page = Page1();
if (settings.name == 'page2') page = Page2();
return MaterialPageRoute(builder: (_) => page);
},
),
);
}
}
```

**1st Page:**
```dart
class Page1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page1')),
body: Center(
child: RaisedButton(
onPressed: () => Navigator.pushNamed(context, 'page2'),
child: Text('Go to Page2'),
),
),
);
}
}
```
**2nd Page:**

```dart
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Page2')));
}
```
Reply

#9
If someone want to keep bottomNavigationBar, Just return a Navigator from body of home page and pass a unique key for each route.

import 'package:bottomnavigation_sample/page1.dart';
import 'package:bottomnavigation_sample/page2.dart';
import 'package:bottomnavigation_sample/page3.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 0;
Map<int, GlobalKey<NavigatorState>> navigatorKeys = {
0: GlobalKey<NavigatorState>(),
1: GlobalKey<NavigatorState>(),
2: GlobalKey<NavigatorState>(),
};
final List<Widget> _widgetOptions = <Widget>[
const page1(),
const page2(),
const page3()
];

void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
body: buildNavigator(),
);
}

buildNavigator() {
return Navigator(
key: navigatorKeys[_selectedIndex],
onGenerateRoute: (RouteSettings settings){
return MaterialPageRoute(builder: (_) => _widgetOptions.elementAt(_selectedIndex));
},
);
}
}

Reply

#10
It's very easy when you use GoRouter (

[To see links please register here]

) as a Navigation.
It has a ShellRoute option - you can wrap routes that you want to have the persistent navigation bar (or any other element like app bar and others) with this Shell.


example:

ShellRoute(
builder: (context, state, child) {
return BottomNavigationPage(
child: child,
);
},
routes: [
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) {
return const HomePage();
},
),
GoRoute(
path: '/settings',
builder: (BuildContext context, GoRouterState state) {
return const SettingsPage();
},
),
],
),

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through