I am creating an admin dashboard, I currently have two view widgets in a row:
A side bar - 300px (not drawer, because I want it to show permanently) - which has a list.
A content widget - expanded.
Admin Dashboard View
Here is the code for the page:
import 'package:flutter/material.dart';
import 'package:webenrol/widgets/dashboard_admin.dart';
import 'package:webenrol/widgets/drawer.dart';
//TODO: add flappy_search_bar package and add to appBar
class AdminDashboard extends StatelessWidget {
//TODO: Add title
static String id = '/admin_dashboard';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Admin Dashboard - Overview'),),
body: Container(child: Row(
children: <Widget>[
//Sidebar
DashboardSideBar(),
//Main Dashboard Content
DashboardAdmin(),
],
)),
);
}
}
I am going to create other content widgets for the links in the sidebar, what I preferably would like, is have the content widget update to what is clicked on the menu, and also have the ListTile selected as active without the page needing to reload.
Is this possible and is my WebApp laid out correctly for this, or do I need to change it?
So I found a solution, I needed to use a TabController and TabView.
When I setup my TabController, I setup a Listener to listen for any events on its index.
class _State extends State<AdminDashboard> with SingleTickerProviderStateMixin{
int active = 0;
//TODO: Add title
#override
void initState() {
super.initState();
tabController = TabController(length: 5, vsync: this, initialIndex: 0)
..addListener(() {
setState(() {
active = tabController.index;
});
});
}
Then I modified my menu to animate to the correct page onTap and also be selected if the page I was on was true.
Widget adminMenu() {
return ListView(
shrinkWrap: true,
children: <Widget>[
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
selected: tabController.index == 0 ? true : false,
onTap: () {
tabController.animateTo(0);
},
),
ListTile(
leading: Icon(Icons.add),
title: Text('Add New Centre'),
selected: tabController.index == 1 ? true : false,
onTap: () {
tabController.animateTo(1);
},
),
ListTile(
leading: Icon(Icons.list),
title: Text('List Centres'),
selected: tabController.index == 2 ? true : false,
onTap: () {
tabController.animateTo(2);
},
),
ListTile(
leading: Icon(Icons.people),
title: Text('Users'),
selected: tabController.index == 3 ? true : false,
onTap: () {
tabController.animateTo(3);
},
),
ListTile(
leading: Icon(Icons.exit_to_app),
title: Text('Logout'),
selected: tabController.index == 4 ? true : false,
onTap: () {
tabController.animateTo(4);
},
),
],
);
}
Then I had to simply setup my TabBarView in the content area:
return Scaffold(
appBar: AppBar(
//TODO: Make title dynamic to page using tabController.index turnkey operator
title: Text('Admin Dashboard - Overview'),
),
body: Container(
child: Row(
children: <Widget>[
//Responsive Sidebar
DashboardSideBar(),
//Main Dashboard Content
Expanded(
child: TabBarView(controller: tabController,
children: <Widget>[
DashboardAdmin(),
Container(child: Text('Hello World!'),),
Container(child: Text('Page 3'),),
Container(child: Text('Page 4'),),
Container(child: Text('Page 5'),),
],),
),
],
)),
);
I still need to refactor my code and clean it up, but for anyone wanting to create a clean dynamic Web Dashboard this is how :)
Related
screentshot
In my app there are some features in home page,what I want is when direct to their sub pages and still keep the buttom navigation bar.
Code for navigation bar Below the answer
Code for parts of home page
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold( resizeToAvoidBottomInset:false,
body: SlidingUpPanel(
body: Center(
child:Container(
constraints: BoxConstraints.expand(),
margin: const EdgeInsets.only(top:23),
child: Column(
children: [
.....
Container(
width: 730,
height: 190,
alignment:Alignment.center,
child:Wrap(
children: <Widget>[
//...otherFiveFeatures...//
OutlinedButton(
onPressed:()async{
var nav = await Navigator.of(context).pushNamed('/routerMobileScannerPage');
if(nav==true||nav==null)
{Navigator.of(context).pushNamedAndRemoveUntil('/routerHomePage',(Route<dynamic>route)=>false);
}
},
),
],
),
)
],
),
),
),
collapsed: Container(),
panel: Center(),
),
)
);
}
To achieve this, you need to manage multiple widgets for a single selection index. For example, from Home Screen you want to navigate to Details screen keeping the Home tab selected, you need to manage a flag for that selection. Something like this.
Code to get widget based on selection
Widget _getBodyWidget() {
switch (currentIndex) {
case 0:
return shouldShowDetails ? DetailsView() : HomeView();
case 1:
return CategoriesView();
default:
return HomeView();
}
}
In the code above, there is a flag shouldShowDetails which will be assigned as true when user taps on the Details button. When user wants to come to HomeScreen, change to false.
For such scenarios, I would suggest you to use the Provider plugin. It provides us an easy way to update the widget state based on such flags.
Code for buttom navigation bar
class PageCTRLWidget extends State<statePageCTRLWidget> with AutomaticKeepAliveClientMixin{
#override
bool get wantKeepAlive => true;
int currentIndex=0;
final screens=[
stateHomePageWidget(),
Center(child: Text('Categories',style: TextStyle(fontSize: 45),),),
Center(child: Text('Assistant',style: TextStyle(fontSize: 45),),),
stateMemberPageWidget()
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: currentIndex,
children: screens,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.orange,
currentIndex: currentIndex,
onTap:(tappedIndex)=>setState(()=>currentIndex=tappedIndex),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.menu_book_rounded),
label: 'Categories',
),
BottomNavigationBarItem(
icon: Icon(Icons.add_location_alt_rounded),
label: 'Assistant',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box_rounded),
label: 'Member',
)
],
),
);
}
}
I have a Parent page, that contains a PageView, like this :
class _ParentState extends State<ParentOverview> {
String title = "" ;
List<String> pageTitles = [
"Page 1",
"Page 2",
] ;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Expenses"),
centerTitle: true,
actions: [
Padding(
padding: EdgeInsets.only(right: 20),
child: InkWell(
child: Icon(Icons.refresh),
onTap: (() {
// TODO
}),
),
),
],
),
body: PageView(
scrollDirection: Axis.vertical,
children: [
ChildPage1(),
ChildPage2(),
],
onPageChanged: ((selectedPage) {
setState(() {
title = pageTitles[selectedPage] ;
});
}),
),
);
}
}
The children of PageView are StatefulWidget.
This Parent page contains an AppBar with a button that is used to reload.
When clicking on this button, I want to make a call that reload the data contained inside Page1 and Page2.
How can I achieve that ?
I have been told to use Provider, but is this the best way to do that ?
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*
},
),
],
),
],
);
},
I want a popup menu or some kind of slide screen with options to come when i click on an icon in the app bar, however i dont want to use PopMenuButton as i dont want to use that icon. How can I do this?
My code
return new Scaffold(
appBar: new AppBar(
title: new Text("Home"),
leading: IconButton(
icon: Icon(
Icons.dehaze,
color: Colors.black,
),
onPressed: () {
// do something
},
),
),
body: new Center(...),
);
#Denise, you don't need to manually create a button and assign action for drawer menu. You can simply use drawer in Scaffold with Drawer widget like so,
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
body: Padding(
padding: EdgeInsets.all(20.0),
child: Center(
child: Column(
children: <Widget>[
Text('')
],
)
)
),
)
);
}
}
And if you wanna use different icon,
class MyAppState extends State<MyApp> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Test'),
leading: new IconButton(
icon: new Icon(Icons.dehaze),
onPressed: () => _scaffoldKey.currentState.openDrawer()),
),
drawer: Drawer(......
Hope this helps.
If the icon is the problem in PopMenuButton. You can change it by assigning icon attribute in PopMenuButton.
PopupMenuButton<Choice>(
onSelected: _select,
icon:Icon(
Icons.dehaze,
color: Colors.black,
),
itemBuilder: (BuildContext context) {
return choices.skip(2).map((Choice choice) {
return PopupMenuItem<Choice>(
value: choice,
child: Text(choice.title),
);
}).toList();
https://flutter.dev/docs/catalog/samples/basic-app-bar
in flutter I need that when I call setstate, it only rebuilds a widget
I put 2 children in a stack, I need that when a button is pressed, only the second one is rebuilt.
bool popup = false;
Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () {
setState(() {
popup = true;
});
},
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
popup = false;
});
),
],
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container( // FIRST WIDGET
key: ValueKey(1),
child: Text("Random - "+new Random().nextInt(20).toString())
),
popup ? Center(child: Text("abc")) : Text("") , // SECOND WIDGET
],
),
),
);
I expect that when I press the "+" button only the second widget will be re-built, but now it will rebuild all the contents of the stack.
thank you all.
From the official docs we can read:
"When setState() is called on a State, all descendent widgets rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree."
My suggestion, and I use it most of the times, is separate the widget that you want to rebuild in a new StatefulWidget. This way the setState only will be rebuild that widget.
class MyAppBar extends StatefulWidget
...
class _MyAppBarState extends State<MyAppBar> {
bool popup = false;
#override
Widget build(BuildContext context) {
return AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () {
setState(() {
popup = true;
});
},
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
popup = false;
});
),
],
),
}
Then call it in your Scaffold:
Scaffold(
appBar: MyAppBar(),
Other method I can suggest is using ValueNotifier or notifyListeners(). Please read this page Avoid rebuilding all the widgets repetitively. It is well explained.
Another option is to use ValueListenableBuilder:
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<bool> popup = ValueNotifier<bool>(false);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton(
// + BUTTON
icon: Icon(Icons.add),
onPressed: () {
popup.value = true;
}),
IconButton(
// - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
popup.value = false;
})
],
),
body: Center(
child: ValueListenableBuilder<bool>(
valueListenable: popup,
builder: (context, value, _) {
return Stack(
children: [
Text("Random - " + new Random().nextInt(20).toString()),
popup.value ? Center(child: Text("abc")) : Text(""),
],
);
}),
),
);
}
}
You can use StreamBuilder:
StreamController<bool> popup = StreamController<bool>();
#override
void dispose() {
popup.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () => popup.add(true),
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () => popup.add(false),
),
],
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container( // FIRST WIDGET
key: ValueKey(1),
child: Text("Random - "+new Random().nextInt(20).toString())
),
StreamBuilder<bool>(
stream: popup.stream,
initialData: false,
builder: (cxt, snapshot) {
return snapshot.data ? Center(child: Text("abc")) : Text("");
},
)
],
),
),
);
}
Remove the setState from the widget you don't want to be changed. And only use setState for the ones you need to rebuild
Or you can consider using inheritedModel widget
Here is the example from where you can learn how to build an Inherited model widget to update only specific widgets rather than the whole widgets.
https://medium.com/flutter-community/flutter-state-management-setstate-fn-is-the-easiest-and-the-most-powerful-44703c97f035