I am trying to create a music player and I stared the code for navigation bar and some other customizations. but when I run the app it says
"TabBar widgets require a Material widget ancestor, To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, such as a Card, Dialog, Drawer, or Scaffold.
The specific widget that could not find a Material ancestor was:
TabBar"
I am new to flutter so I don't know much please help.
import 'package:envymusic/views/home.dart';
import 'package:envymusic/views/library.dart';
import 'package:envymusic/views/account.dart';
import 'package:envymusic/views/explore.dart';
class Tabbar extends StatefulWidget {
const Tabbar({Key? key}) : super(key: key);
#override
State<Tabbar> createState() => _TabbarState();
}
class _TabbarState extends State<Tabbar> {
int _selectedTab = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedTab,
onTap: (index) {
setState(() {
_selectedTab = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.search_outlined),
label: "Explore",
),
BottomNavigationBarItem(
icon: Icon(Icons.library_music),
label: "Library",
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: "Account",
),
],
),
body: Stack(
children: [
renderView(
0,
const HomeView(),
),
renderView(
1,
const ExploreView(),
),
renderView(
2,
const LibraryView(),
),
renderView(
3,
const AccountView(),
),
],
),
),
);
}
Widget renderView(int tabIndex, Widget view) {
return IgnorePointer(
ignoring: _selectedTab != tabIndex,
child: Opacity(
opacity: _selectedTab == tabIndex ? 1 : 0,
child: view,
),
);
}
}
You can Wrap your Tabbar widget with Material widget.
e.g:
Material(
child: Tabbar(),
),
Related
I want to put a drawer like this in my Flutter app:
just like https://m3.material.io/develop/flutter
I'm using NavigationRail and it's said that a menu button can be added to open a navigation drawer. Does any knows how to add the menu button and the drawer?
menu button of NavigationRail
thanks.
It's a bit hard to use a regular Scaffold Drawer without the regular scaffold controls, as far as I can tell.
I came up with a solution for your problem, if I understood it correctly. Looks a lot like the spec site, needs a bit of styling.
Took the example from the NavigationRail documentation and added a Visibility widget. Now clicking on the destinations, you can show and hide their child widgets(drawer). No drawer animation though.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
home: const NavRailExample(),
);
}
}
class NavRailExample extends StatefulWidget {
const NavRailExample({super.key});
#override
State<NavRailExample> createState() => _NavRailExampleState();
}
class _NavRailExampleState extends State<NavRailExample> {
int _selectedIndex = 0;
NavigationRailLabelType labelType = NavigationRailLabelType.all;
bool showLeading = false;
bool showTrailing = false;
double groupAligment = -1.0;
bool _isClosed = false;
Widget _getWidget(int index) {
switch (index) {
case 1:
return GestureDetector(
child: const Text('Tap!'),
onTap: () => setState(() {
_isClosed = true;
}),
);
case 2:
return const Text('empty');
default:
return ListView(
children: const [
ExpansionTile(
title: Text('whatev'),
children: [Text('1'), Text('2')],
),
ListTile(
title: Text('adfafdafaf'),
)
],
);
}
}
Widget _getPage(int index) {
switch (index) {
case 1:
return const Center(child: Text('sheeesh'));
case 2:
return const Center(child: Text('empty'));
default:
return const Center(child: Text('yolo'),);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: <Widget>[
NavigationRail(
selectedIndex: _selectedIndex,
groupAlignment: groupAligment,
onDestinationSelected: (int index) {
setState(() {
_isClosed = (_selectedIndex == index || _isClosed)
? !_isClosed
: _isClosed;
_selectedIndex = index;
});
},
labelType: labelType,
leading: showLeading
? FloatingActionButton(
elevation: 0,
onPressed: () {
// Add your onPressed code here!
},
child: const Icon(Icons.add),
)
: const SizedBox(),
trailing: showTrailing
? IconButton(
onPressed: () {
// Add your onPressed code here!
},
icon: const Icon(Icons.more_horiz_rounded),
)
: const SizedBox(),
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
),
Visibility(
maintainState: false,
visible: !_isClosed,
child: Row(
children: [
const VerticalDivider(thickness: 1, width: 1),
SizedBox(
height: double.infinity,
width: 200,
child: _getWidget(_selectedIndex),
)
],
),
),
const VerticalDivider(thickness: 1, width: 1),
// This is the main content.
Expanded(
child: _getPage(_selectedIndex),
),
],
),
),
);
}
}
I just need to separate the code of bottom navigation bar from the scaffold. But the issue is when i click another tab to switch to another page, there is a 7 seconds delay.
The code of scaffold Is:
Scaffold(
extendBody: true,
body: IndexedStack(
index: HomeScreen.pageIndex,
children: [
HomePage(isMember: widget.isMember),
(widget.isMember)
? const MyOpportunityPage()
: const AddOpportunity(),
ProfilePage(isMember: widget.isMember),
],
),
bottomNavigationBar: BottomNavBar(
isMember: widget.isMember,
),
);
The Bottom Navigation Bar code is:
class BottomNavBar extends StatelessWidget {
BottomNavBar({
Key? key,
required this.isMember,
}) : super(key: key);
bool isMember;
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (context, StateSetter setState) {
return BottomNavigationBar(
currentIndex: HomeScreen.pageIndex,
onTap: (int value) => setState(() {
HomeScreen.pageIndex = value;
}),
type: BottomNavigationBarType.fixed,
elevation: 2,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'home'.tr,
),
BottomNavigationBarItem(
icon: Icon(Icons.flag_sharp),
label:isMember
? 'my_opportunities'.tr
: "add_opportunity".tr,
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'profile'.tr,
),
],
);
}
);
}
}
I'm not sure why you are having a 7-second delay, especially when you did not specify one, maybe you should first close your program and probably uninstall it and reboot your IDE and try again, and if that is still the case, you can refactor your code like this;
Scaffold(
body: IndexedStack(
index: currentIndex,
children: [
Center(child: MyHomeWidget()),
Center(child: AnotherWidget()),
Center(child: SomeOtherWidget()),
],
),
bottomNavigationBar: BottomNavBar(
isMember: isMember,
currentI: currentIndex,
onPress: (int value) => setState(() {
currentIndex = value;
}),
),
),
class BottomNavBar extends StatelessWidget {
const BottomNavBar({
Key? key,
required this.isMember,
required this.onPress,
this.currentI = 0,
}) : super(key: key);
final bool isMember;
final Function(int) onPress;
final int currentI;
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: currentI,
onTap: onPress,
type: BottomNavigationBarType.fixed,
elevation: 2,
items: [
const BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'home',
),
BottomNavigationBarItem(
icon: const Icon(Icons.flag_sharp),
label: isMember ? 'my_opportunities' : "add_opportunity",
),
const BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'profile',
),
],
);
}
}
Hopefully there would be a difference. Just implement your own index and any other vital part of the code. I tested this on dartpad and it worked fine, here is a link
I am working on a flutter application where I need to redirect to the first screen on BottomNavigationBar when the user presses back from any other screen of the remaining BottomNavigationBar screens. For now, I have added redirecting event on a simple button, will replace this on _onWillPop event.
Please find the code below:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final PageStorageBucket bucket = PageStorageBucket();
Widget currentScreen = HomeFragment();
int currentTab = 0;
static int selectedIndexN = 0;
static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
List<Widget> _widgetOptions1 = <Widget>[
HomeFragment(),
LoginFargment(),
SignUpFargment(),
ProfileFargment(),
];
void changeTabMethod(int index) {
print('changeTabMethod is called');
setState(() {
selectedIndexN = index;
});
print('changeTabMethod is called : selectedIndexN : $selectedIndexN');
}
#override
Widget build(BuildContext context) {
return Scaffold(
// return GetBuilder<DashboardController>(
body: Center(
child: _widgetOptions1.elementAt(selectedIndexN),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: selectedIndexN,
onTap: changeTabMethod,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Login',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'SignUp',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Profile',
),
],
),
);
}
}
Profile screen code:
class ProfileFargment extends StatefulWidget {
#override
_ProfileFragmentState createState() => _ProfileFragmentState();
}
class _ProfileFragmentState extends State<ProfileFargment> {
#override
Widget build(BuildContext context) {
return Scaffold(
body:SafeArea(
child: Container(
padding: EdgeInsets.all(20.0),
height: double.infinity,
width: double.infinity,
color: Colors.teal,
child: GestureDetector(
onTap: () {
//Calling method changeTabMethod(0)
HomeScreen().createState().changeTabMethod(0);
},
child: Container(
margin: EdgeInsets.only(top: 20.0),
height: 40.0,
width: 150.0,
color: Colors.white,
child: Center(child: Text('Profile'),),
),
),
),
),
);
}
}
On the other hand, when I call changeTabMethod from a ProfileFragment screen, it will enter into changeTabMethod but couldn't execute the setState method. So my tab is not changing.
You can consider this console report:
changeTabMethod is called is only printed the second print after setState was not executed.
Can you please let me know what or where I am doing anything wrong?
Thanks in advance :-)
Try below code. By passing function as parameter you can trigger function on home page from any other page.
Home screen code:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final PageStorageBucket bucket = PageStorageBucket();
// Widget currentScreen = HomeFragment();
int currentTab = 0;
static int selectedIndexN = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
Widget _widgetOptions1(int index) {
switch (index) {
case 0:
return ProfileFargment(onButtonPressed: changeTabMethod);
case 1:
return Container(child: Text("Page - 2 "));
case 2:
return Container(child: Text("Page - 3 "));
default:
return Container();
}
}
void changeTabMethod(int index) {
print('changeTabMethod is called');
setState(() {
selectedIndexN = index;
});
print('changeTabMethod is called : selectedIndexN : $selectedIndexN');
}
#override
Widget build(BuildContext context) {
return Scaffold(
// return GetBuilder<DashboardController>(
body: Center(
child: _widgetOptions1(selectedIndexN),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: selectedIndexN,
onTap: changeTabMethod,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Login',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'SignUp',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Profile',
),
],
),
);
}
}
Profile screen code:
class ProfileFargment extends StatefulWidget {
final void Function(int) onButtonPressed;
const ProfileFargment({Key key, this.onButtonPressed});
#override
_ProfileFragmentState createState() => _ProfileFragmentState();
}
class _ProfileFragmentState extends State<ProfileFargment> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.all(20.0),
height: double.infinity,
width: double.infinity,
color: Colors.teal,
child: GestureDetector(
onTap: () {
//Calling method changeTabMethod(0)
// HomeScreen().createState().changeTabMethod(0);
widget.onButtonPressed(0);
},
child: Container(
margin: EdgeInsets.only(top: 20.0),
height: 40.0,
width: 150.0,
color: Colors.white,
child: Center(
child: Text('Profile'),
),
),
),
),
),
);
}
}
This is actually quite simple, you just need to pass the function off to the child widget.
So your ProfileFragment will take a variable called changeTab of type Function(int):
Function(int) changeTab;
ProfileFragment(this.changeType); // Constructor
You pass that off when you create the widget inside the _HomeScreenState:
List<Widget> _widgetOptions1 = <Widget>[
…
ProfileFargment(changeTabMethod),
];
Then you can call the function directly in the _ProfileFragmentState:
onTap: () => widget.changeTab(0);
I'm facing an issue of "Multiple GlobalKeys in the Widget tree" while Navigation.
I have a BottomNavigationBar & a Drawer defined in the Scaffold of a Base Screen and in the body parameter of Scaffold I have multiple screens which I'm accessing with BottomNavigationBar. The thing is, I'm accessing the Drawer of the Base Screen from one of the multiple screens by using a GlobalKey, and everything's working fine but when I Navigate to the Base Screen from Another Screen then I get the above-mentioned error.
I have tried a solution of not using a static keyword while defining the key and it solves the error of navigation but then I can't access the Drawer because then I get another error of "method 'openDrawer' was called on null".
This is a separate class where I have defined the Key:
class AppKeys {
final GlobalKey<ScaffoldState> homeKey = GlobalKey<ScaffoldState>();
}
This is the Base Screen:
class Base extends StatefulWidget {
#override
_BaseState createState() => _BaseState();
}
class _BaseState extends State<Base> {
int selectedScreen = 0;
final screens = List<Widget>.unmodifiable([Home(), Cart(), Orders(), Help()]);
AppKeys appKeys = AppKeys();
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
key: appKeys.homeKey,
body: screens[selectedScreen],
bottomNavigationBar: SizedBox(
height: 80,
child: BottomNavigationBar(
onTap: (val) {
setState(() {
selectedScreen = val;
});
},
currentIndex: selectedScreen,
selectedItemColor: AppColor.primary,
elevation: 20.0,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
iconSize: 25,
selectedFontSize: 15,
unselectedFontSize: 15,
items: [
BottomNavigationBarItem(
icon: Icon(AnanasIcons.home), title: Text("Home")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.cart), title: Text("Cart")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.orders), title: Text("My Orders")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.help), title: Text("Help")),
],
),
),
);
}
}
This is the Home Screen from where I'm accessing the Drawer:
class Home extends StatelessWidget {
final AppKeys appKeys = AppKeys();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(
AnanasIcons.menu,
color: Colors.black,
),
onPressed: () {
appKeys.homeKey.currentState.openDrawer();
}),
backgroundColor: Theme.of(context).canvasColor,
title: Text("Hi"),
actions: [
IconButton(
icon: Icon(
Icons.person,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(),
));
})
],
),
body: Container(),
);
}
}
I've found the answer! Instead of pushing the route with the key, we need to remove all the routes from the stack till the screen where we have to go.
Here's the code for that:
Navigator.of(context).popUntil(ModalRoute.withName(Base.routeName));
Please, anyone, tell me how can I make Bottom Navigation Bar visible on every page of my app in flutter? I know there's an option called Custom Navigator (https://pub.dev/packages/custom_navigator), but how to use this for more than 2 subpages? Please help me I am stucked on a big project. Thank you in Advance :)
you just need to change widgets on the same page, not navigating, check this code out!
import 'package:flutter/material.dart';
import './pages/home.dart'; //imported widget 1
import './pages/listed_homes.dart'; //imported widget 2
import './widgets/form_card.dart'; //imported widget 3
class BottomNav extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return BottomNavState();
}
}
class BottomNavState extends State<BottomNav> {
int _currentIndex = 0; //initialize index that alters widgets when increased or decreased`
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (value) {
_currentIndex = value;
setState(() {});
},
items: [
BottomNavigationBarItem(
//<--- item 1 text and icon declared
icon: Icon(Icons.book),
title: Text('Find a home')),
BottomNavigationBarItem(
//<--- item 2 text and icon declared
icon: Icon(Icons.add_a_photo),
title: Text('Enlist a home')),
BottomNavigationBarItem(
//<--- item 3 text and icon declared
icon: Icon(Icons.message),
title: Text('Messages')),
]),
body: Stack(children: [
[
Home(_cent), //widget one
FormCard(widget.model), //widget two
Messages() //widget three
][_currentIndex], //Alter widgets with changing index
Positioned(
top: 30,
left: 15,
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
padding: EdgeInsets.all(0.0),
iconSize: 40.0,
),
)
]),
);
}
}
Check this method to keep a widget on every page:
MaterialApp(
title: 'Flutter Demo',
initialRoute:"/home",
routes: [
...
],
builder: (context, child) {
return Stack(
children: [
child!,
Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
return YourCustomWidget(); *//This widget now appears on all pages*
},
),
],
),
],
);
},