flutter bottom bar change page setstate as default for some pages - flutter

I have a problem with my BottomNavigationBar in Flutter.
Please help me with this issue. i really need it to done.
I don't want to keep my page alive if I press jump to any page from screens, not from my BottomNavigationBar.
eg, if I have three screens that navigate from the bottom bar its works fine but if I add a button in any of the three pages that navigate to another screen that does not belong to the bottom bar then it keeps that screen alive the previous screen.
here my implementation.
BottomNavigation :
footer.dart
import 'package:flutter/material.dart';
import 'MyPage.dart';
import 'MyPage2.dart';
import 'MyPage3.dart';
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'Notifications.dart';
void main() {
runApp(MaterialApp(
home: Footer(),
));
}
class Footer extends StatefulWidget {
#override
_Footer createState() => _Footer();
}
class _Footer extends State<Footer> {
late List<Widget> _pages;
List<BottomNavigationBarItem> _items = [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.messenger_rounded),
label: "Messages",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: "Settings",
)
];
late int _selectedPage;
#override
void initState() {
super.initState();
_selectedPage = 0;
_pages = [
MyPage(
count: 1,
),
MyPage2(
count: 2,
),
MyPage3(
count: 3,
),
// This avoid the other pages to be built unnecessarily
//Notifications(),
// SizedBox(),
];
}
late DateTime currentBackPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
body: DoubleBackToCloseApp(
snackBar: SnackBar(
content: const Text(
'Tap back again to leave',
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.normal),
),
duration: Duration(seconds: 4),
backgroundColor: Colors.blue,
width: 340.0,
padding: EdgeInsets.all(15),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
child: _pages[_selectedPage],
),
bottomNavigationBar: BottomNavigationBar(
items: _items,
currentIndex: _selectedPage,
onTap: (index) {
setState(() {
// now check if the chosen page has already been built
// if it hasn't, then it still is a SizedBox
_selectedPage = index;
});
},
)
);
}
}
MyPage.dart:
import 'package:flutter/material.dart';
import 'MyCustomPage.dart';
import 'Notifications.dart';
class MyPage extends StatefulWidget {
final count;
MyPage({Key? key, this.count}) : super(key: key);
#override
_MyPage createState() => _MyPage();
}
class _MyPage extends State<MyPage>{
#override
Widget build(BuildContext context) {
// You'll see that it will only print once
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.text),
actions: <Widget>[
Row(
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => Notifications()));
},
child: Icon(Icons.notifications),
)
],
)
],
),
body: Center(
child: RaisedButton(
child: Text('my page1'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => MyCustomPage()));
},
),
),
);
},
);
},
);
}
}
MyCustomPage.dart
import 'package:flutter/material.dart';
class MyCustomPage extends StatefulWidget {
MyCustomPage ({Key? key}) : super(key: key);
#override
_MyCustomPage createState() => _MyCustomPage();
}
class _MyCustomPage extends State<MyCustomPage>{
#override
Widget build(BuildContext parentContext) {
return Scaffold(
appBar: AppBar(
title: Text('custompage'),
),
body: Column(
children: [
Expanded(
child: Container(
child: ListView.builder(
itemCount: 15,
itemBuilder: (context, index) {
return Container(
width: double.infinity,
child: Card(
child: Center(
child: Text('My Custom Page'),
),
),
);
},
),
),
),
],
),
);
}
}
Here are my three files that navigate the inner screen
the flow of navigation is first load Footer.dart file that is the main file inside there is three pages in BottomNavigationBar() in the first screen on tap home icon it loads MyPage.dart file which contains a text and button. on the button press, it navigates MyCustomPage.dart file. now the issue is when I click on the home icon from BottomNavigationBar() it loads MyPage screen and on press button inside that screen it loads MyCustomPage but when MyCustomPage.dart file load it keeps home icon alive and I don't have access to press the home icon again.
I hope you understand what I am trying to say.
If anyone knows please help me.

