How to refactor / extract bottom nav bar from Scaffold? - flutter

How do I extract BottomNavigationBar widget from the Flutter's example, please? The problematic part for me is the _onItemTapped function that needs to be used in the BottomNavigationBar as well as in the Scaffold
This is Flutter's BottomNavigationBar example:
/// Flutter code sample for BottomNavigationBar
// This example shows a [BottomNavigationBar] as it is used within a [Scaffold]
// widget. The [BottomNavigationBar] has three [BottomNavigationBarItem]
// widgets and the [currentIndex] is set to index 0. The selected item is
// amber. The `_onItemTapped` function changes the selected item's index
// and displays a corresponding message in the center of the [Scaffold].
//
// ![A scaffold with a bottom navigation bar containing three bottom navigation
// bar items. The first one is selected.](https://flutter.github.io/assets-for-api-docs/assets/material/bottom_navigation_bar.png)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
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,
),
);
}
}

You can extract the BottomNavigationBar as a widget & handle the _onItemTapped as a callback as shown below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: CustomBottomNavigation(
selectedIndex: _selectedIndex,
onTap: _onItemTapped,
),
);
}
}
Create another file called custom_bottom_navigation.dart with the following code:
class CustomBottomNavigation extends StatelessWidget {
const CustomBottomNavigation({this.selectedIndex = 0, this.onTap});
final int selectedIndex;
final void Function(int) onTap;
#override
Widget build(BuildContext context) {
return 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: onTap,
);
}
}
This way your code will be refactored into two simple widgets.

Related

Navigation between pages bottom nav-bar flutter?

I am using flutter. I would like to navigate between pages of bottom nav-bar from inside the body of page.
Not to create a new screen on top of it. And carry data between pages.
I am using classic bottom navbar in flutter with pages in body of scaffold.
Something like this.
import 'package:flutter/material.dart';
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> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
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,
),
);
}
}
I tried pageview but with that I can switch between pages only on swipe and cant send data between them.
you can also get that widget from other places with A GlobalKey :
First, create a GlobalKey in the global scope ( make it outside you StatefulWidegt class )
final glbKey = GlobalKey();
then assign that key to BottomNavigationBar :
bottomNavigationBar: BottomNavigationBar(
key: glbKey,
items: const <BottomNavigationBarItem>[
// ...
Now, you can call it from your onPressed method like this:
onPressed: () {
//...
BottomNavigationBar navigationBar = glbKey.currentWidget as BottomNavigationBar;
navigationBar.onTap!(2); // change 2 with index of your target screen
}
you can simply simulate running the _onItemTapped() with the index of your target navigation item, in your search button icon onPressed, add this:
onPressed: () {
//...
_onItemTapped(2); // replace 2 with you screen index.
}
this will navigate to the search page programmatically.

How to make BottonNavigationBar route to different body Containers in Flutter

I'm creating a simple app consisting of three pages, the only thing to be changed when using the navigation bar is the body. I need every body to be in it's own dart file. whenever I try achieving this and replace the text with another widget, I break the app.
Here's my code so far:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Sample App';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 2: Account',
style: optionStyle,
),
Text(
'Index 1: Locate a Store',
style: optionStyle,
),
Text(
'Index 0: Home',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
backgroundColor: Colors.amber[400],
bottomNavigationBar: BottomNavigationBar(
showSelectedLabels: false, // <-- HERE
showUnselectedLabels: false, // <-- AND HERE
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.manage_accounts),
label: 'Account',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Store Locator',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home Page',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amberAccent[400],
onTap: _onItemTapped,
iconSize: 35,
),
);
}
}
To sum it up, The Text widget need to be replaced with new body/container that's saved in its own dart file, and work with the navigation bar.

Bottom Navigation Bar doesn't work correctly Flutter

I'm trying to make a Bottom Navigation Bar in Flutter but it keeps showing a shadow in front of the Navigation Bar. The code below is the code that taken from the flutter websites which has guarantee 100% work. But the fact is I keep failing because it doesn't work well on my Flutter. Is the version/SDK the problem?
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: 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({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
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,
),
);
}
}
Set elevation: 0.0, Inside your BottomNavigationBar widget
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
//backgroundColor: Theme.of(context).primaryColor,
elevation: 0.0,

