I followed this tutorial on how to create a bottom navbar and it works great apart from the fact that I dont know how to appropriately add a drawer.
Currently my code looks something like this for the screen that holds the navigational bar:
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async =>
!await navigatorKeys[currentTab].currentState.maybePop(),
child: Scaffold(
body: Stack(children: <Widget>[
_buildOffstageNavigator(TabItem.red),
_buildOffstageNavigator(TabItem.green),
_buildOffstageNavigator(TabItem.blue),
]),
bottomNavigationBar: BottomNavigation(
currentTab: currentTab,
onSelectTab: _selectTab,
),
),
);
}
And like this for my home screen:
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
endDrawer: const NavigationDrawerWidget(),
body: const _HomePageBody(),
);
}
}
All of this is functioning, however, the drawer does not hide the navigational bar when its opened. I have thought about placing the drawer instead on the screen that holds the but that introduces more problems than it fixes. Such as the hamburger icon does not appear unless I also add an appbar to the said screen and I only want it to be present on the homepage and not its "subpages" (pages that I can access through the homepage but are not the pages present on the navbar).
My next thought is that I could possibly hide the navbar when the drawer opens and reveal it when the drawer is closed again. But then there is the difficulty of animating in such a way to make it look nice and seems like a long winded solution.
At this point it seems like the navbar is the problem but I have tried redoing it so many times such that the end result would be a bar that is present on all pages (+ subpages) and saves state that it would be unfortunate to change it up again.
I would appreciate any suggestions or links to other projects/tutorials that are doing something similar. Thanks :)
Edit
here is a reproducible example, I wipped it up quite quickly so sorry for it being a bit messy
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 const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int pageIndex = 0;
final pages = [
const Page(
title: "page 1",
drawer: true,
),
const Page(
title: "page 2",
drawer: false,
),
const Page(
title: "page 3",
drawer: false,
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: pages[pageIndex],
bottomNavigationBar: Container(
color: Theme.of(context).primaryColor,
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {
setState(() {
pageIndex = 0;
});
},
icon: const Icon(
Icons.home_outlined,
),
),
IconButton(
onPressed: () {
setState(() {
pageIndex = 1;
});
},
icon: const Icon(
Icons.work_outline_outlined,
),
),
IconButton(
onPressed: () {
setState(() {
pageIndex = 2;
});
},
icon: const Icon(
Icons.widgets_outlined,
),
),
],
),
),
);
}
}
class Page extends StatelessWidget {
const Page({Key? key, required this.title, required this.drawer})
: super(key: key);
final String title;
final bool drawer;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
endDrawer: drawer ? const Drawer() : null,
body: Center(
child: Text(
title,
style: const TextStyle(
fontSize: 45,
fontWeight: FontWeight.w500,
),
),
),
);
}
}
I think you can use single scaffold and include your drawer and appBar on 1st scaffold.
return Scaffold(
endDrawer: pageIndex == 0 ? const Drawer() : null,
body: pages[pageIndex],
appBar: AppBar(),
bottomNavigationBar: Container(
Related
I'm trying to call a StatefulWidget(i.e FirstPage()) within a MaterialApp. I'm pretty much new to flutter and I don't know where I went wrong. According to my knownledge I've used StatefulWidget to tell flutter my screen on that page is going to encounter some changes in UI. But I got no idea to fix this error.
main.dart file:
import 'package:flutter/material.dart';
import 'package:flutter_project/main.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage());
}
}
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
String buttonName = "Click";
int currentIndex = 0;
return Scaffold(
appBar: AppBar(
title: const Text("App title "),
),
body: Center(
child: currentIndex == 0
? Container(
width: double.infinity,
height: double.infinity,
color: Colors.blueAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 280,
height: 80,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.circular(18),
),
backgroundColor: const Color.fromRGBO(9, 8, 99, 90),
foregroundColor: Colors.red,
),
onPressed: () {
setState(() {
buttonName = "Clicked";
//print(buttonName0);
});
},
child: Text(buttonName),
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
//'BuildContext' - datatype and 'context' - variable name
return const SecondPage();
},
),
);
},
child: const Text("Move to new page"),
),
],
),
)
: Image.asset("images/img.png"),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(label: "Home", icon: Icon(Icons.home)),
BottomNavigationBarItem(label: "Settings", icon: Icon(Icons.settings))
],
currentIndex: currentIndex,
onTap: (int index) {
//index value here is returned by flutter by the function 'onTap'
setState(() {
currentIndex = index;
//print(currentIndex);
});
},
),
); //Scaffold represents the skeleton of the app(displays white page)
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
}
Images:
Before pressing Click and Settings button
After pressing Click and Settings looks the same
I want the screen to change the ElevatedButton Click to Clicked when onPressed() is triggered and also, the app should be able to switch settings page when the onTap() method is triggered in the bottom navigation bar.
The code worked initially when I refrained from calling an entire page of Scaffold from Material app, but as soon as I changed the part
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage()); //<-- this part
}
}
I'm getting this error.
Put your variables outside the build method.Else it will reset to default on every build.
It will be like
class _FirstPageState extends State<FirstPage> {
//here
String buttonName = "Click";
int currentIndex = 0;
#override
Widget build(BuildContext context) {
// Not here
return Scaffold(
appBar: AppBar(
More about StatefulWidget
I am creating a flutter windows app. One page has listview in a scaffold widget. There is an action button on app bar. When I move the up/down key on listview. The focus item jumps from item 2 to say item 7, instead of next item, item 3. This occurs when I use up key moves to app bar button, then down key into listview. This does not occur if I move up and down within listview. This is an example code snippet I created for illustration. I found that the Focus widget enclosing Scaffold widget causes this problem. Removing the Focus widget can solve the problem. If I replace the whole Shortcuts-Actions-Focus widget chain by FocusActionDetector, this problem also exists. As I am still confusing about flutter's focus system, this may be incorrect.
import 'package:flutter/material.dart';
import 'package:flutter/services.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(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final int nItems = 20;
late List<FocusNode> _fnList;
int idxFocus = 0;
#override
void initState() {
super.initState();
_fnList = List.generate(nItems, (i) => FocusNode());
}
#override
void dispose() {
super.dispose();
for (FocusNode fn in _fnList) {
fn.dispose();
}
}
#override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
LogicalKeySet(LogicalKeyboardKey.escape): const DismissIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
DismissIntent: CallbackAction<DismissIntent>(
onInvoke: (DismissIntent intent) => debugPrint('escape pressed'),
),
},
child: Focus(
child: Scaffold(
appBar: AppBar(
title: const Text('ListView Focus Action Example'),
actions: [
IconButton(
icon: const Icon(Icons.done),
onPressed: (){},
),
]
),
body: Center(
child: ListView.builder(
itemCount: nItems,
itemBuilder: (BuildContext context, int index) {
return Focus(
focusNode: _fnList[index],
onFocusChange: (bool focused) {
debugPrint('Focus Change: $index - $focused');
},
debugLabel: 'index: $index',
child: Card(
child: ListTile(
title: Text('item $index'),
trailing: TextButton(
onPressed: () {},
child: const Text('OK')
),
),
)
);
},
),
),
),
),
),
// ),
);
}
}
My app is now being broken down into multiple pages (separate dart files) with a bottom Navigation bar. The first page is a map, and every time I go to a different page, I return and the map widget rebuilds itself and re-initialises (markers, map.center and map.zoom, etc).
So, I introduced an IndexedStack() which has the desired effect of keeping the pages in their original state. However, when I make some changes I'd like to trigger a rebuild with setState((){}), however the error I'm then getting is
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: setState() called in constructor: _PageMapState#52149(lifecycle state: created, no widget, not mounted)
So this leads me to think that the way I'm creating and displaying these pages is perhaps wrong, as I'm trying to setState() on something that doesn't yet exist (or a different version of it)
I'd like to see if I'm using the correct methods to manage separate pages in the app state and navigation. My current setup with the IndexedStack is like so:
Main App
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My app',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'My app'),
);
}
}
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> {
Widget _widgetOptionsPages(int index) {
switch (index) {
case 0:
return PageMap();
break;
case 1:
return PageSearch();
break;
case 2:
return PageDownloads();
break;
case 3:
return PageSettings();
break;
case 4:
return PageAbout();
break;
}
return PageMap();
}
#override
Widget build(BuildContext context) {
return
Scaffold(
// appBar: AppBar(
// title: const Text('Title'),
// ),
appBar: AppBar(
backgroundColor: Colors.white,
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => btn1(),
child: const Text('BTN 1'),
),
ElevatedButton(
onPressed: () => btn2(),
child: const Text('BTN 2'),
),
ElevatedButton(
onPressed: () => btn3(),
child: const Text('BTN 3'),
),
],
),
),
bottomNavigationBar: SalomonBottomBar(
duration: Duration(milliseconds: 300),
itemPadding: EdgeInsets.all(15),
margin: EdgeInsets.fromLTRB(20,10,20,10),
currentIndex: _currentIndex,
onTap: (i) => setState(() => _currentIndex = i),
items: [
/// Nav Item to Map
SalomonBottomBarItem(
icon: Icon(Icons.map_sharp),
title: Text("Map"),
selectedColor: Colors.purple,
),
/// Nav Item to About
SalomonBottomBarItem(
icon: Icon(Icons.help),
title: Text("About"),
selectedColor: Colors.deepOrangeAccent,
),
],
),
body: _widgetOptionsPages(_currentIndex),
);
}
About Page (Example About page fragment. Map is the first page, and similar)
class PageAbout extends StatelessWidget {
const PageAbout({Key? key}) : super(key: key);
#override
Widget build (BuildContext context){
return Scaffold(
appBar: AppBar(
title: const Text('About'),
), body: Center(child: Text("About")));
}
}
I started looking at the following methods in MainApp, but they kept forcing the map to rebuild and reposition back to the starting pos (mentioned earlier)
static final List<Widget> _pagesFragments = [
PageMap(),
...
PageAbout(),
];
and
body: IndexedStack(
index: _currentIndex,
children: _pagesFragments,
),
),
Many thanks
edit: SOLUTION
i used package custom_navigator
In navigation bar I have 2 pages to redirect, but I want to navigate to third page and still want to see navigation bar (this one with 2 pages) there.
Is it possible to do? Do I have to make my own navigation bar for this page?
class Bar extends StatefulWidget {
#override
BarState createState() => BarState();
}
class BarState extends State<Bar> {
int tabIndex = 0;
List<Widget> pages = [
FirstPage(),
SecondPage(),
];
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: SafeArea(
child: BottomNavigationBar(
iconSize: 25,
elevation: 4.0,
items: <BottomNavigationBarItem>[
barItem(Icons.message),
barItem(Icons.camera_enhance),
barItem(Icons.person),
],
currentIndex: tabIndex,
onTap: (int index) {
setState(() {
tabIndex = index;
});
},
),
)),
body: Container(
child: pages.elementAt(tabIndex),
),
);
}
}
this is what i try:
List<Widget> pages = [
Container(
color: Colors.green,
child: Center(
child: ElevatedButton(
onPressed: state
),
),
),
SecondPage(),
ThirdPage()
];
state() {
tabIndex = 2;
setState(() {
});
}
The simplest way, if you don't mind it animating would be to init an AppBar in your navigator and pass it to the pages and they would use it in there scaffold.
class MyFlow extends StatefulWidget {
const MyFlow({Key? key}) : super(key: key);
#override
_MyFlowState createState() => _MyFlowState();
}
class _MyFlowState extends State<MyFlow> {
#override
Widget build(BuildContext context) {
final appBar = AppBar();
return Navigator(
onPopPage: (route, result) => true,
pages: [
MaterialPage(child: PageOne(appBar: appBar)),
MaterialPage(child: PageTwo(appBar: appBar)),
MaterialPage(child: PageThree(appBar: appBar)),
],
);
}
}
class PageOne extends StatelessWidget {
const PageOne({Key? key, required this.appBar}) : super(key: key);
final AppBar appBar;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
);
}
}
I would like to keep showing a floating button or widget in Flutter even though the page is changed by Navigator.of(context).push() like mini music player which placed in the bottom.
How can I implement that ??
You can extract the scaffold as a layout that contains a bottom sheet, and use this layout in every page you build, passing in the title, body, etc., so that the bottom sheet is persistent in all pages. Snippet below.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Persistent Bottom Sheet',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
),
initialRoute: "/",
routes: {
"/": (context) => Home(),
"/library": (context) => Library(),
},
);
}
}
class Home extends StatelessWidget {
Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Layout(
title: "Home",
body: Center(
child: Text("Home"),
),
actions: <Widget>[
InkWell(
onTap: () {
Navigator.of(context).pushNamed("/library");
},
child: Tooltip(
message: "Go To Library",
child: Padding(
padding: const EdgeInsets.all(12),
child: Icon(Icons.library_music),
),
),
)
],
);
}
}
class Library extends StatelessWidget {
Library({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Layout(
title: "Library",
body: Center(
child: Text("Library"),
),
);
}
}
class Layout extends StatelessWidget {
final String title;
final Widget body;
final List<Widget>? actions;
const Layout({
Key? key,
required this.title,
required this.body,
this.actions,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(title),
actions: actions,
),
body: body,
bottomSheet: Container(
width: double.infinity,
padding: const EdgeInsets.all(15),
color: Theme.of(context).cardColor,
child: Text("Persistent Bottom Sheet"),
),
);
}
}