Related

how to change the three dot colour of popup menu button in flutter

Hello flutter family I stucked in changing the color of the three-dot button of the popup menu button in my flutter project please anyone help me to clear this error
how to achieve this
my code
PopupMenuButton<Menumodels>(color: Colors.grey,
onSelected: (item)=>onSelected(context,item),
itemBuilder: (context)=>[...MenuItems.items.map(buildItem).toList(
)])
],
PopupMenuItem<Menumodels>buildItem(
Menumodels item)=>PopupMenuItem<Menumodels>(value: item,
child: Row(
children: [
Icon(item.icon,color: Colors.black,size: 20),
SizedBox(width: 11),
Text(item.text),
],
));
You can use icon param
PopupMenuButton(
icon: Icon(Icons.more_vert,color: Colors.white), // add this line
itemBuilder: (_) => ......,
onSelected:....
)
Hope this will work for you.
import 'package:flutter/material.dart';
// This is the type used by the popup menu below.
enum Menu { itemOne, itemTwo, itemThree, itemFour }
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String _selectedMenu = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
// This button presents popup menu items.
PopupMenuButton<Menu>(
icon: Icon(Icons.more_vert,color: Colors.white), //add this
// Callback that sets the selected popup menu item.
onSelected: (Menu item) {
setState(() {
_selectedMenu = item.name;
});
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<Menu>>[
const PopupMenuItem<Menu>(
value: Menu.itemOne,
child: Text('Item 1'),
),
const PopupMenuItem<Menu>(
value: Menu.itemTwo,
child: Text('Item 2'),
),
const PopupMenuItem<Menu>(
value: Menu.itemThree,
child: Text('Item 3'),
),
const PopupMenuItem<Menu>(
value: Menu.itemFour,
child: Text('Item 4'),
),
]),
],
),
body: Center(
child: Text('_selectedMenu: $_selectedMenu'),
),
);
}
}
The easy way of doing this overriding the icon as #Rohan Jariwala shown on his post. The icon color comes from iconTheme's color. You can override it on theme level like
Theme(
data: Theme.of(context).copyWith(
iconTheme: IconTheme.of(context).copyWith(
color: Colors.amber,
)),
child: PopupMenuButton(

How to show next page (Stateless widget) on click only in specific Container in SplitView, not all over the page

I have TestApp, where I have SplitView with 2 horizontal Containers. By clicking button in the first container on the left(blue) I want to show new page (DetailPage widget) but not all over the page, but only in the first Container. Now it shows on the whole screen. What is a best approach to do it?
import 'package:flutter/material.dart';
import 'package:split_view/split_view.dart';
void main() {
runApp(MaterialApp(
title: 'Test',
home: TestApp(),
));
}
class TestApp extends StatelessWidget {
const TestApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SplitView(
children: [
Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailPage()));
},
child: const Text('CLICK')),
),
Container(color: Colors.yellow),
],
viewMode: SplitViewMode.Horizontal,
indicator: SplitIndicator(viewMode: SplitViewMode.Horizontal),
activeIndicator: SplitIndicator(
viewMode: SplitViewMode.Horizontal,
isActive: true,
),
controller: SplitViewController(limits: [null, WeightLimit(max: 1)]),
),
);
}
}
class DetailPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')), body: Container(color: Colors.red));
}
}
When pushing a new page you will be overriding the old one, meaning the new page will not have a spiltView, the best way to do this is by changing the widget displayed inside of the splitView like this :
import 'package:flutter/material.dart';
import 'package:split_view/split_view.dart';
void main() {
runApp(MaterialApp(
title: 'Test',
home: TestApp(),
));
}
class TestApp extends StatefulWidget { // I have already changed the widgte to stateful here
const TestApp({Key? key}) : super(key: key);
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
Widget build(BuildContext context) {
bool Bool;
return MaterialApp(
home: SplitView(
children: [
if (Bool == false){
Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
setState(() {
Bool = !Bool; // this the method for inverting the boolean, it just gives it the opposite value
});
},
child: const Text('CLICK')),
),
}
else{
DetailPage()
},
Container(color: Colors.yellow),
],
viewMode: SplitViewMode.Horizontal,
indicator: SplitIndicator(viewMode: SplitViewMode.Horizontal),
activeIndicator: SplitIndicator(
viewMode: SplitViewMode.Horizontal,
isActive: true,
),
controller: SplitViewController(limits: [null, WeightLimit(max: 1)]),
),
);
}
}
class DetailPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')), body: Container(color: Colors.red));
}
}
Above I defined a bool called Bool, when rendering the page it checks if Bool is false, in that case it returns the blue widget, if it is true then it returns the red one, and when you click on the button it inverts the bool and updates the page.
Please note that for updating the page you have to use setState which rebuilds the widget, and to use it you have to use a stateful widget since stateless widget is static and cannot be changed.
Also I haven't tested the code because I don't have split_view package, but you should be able to copy and paste it just fine, if you get any errors please let me know.
When you use Navigator.push your routing to a new page and creating a new state. I think you should use showGeneralDialog instead.
showGeneralDialog(
context: context,
pageBuilder: (BuildContext context,
Animation<double> animation, Animation<double> pagebuilder) {
return Align(
alignment: Alignment.centerLeft,
child: Card(
child: Container(
alignment: Alignment.topLeft,
color: Colors.amber,
//show half the screen width
width: MediaQuery.of(context).size.width / 2,
child: IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
}))),
);
});
try to create new Navigator within Container:
GlobalKey<NavigatorState> _navKey = GlobalKey();
home: SplitView(
children: [
Container(
child: Navigator(
key: _navKey,
onGenerateRoute: (_) => MaterialPageRoute<dynamic>(
builder: (_) {
return Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailPage()));
},
child: const Text('CLICK')),
);
},
),
),),