How to add functionality to an instantiated widget in flutter?

I am trying to create a package where I add some functionality to a BottomNavigationBar. I want a generic helper that when used, wraps a bottom nav widget with another widget and changes its onTap method. Unfortunately, onTap is final and cannot be changed. This forces me to create the BottomNavigationBar widget in the package code. This results in me having to delegate all properties of the BottomNavigationBar from the user.
Ideally, I want the user to pass me a navigation bar instance, and I add the functionality to it as long as the passed in widget has currentIndex and onTap properties settable.
How would you solve this?
Edit
Code snippet for what I am trying to achieve:
class ExtendedBottomNav extends StatefulWidget {
const ExtendedBottomNav({required this.bottomNavBar});
final BottomNavigationBar bottomNavBar;
#override
State<StatefulWidget> createState() => ExtendedBottomNavState();
}
class ExtendedBottomNavState extends State<ExtendedBottomNav> {
int _currentTabIndex = 0;
#override
Widget build(BuildContext context) {
widget.bottomNavBar.currentIndex = _currentTabIndex;
widget.bottomNavBar.onTap = (int index) => {
// Some code, using _currentTabIndex
};
return Scaffold(
body: Text("Text"), // Some body
bottomNavigationBar: widget.bottomNavBar,
);
}
}
This is not possible because 'onTap' can't be used as a setter because it's final.
You can use this approach (uses null safety). I've not used all the constructor parameters, you can modify as per your need.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: CustomBottomNavigationBar(bar: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,
),
));
}
}
class CustomBottomNavigationBar extends StatelessWidget {
final BottomNavigationBar bar;
CustomBottomNavigationBar({ required this.bar});
BottomNavigationBar? customBar;
#override
Widget build(BuildContext context) {
customBar = BottomNavigationBar(
items: bar.items,
currentIndex: bar.currentIndex,
selectedItemColor: bar.selectedItemColor,
onTap: (int index){
// here you can add your additional code
print("hi");
if(bar.onTap != null)
bar.onTap!(index);
},
);
return Container(
child:customBar);
}
}
Without null safety
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: CustomBottomNavigationBar(bar: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,
),
));
}
}
class CustomBottomNavigationBar extends StatelessWidget {
final BottomNavigationBar bar;
CustomBottomNavigationBar({ #required this.bar});
BottomNavigationBar customBar;
#override
Widget build(BuildContext context) {
customBar = BottomNavigationBar(
items: bar.items,
currentIndex: bar.currentIndex,
selectedItemColor: bar.selectedItemColor,
onTap: (int index){
print("hi");
if(bar.onTap != null)
bar.onTap(index);
},
);
return Container(
child:customBar);
}
}
i think you can do it by extending the BottomNavigationBar widget and override
the onTap funtion and tell user to pass the new subclass of BottomNavigationBar
class NewBottomNavigationBar extends BottomNavigationBar {
NewBottomNavigationBar(List<BottomNavigationBarItem> items)
: super(items: []);
#override
ValueChanged<int>? onTap;
}

Flutter: Strange animation combining bottomSheet and bottomNavigationBar and calling setState

Im having a Scaffold with a bottomSheet and a bottomNavigationBar in a stateful widget.
Within the bottomSheet, i plan to add buttons, calling setState and triggering a rebuild of the local widget tree.
The problem is, that there is a strange animation happening when calling setState or when rebuilding the widgets. The bottom sheet appears to come from the bottom on top of the bottomNavigationbar.
Is there a way to fix that, such that this animation does not happen, when rebuilding or calling setState?
Please see the code below. To observe the behaviour, run the code and click on the green bottomSheet:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomSheet: MaterialButton(onPressed: () {
setState(() {});
}, child: Container(height: 50, color: Colors.green)),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('School'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
You should be able to reproduce the behavior i'm talking about when copy/pasting and running the code above.
I'm not sure what i'm doing wrong here, but this appears quite fishy to me.