Flutter: Bottom Navigation bar with nested navigation and Restoring the Root page when tabs are changed

I am new to Flutter development. And I have been going through multiple tutorials to understand the Bottom Navigation bar.
I have tried these tutorials but I am not able to achieve the requirement that I have.
Tutorials I have followed:
https://codewithandrea.com/articles/multiple-navigators-bottom-navigation-bar/
https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386
https://medium.com/#theboringdeveloper/common-bottom-navigation-bar-flutter-e3693305d2d
I personally liked the 1st tutorial because there are nested routes.
Information:
I have bottom navigation with 3 tabs: Home, Calendar, Profile.
Home tabs has a screen: Screen2. Calendar has a screen: Screen3. Profile has a screen: Screen4
Problem:
My bottom navigation bar persisting the state of the screen(which is good).
Home screen has a button which opens Screen2. When user clicks, it pushes the Screen2. And when user clicks on Calendar (tab) user sees Calendar screen. Now, user again clicks on Home button(tab), user sees the Screen2. Because It was part of that route(Home). Where he should see Home screen only.
And I just want to reset it. Basically Home screen should push Home screen and pop all the children of Home page. When tabs are switched.
Code:
main.dart
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.blue,
),
home: MainScreen(),
);
}
}
main_screen.dart
class MainScreen extends StatefulWidget {
MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedIndex = 0;
List<GlobalKey<NavigatorState>> _navigatorKeys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>()
];
List<Widget> _widgetOptions = <Widget>[
HomePage(),
CalendarPage(),
ProfilePage(),
];
Map<String, WidgetBuilder> _routeBuilders(BuildContext context, int index) {
return {
'/': (context) {
return [
HomePage(),
CalendarPage(),
ProfilePage(),
].elementAt(index);
},
};
}
Widget _buildOffstageNavigator(int index) {
var routeBuilders = _routeBuilders(context, index);
return Offstage(
offstage: _selectedIndex != index,
child: Navigator(
key: _navigatorKeys[index],
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => routeBuilders[routeSettings.name](context),
);
},
),
);
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await _navigatorKeys[_selectedIndex].currentState.maybePop();
// let system handle back button if we're on the first route
return isFirstRouteInCurrentTab;
},
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Stack(
children: [
_buildOffstageNavigator(0),
_buildOffstageNavigator(1),
_buildOffstageNavigator(2),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
showSelectedLabels: false,
showUnselectedLabels: false,
items: [
BottomNavigationBarItem(
icon: Icon(
Feather.home,
color: Colors.grey[300],
),
label: 'HOME',
activeIcon: Icon(
Feather.home,
color: Colors.purple[300],
),
),
BottomNavigationBarItem(
icon: Icon(
FontAwesome.calendar,
color: Colors.grey[300],
),
label: 'CALENDAR',
activeIcon: Icon(
FontAwesome.calendar,
color: Colors.purple[300],
),
),
BottomNavigationBarItem(
icon: Icon(
EvilIcons.user,
color: Colors.grey[300],
size: 36,
),
label: 'PROFILE',
activeIcon: Icon(
EvilIcons.user,
color: Colors.purple[300],
size: 36,
),
),
],
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
),
),
);
}
}
home_page.dart
class HomePage extends StatefulWidget {
HomePage();
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.lightBlueAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
child: Text(
'Screen 1',
style: TextStyle(color: Colors.white, fontSize: 20),
),
margin: EdgeInsets.all(16),
),
FlatButton(
onPressed: () {
// Navigator.push(context, MaterialPageRoute(
// builder: (context) => Screen2()
// ));
Navigator.push(context, PageRouteBuilder(pageBuilder: (_,__,___) => Screen2()));
},
child: Text('Go to next screen'),
color: Colors.white,
),
],
));
}
}
calendar_page.dart
class CalendarPage extends StatefulWidget {
CalendarPage({Key key}) : super(key: key);
#override
_CalendarPageState createState() => _CalendarPageState();
}
class _CalendarPageState extends State<CalendarPage> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Center(
child: FlatButton(
onPressed: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => Screen3()
));
},
child: Text('Go to next screen'),
color: Colors.white,
),
),
);
}
}
I would really appreciate if anyone could point me to direction. Thanks in Advance.
Requirement:
There are tabs, each tab will respective screens(ScreenX-> ScreenY-> ScreenN). And when tab is switched it should pop all the children of the tabs. I hope this understandable(Sorry, my English is not good).
What am I missing here?
So the logic is if I move away from Home screen(tab) to any other tab. I should clear the stack(not referring to the widget).
Assuming you are following 1st tutorial.
There are 3 tabs. Home, Calendar and Profile.
Now from Home screen I added "Screen2". So, right now my currentTab is
"Home". If I click Calendar tab my selectedTtab is "Calendar".
I will pop everything from my current tab until the first route is met. Once this is done I will set the state.
Code:
void _selectTab(TabItem tabItem) {
if (tabItem == _currentTab) {
_navigatorKeys[tabItem]?.currentState?.popUntil((route) => route.isFirst);
} else {
//! Added logic to Pop everything from Home tab, if any other tab is clicked
if (_currentTab == TabItem.HOME) {
_navigatorKeys[_currentTab]
?.currentState
?.popUntil((route) => route.isFirst);
}
setState(() => _currentTab = tabItem);
}
}
And I am calling this method from Bottom navigation.
bottomNavigationBar: BottomNavigation(
currentTab: _currentTab,
onSelectTab: _selectTab,
),
Hope this helps. Let me know if this is sufficient.

How to make Modal Bottom Sheet elevated equally with Bottom App Bar in Flutter?

So, I am a new Flutter Developer and currently trying to make my own flutter app without any tutorial. I am confused with the elevation of the Modal Bottom Sheet and Bottom App Bar. I want both of the widgets to be elevated equally. Currently, my app behavior is like this.. The Bottom Modal Sheet just covers the Bottom App Bar and everything else. My code is something like this.
home_screen.dart (where my Bottom Modal Sheet, FAB, Bottom App Bar is)
// Packages
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:moneige/models/transaction.dart';
// UIs
import '../ui/home_screen/app_bar_title.dart';
import '../ui/home_screen/bottom_app_bar.dart';
import '../ui/home_screen/transaction_list_view.dart';
// Widgets
import '../widget/add_button.dart';
// Styles
import '../constants/styles.dart' as Styles;
class HomeScreen extends StatelessWidget {
final int totalBalance = 100000;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
title: MyAppBarTitle(totalBalance: totalBalance),
),
body: ValueListenableBuilder(
valueListenable: Hive.box('transactions').listenable(),
builder: (context, transactionBox, widget) {
return (transactionBox.length > 0)
? TransactionListView(transactionBox: transactionBox)
: Center(
child: Text('You have no transaction yet',
style: Styles.textMedium));
},
),
bottomNavigationBar: MyBottomAppBar(),
floatingActionButton: AddButton(() {
Hive.box('transactions').add(Transaction(
date: DateTime.now(),
changes: 123000,
notes: 'Crazier than usual'));
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
backgroundColor: Colors.white,
context: context,
elevation: 10,
useRootNavigator: true,
builder: (BuildContext context) {
return Container(height: 200);
});
}),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
bottom_app_bar.dart (where MyBottomAppBar is)
// Packages
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
// Widgets
import '../../widget/settings_button.dart';
import '../../widget/transaction_report_switch.dart';
// Styles
import '../../constants/styles.dart' as Styles;
class MyBottomAppBar extends StatelessWidget {
const MyBottomAppBar({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BottomAppBar(
shape: CircularNotchedRectangle(),
color: Styles.colorPrimary,
child: Row(
children: [
Spacer(),
Container(
child: TransactionReportSwitch(() => Hive.box('transactions').deleteAll(Hive.box('transactions').keys)),
),
Container(
child: SettingsButton(),
),
],
),
);
}
}
add_button.dart (where AddButton is)
// Packages
import 'package:flutter/material.dart';
// Styles
import '../constants/styles.dart' as Styles;
class AddButton extends StatelessWidget {
final Function handler;
AddButton(this.handler);
#override
Widget build(BuildContext context) {
return FloatingActionButton(
child: Icon(
Icons.add_rounded,
color: Colors.white,
size: 28,
),
backgroundColor: Styles.colorPrimary,
focusColor: Colors.white12,
hoverColor: Colors.white12,
foregroundColor: Colors.white12,
splashColor: Colors.white24,
onPressed: handler,
);
}
}
I saw a really good animated FAB, Modal Bottom Sheet, and Bottom App Bar composition in the Flutter Gallery app, Reply example. . (https://play.google.com/store/apps/details?id=io.flutter.demo.gallery&hl=en) When the Modal Bottom Sheet appears, the FAB disappears animatedly and the Sheet and App Bar are equally elevated, also the Sheet is above the Bottom App Bar. This is the behavior I wanted in my app, do you guys have any solution?
A solution would be to have the BottomAppBar in other ascendant Scaffold in your widget tree.
Reduced example:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold( // Your Ascendant Scaffold
body: MyScreen(),
bottomNavigationBar: MyBottomAppBar() // Your BottomAppBar here
),
);
}
}
class MyScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold( // Your descendant Scaffold
body: Center(
child: Text('Hello world'),
),
);
}
}
Complete example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyWidget(),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Item 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Item 2',
),
],
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Builder(
builder: (context) => ElevatedButton(
child: Text('Show modal bottom sheet'),
onPressed: () => _displaysBottomSheet(context),
),
),
),
);
}
void _displaysBottomSheet(BuildContext context) {
Scaffold.of(context).showBottomSheet(
(context) => Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
),
);
}
}
RESULT ON DEVICE

BottomNavigationBar with BLoC pattern

I really like the BLoC pattern and I am trying to understand it. But I can't seem to figure it out exactly how it should apply with the BottomNavigationBar.
Making a list of navigation pages and setting the current index on navigation bar tap event causes the whole app to redraw because of setState().
Can I use the Navigator to show the clicked navigation page without losing the navigation bar ?
Did anyone use the BLoC pattern with the BottomNavigationBar ? How do I do this ? I'd love to see a sample code.
I finally got it. I'm putting the whole code here to help others.
First read this wonderful article from didier boelens : https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/
using his bloc provider and base bloc create blocs. mine is like the following:
import 'dart:async';
import 'bloc_provider.dart';
import 'package:rxdart/rxdart.dart';
class NewsfeedBloc implements BlocBase {
BehaviorSubject<int> _ctrl = new BehaviorSubject<int>();
NewsfeedBloc(
// listen _ctrl event and do other business logic
);
void dispose() {
_ctrl.close();
}
}
then create the page that will use the bloc:
import 'package:flutter/material.dart';
import '../blocs/newsfeed_bloc.dart';
import '../blocs/bloc_provider.dart';
class NewsfeedPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final NewsfeedBloc bloc = BlocProvider.of<NewsfeedBloc>(context);
// here you should use a stream builder or such to build the ui
return Container(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text('The Enchanted Nightingale'),
subtitle: Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {/* do something with the bloc */},
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () {/* do something with the bloc */},
),
],
),
),
],
),
),
);
}
}
and finally the main.dart file containing a navigationbottombar and a drawer:
import 'dart:async';
import 'package:flutter/material.dart';
import 'blocs/bloc_provider.dart';
import 'blocs/application_bloc.dart';
import 'blocs/newsfeed_bloc.dart';
import 'blocs/tracking_bloc.dart';
import 'blocs/notifications_bloc.dart';
import 'blocs/item1_bloc.dart';
import 'blocs/item2_bloc.dart';
import 'pages/newsfeed.dart';
import 'pages/tracking.dart';
import 'pages/notifications.dart';
import 'pages/item1.dart';
import 'pages/item2.dart';
Future<void> main() async {
return runApp(BlocProvider<ApplicationBloc>(
bloc: ApplicationBloc(),
child: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
// define your blocs here so that you dont lose the state when your app rebuilds for some reason. thanks boformer for pointing that out.
NewsfeedBloc _newsfeedBloc;
PageController _pageController;
var _page = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Movies',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: AppBar(
title: new Text('App Title'),
),
body: PageView(
children: <Widget>[
BlocProvider<NewsfeedBloc>(
bloc: _newsfeedBloc(),
child: NewsfeedPage(),
),
// ...
],
controller: _pageController,
onPageChanged: onPageChanged,
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.timeline),
title: Text("Timeline"),
),
BottomNavigationBarItem(
icon: Icon(Icons.art_track),
title: Text("Some Page"),
),
BottomNavigationBarItem(
icon: Icon(Icons.notifications),
title: Text("Notifications"),
),
],
onTap: navigationTapped,
currentIndex: _page,
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Settings'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return BlocProvider<Item1Bloc>(
bloc: Item1Bloc(),
child: Item1Page(),
);
}
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return BlocProvider<Item2Bloc>(
bloc: Item2Bloc(),
child: Item2Page(),
);
}
},
),
],
),
),
),
);
}
void navigationTapped(int page) {
_pageController.animateToPage(
page,
duration: Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
void onPageChanged(int page) {
setState(() {
this._page = page;
});
}
#override
void initState() {
super.initState();
_pageController = new PageController();
_newsfeedBloc = NewsfeedBloc();
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
}