Flutter stateful widget is not updating while calling from Navigation drawer - flutter

I am trying to update my stateful widget of my class while calling it from Navigation Drawer. stateless widget are being updated when they are called from Navigation Drawer. Here is my Navigation drawer from where I am calling 'Fragment First'.
class DrawerItem {
String title;
IconData icon;
DrawerItem(this.title, this.icon);
}
class HomePage extends StatefulWidget {
final drawerItems = [
new DrawerItem("First Fragment", Icons.rss_feed),
new DrawerItem("Second Fragment", Icons.local_pizza),
new DrawerItem("Third Fragment", Icons.info)
];
#override
State<StatefulWidget> createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
int _selectedDrawerIndex = 0;
_getDrawerItemWidget(int pos) {
switch (pos) {
case 0:
return new FirstFragmen(pos);
case 1:
return new FirstFragmen(pos);
case 2:
return new FirstFragmen(pos);
default:
return new Text("Error");
}
}
_onSelectItem(int index) {
setState(() => _selectedDrawerIndex = index);
Navigator.of(context).pop(); // close the drawer
}
#override
Widget build(BuildContext context) {
List<Widget> drawerOptions = [];
for (var i = 0; i < widget.drawerItems.length; i++) {
var d = widget.drawerItems[i];
drawerOptions.add(
new ListTile(
leading: new Icon(d.icon),
title: new Text(d.title),
selected: i == _selectedDrawerIndex,
onTap: () => _onSelectItem(i),
)
);
}
return new Scaffold(
appBar: new AppBar(
// here we display the title corresponding to the fragment
// you can instead choose to have a static title
title: new Text(widget.drawerItems[_selectedDrawerIndex].title),
),
drawer: new Drawer(
child: new Column(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: new Text("John Doe"), accountEmail: null),
new Column(children: drawerOptions)
],
),
),
body: _getDrawerItemWidget(_selectedDrawerIndex),
);
}
}
Here is Fragment First:
class FirstFragment extends StatefulWidget {
int pos;
FirstFragment(this.pos);
#override
_FirstFragmentState createState() => new _FirstFragmentState(pos);
}
class _FirstFragmentState extends State<FirstFragment> {
int pos;
_FirstFragmentState(this.pos);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Center(
child: new Text("Hello Fragment $pos"), >printing 'pos' only. It remains
> same all time when new class is called.
);
}
}
if I am using stateless widget then its being updated, but stateful widget is not being updated. I've tried to debug using breakpoints but _FirstFragmentState class is called only once. Is there any way to redraw all widgets when its called second time.

The state is created once and then shared for multiple instances of your widget. Since you're taking pos in the state constructor, it's not being updated later when widgets change.
One way to solve this would be to remove the pos in your _FirstFragmentState, and reference the pos in FirstFragment directly. You can access it through the widget field of your state class.
class _FirstFragmentState extends State<FirstFragment> {
#override
Widget build(BuildContext context) {
return new Center(
child: new Text("Hello Fragment ${widget.pos}"), // -> use pos from FirstFragment
);
}
}

Related

Reusing a Widget with Setters

New to Dart/Flutter and unsure if this is the proper way of going about this, but I want to make a class for an ElevatedButton widget that I can use over-and-over and only need to set the text and callback (onPressed) for each instance of ElevatedButton.
For now I'm just trying to get to the point where I can make a list of Widgets (ElevatedButtons) where I set each button text, but am struggling. This is what I have:
class AElevatedButton extends StatefulWidget
{
AElevatedButton({Key? key}) : super(key:key);
#override
State<AElevatedButton> createState() => ElevatedButtonState();
}
class ElevatedButtonState extends State<AElevatedButton>
{
String buttonText = "Button";
void setText(String buttonText) {
setState(() {
this.buttonText = buttonText;
});
}
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: null,
child: Text(buttonText)
)
);
}
}
In my "home page" State I have below but am not able to access the "setText" method in ElevatedButtonState, which somewhat makes sense as I'm creating a ElevatedButton object and not a ElevatedButtonState. Unsure if there is a way to get the state so I can call the method?
class _MyHomePageState extends State<MyHomePage>
{
AElevatedButton firstButton = new AElevatedButton();
AElevatedButton secondButton = new AElevatedButton();
void initiateButtons()
{
firstButton.setText("Button 1"); <---- error
secondButton.setText("Button 2"); <---- error
}
#override
Widget build(BuildContext context) {
initiateButtons();
return Scaffold(
appBar: AppBar(
title: const Text("Test Buttons")
),
body:
Column(
children: <Widget>[
firstButton,
secondButton
])
);
}
}
It seems that what you're making is more or less a wrapper of an existing widget with your own customization. This is rather common in Flutter, and actually its how a lot of material widgets are implemented, just setting up a bunch of properties with a predefined state.
In your case the correct way to achieve what you want, is to make a new class just as you did, but you don't need to create setters to change the state/attributes of your new widgets. Simply pass them in the constructors of your new class, for example:
class AElevatedButton extends StatelessWidget {
final String text;
final VoidCallback? onTap;
const AElevatedButton({required this.text,this.onTap});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: onTap,
child: Text(text)
)
);
}}
Here there is only text and onTap, but you could add more attributes as your necessities change, and to use them is just like any other flutter widget:
class _MyHomePageState extends State<MyHomePage>
{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Test Buttons")
),
body:
Column(
children: <Widget>[
AElevatedButton(text:"Button 1",onTap:testcallback1),
AElevatedButton(text:"Button 2",onTap:testcallback2)
])
);
}
}
If I missed any point or could clarify something more, do comment.
After some experimenting, I got a solution but unsure if this is the best way:
class AElevatedButton extends StatefulWidget
{
AElevatedButton({Key? key}) : super(key:key);
String buttonText = "Default";
Function() cb = nothingfunc;
#override
State<AElevatedButton> createState() => ElevatedButtonState();
}
class ElevatedButtonState extends State<AElevatedButton>
{
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: widget.cb,
child: Text(widget.buttonText)
)
);
}
}
And the homepage State:
AElevatedButton firstButton = new AElevatedButton();
AElevatedButton secondButton = new AElevatedButton();
void initiateButtons()
{
firstButton.buttonText = "Button 1";
firstButton.cb = testcallback1;
secondButton.buttonText = "Button 2";
secondButton.cb = testcallback2;
}
Some misc. functions added for testing above:
void nothingfunc()
{
}
void testcallback1()
{
print("Button 1 pressed");
}
void testcallback2()
{
print("Button 2 pressed");
}

Conditional Rendering of Widgets Flutter

Hello I am new to flutter and have created a form builder app. I am using Cloud Firestore as my database. The issue I am having is trying to render specific widgets based on a conditional. I have build the form and have the object data set up like this: Each projects has a list of Questions objects that hold the question information. I want to iterate through the question objects and check the question type then based on that question type render a certain widget (EX. Multiple choice, image upload, short answer, etc). Ideally it would be display as one question per page with a next button. I would like to stay on one page and just call/display other widgets/functions as the user presses next.
The issue is that I can not get the widgets to render and/or redirect by a conditional statement. Right now I set up a simple for each loop with a switch statement nested inside however since the iteration continues until it the last object the pages never render. My goal is to add a button when on pressed would continue the iteration.
I have a view project page that sets up the GetProject object and then calls the necessary class functions to get the data from firestore.
class Questions{
final String question;
final String number;
final String type;
Questions({this.question, this.number, this.type});
List<String> answers = new List();
}
class GetProject {
final String docID;
final String title;
GetProject({this.docID, this.title});
List<Questions> questions = new List();
//....Class Functions below not relevant
}
Here is what I have so far for the view project page
import 'package:flutter/material.dart';
import '../../models/project.dart';
import '../../Services/getproject.dart';
import 'textquestion.dart';
import 'multiplechoicequestion.dart';
import 'UserLocationInfo.dart';
import 'shortanswerquestion.dart';
class ViewProject extends StatefulWidget {
final String docIDref;
final String title;
ViewProject({this.docIDref, this.title});
#override
_ViewProjectState createState() => _ViewProjectState();
}
class _ViewProjectState extends State<ViewProject> {
#override
Widget build(BuildContext context) {
GetProject project = new GetProject(docID: widget.docIDref, title: widget.title);
project.getdataFromProject();
project.questions.forEach((e){
var type = e.type;
switch(type){
case 'TextInputItem':
return new TextQuestionWidget(question: e);
case 'MultipleChoice':
return new MultQuestionWidget(question: e);
case 'ShortAnswer':
return new ShortAnswerQuestion(question: e);
case 'UserLocation':
return new UserLocationInfo(question: e);
}
return null;
});
//project.printproj();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Card(
child: ListTile(
title: Text('Print Values to debug console'),
subtitle: Text(''),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
project.printproj();
}
),
),
),
);
}
}
Here is an example of what the called widget will look like
import '../../Services/getproject.dart';
class UserLocationInfo extends StatefulWidget {
final Questions question;
UserLocationInfo({this.question});
#override
_UserLocationInfoState createState() => _UserLocationInfoState();
}
class _UserLocationInfoState extends State<UserLocationInfo> {
#override
Widget build(BuildContext context) {
return Container(
);
}
}
Very Interesting Question!!
I posted some code using FutureBuilder. When you push the button labeled 'NEXT' a Widget is displayed based in a random number (random number could be a database result)
Thank you.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Future<int> _newRandomNumber() async{
print("_newRandomNumber");
return await Random().nextInt(4);
}
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(title: Text("Random Widget")),
body: Center(child:
FutureBuilder(
initialData: 0,
future:_newRandomNumber(),
builder: (context, snapshot) {
if(snapshot.hasData){
return getRandomWidget(snapshot.data);
}
}
)
)),
);
}
Widget getRandomWidget(int randomNumber) {
switch(randomNumber){
case 0:
return Column(children: <Widget>[
Text("TextInputItem",textScaleFactor: 4),
getNextButton()
]);
break;
case 1:
return Column(children: <Widget>[
Text("MultipleChoice",textScaleFactor: 4),
getNextButton()
]);
break;
case 2:
return Column(children: <Widget>[
Text("ShortAnswer",textScaleFactor: 4),
getNextButton()
]);
break;
case 3:
return Column(children: <Widget>[
Text("UserLocation",textScaleFactor: 4),
getNextButton()
]);
break;
}
}
Widget getNextButton(){
return RaisedButton(
child: Text("NEXT"),
color: Colors.red,
onPressed: () {
setState(() {
_newRandomNumber();
});
}
);
}
}
It seems like you have few problems that needs to be addressed in your code, so this might not be what exactly you want to achieve, but it will give you a general directions to go from here.
class ViewProject extends StatefulWidget {
final String docIDref;
final String title;
ViewProject({this.docIDref, this.title});
#override
_ViewProjectState createState() => _ViewProjectState();
}
class _ViewProjectState extends State<ViewProject> {
GetProject project;
int _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
// when there is no questions(still loading), show a progress indicator
body: project.questions == null
? Center(child: CircularProgressIndicator())
// IndexedStack will hide all children except the "index"th child
: IndexedStack(
index: _currentPage,
children: project.questions
.map((question) => _question(question))
.toList(),
),
);
}
#override
void initState() {
project = GetProject(docID: widget.docIDref, title: widget.title);
super.initState();
}
// Call this function when you want to move to the next page
void goToNextPage() {
setState(() {
_currentPage++;
});
}
Future<void> _getQuestions() async {
// you mentioned you use firebase for database, so
// you have to wait for the data to be loaded from the network
await project.getdataFromProject();
setState(() {});
}
Widget _question(Questions question) {
switch (question.type) {
case 'TextInputItem':
return TextQuestionWidget(question: e);
case 'MultipleChoice':
return MultQuestionWidget(question: e);
case 'ShortAnswer':
return ShortAnswerQuestion(question: e);
case 'UserLocation':
return UserLocationInfo(question: e);
}
}
}
I was not sure where you wanted to call the goToNextPage(), so you would have to decide where to call it.

Flutter Bloc Design - Inform pages about changes

is there a best practice for this? (Im using this Todo example since its easier to explain my problem here)
TodoOverviewPage (Shows all todos)
TodoAddPage (Page to add todos)
Each page has an own Bloc.
Steps:
From the TodoOverviewPage I navigate wuth pushNamed to TodoAddPage.
In TodoAddPage I add several Todos.
Using the Navigation Back Button to go back to TodoOverviewPage
Question: How should I inform TodoOverviewPage that there are new Todos?
My approaches which Im not sure if this is the right way.
Solutions:
Overwriting the Back Button in TodoAddPage. To add a "refresh=true" property.
Adding the Bloc from TodoOverviewPage to TodoAddPage. And setting the State to something that the TodoOverviewPage will reload todos after building.
Thank you for reading.
EDIT1:
Added my temporary solution till I find something which satisfies me more.
You can achieve by different way
InheritedWidget
ValueCallback in TodoAddPage
For Example:
class Item {
String reference;
Item(this.reference);
}
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
#required Widget child,
#required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
#override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;
}
}
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
#override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
#override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
class MyTree extends StatefulWidget {
#override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
#override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
Temporary solution:
Each (root) Page which has a Bloc now always reloads when build.
The Bloc takes care for caching.
Widget build(BuildContext context) {
final PageBloc pBloc = BlocProvider.of<PageBloc >(context);
bool isNewBuild = true;
return Scaffold(
...
body: BlocBuilder<PageBlocEvent, PageBlocState>(
if (isNewBuild) {
pBloc.dispatch(PageBlocEvent(PageBlocEventType.GETALL));
isNewBuild = false;
return CircularProgressIndicator();
} else {
// Draw data
...
...
}

how to keep the state of my widgets after scrolling?

I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}

Flutter: drawer becomes unavailable when pushing subviews with Navigator

Link to source on github: https://github.com/dnn1s/flutter_navigationtest
I want to accomplish the same navigation approach as Google does with the Play Store app: the drawer lists the available "root" views (in my case, view1 to view3), while any root view can have any number of subviews and its own navigation stack. The catch is: even when the user is on one of the subviews, the drawer is still accessible by using a swipe gesture, starting from the outer left of screen and going to the center - and this is not possible with my current approach. As for the Play Store app, when you tap on an app to see its details, you can either go back by tapping the arrow on the upper left OR directly invoke the drawer by swiping.
main.dart: nothing fancy
void main() => runApp(new NavigationTestApp());
class NavigationTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation test',
home: new MainPage(),
);
}
}
mainpage.dart: this view includes the drawer and its items
import 'package:flutter/material.dart';
import 'view1.dart';
import 'view2.dart';
import 'view3.dart';
/// just a wrapper class for drawer items; in my original code, these include
/// icons and other properties
class DrawerItem {
String title;
DrawerItem({this.title});
}
class MainPage extends StatefulWidget {
/// list of items in the drawer
final drawerItems = [
new DrawerItem(title: 'Item 1'),
new DrawerItem(title: 'Item 2'),
new DrawerItem(title: 'Item 3')
];
#override
State<MainPage> createState() => new MainPageState();
}
class MainPageState extends State<MainPage> {
int _selectedPageIndex = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
/// dynamic title, depending on the current view
title: new Text(widget.drawerItems[_selectedPageIndex].title),
),
drawer: new Drawer(
child: new ListView(
padding: EdgeInsets.zero,
children: <Widget>[
new DrawerHeader(child: new Text('Drawer header')),
/// quick and easy way to create the items;
/// in the original
/// code, these items are built in a loop
_buildDrawerItem(0),
_buildDrawerItem(1),
_buildDrawerItem(2),
],
)
),
body: _buildCurrentPage()
);
}
Widget _buildCurrentPage() {
switch(_selectedPageIndex) {
case 0: return new View1();
case 1: return new View2();
case 2: return new View3();
}
return new Text('Invalid page index');
}
Widget _buildDrawerItem(int index) {
return new ListTile(
title: new Text(widget.drawerItems[index].title),
selected: _selectedPageIndex == index,
onTap: () => _handleSelection(index),
);
}
void _handleSelection(int index) {
setState(() {
_selectedPageIndex = index;
});
/// close the drawer
Navigator.of(context).pop();
}
}
This is the first root view:
view1.dart:
class View1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('Push Subview1'),
onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (context) {
return new SubView1();
}
));
},
);
}
}
subview1.dart:
import 'package:flutter/material.dart';
class SubView1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SubView1'),
),
body: new Text('Pushed from View1')
);
}
}
I know about the Cupertino classes, but I prefer the material design. Can someone point me in the right direction on how to implement the desired behaviour?
Since they are two different pages, drawer in one page will not be available in another page. If you do want that you should create drawer in both pages. I've wrapped everything as much as i can regarding Drawer inside a separate class called drawer.dart with comments everywhere so that it would be helpful to understand.
Plugin used
scoped_model: "^0.2.0"
mainpage.dart
import 'package:commo_drawer/drawer.dart';
import 'package:flutter/material.dart';
import 'view1.dart';
import 'view2.dart';
import 'view3.dart';
MainPageState mainPageState = new MainPageState();
class MainPage extends StatefulWidget {
#override
State<MainPage> createState() => mainPageState;
}
class MainPageState extends State<MainPage> {
MyDrawer myDrawer;
#override
void initState() {
myDrawer =
new MyDrawer(shouldRebuildState: DrawerItemClick.NEED_TO_REBUILD_STATE);
myDrawer.addListener(() {
setState(() {});
});
super.initState();
}
rebuild() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(drawerItems[myDrawer.selectedPageIndex].title),
),
drawer: myDrawer.buildDrawer(context),
body: _buildCurrentPage(),
);
}
Widget _buildCurrentPage() {
switch (myDrawer.selectedPageIndex) {
case 0:
return new View1();
case 1:
return new View2();
case 2:
return new View3();
}
return new Text('Invalid page index');
}
}
subview1.dart
import 'package:commo_drawer/drawer.dart';
import 'package:flutter/material.dart';
class SubView1 extends StatelessWidget {
final MyDrawer myDrawer = new MyDrawer(shouldRebuildState: DrawerItemClick.NEED_NOT_REBUILD_STATE);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SubView1'),
leading: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
}),
),
body: new Text('Pushed from View1'),
drawer: myDrawer.buildDrawer(context),
);
}
}
drawer.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
int _selectedPageIndex = 0;
BuildContext _context;
class MyDrawer extends Model {
DrawerItemClick shouldRebuildState;
MyDrawer({this.shouldRebuildState});
int get selectedPageIndex => _selectedPageIndex;
Drawer buildDrawer(BuildContext context) {
_context = context;
return new Drawer(
child: new ListView(
padding: EdgeInsets.zero,
children: <Widget>[
new DrawerHeader(child: new Text('Drawer header')),
buildDrawerItem(0),
buildDrawerItem(1),
buildDrawerItem(2),
],
),
);
}
Widget buildDrawerItem(int index) {
return new ListTile(
title: new Text(drawerItems[index].title),
selected: selectedPageIndex == index,
onTap: () => _handleSelection(index),
);
}
void _handleSelection(int index) {
Navigator.of(_context).pop(); // Close drawer
if (shouldRebuildState == DrawerItemClick.NEED_TO_REBUILD_STATE) {
if (_selectedPageIndex != index) {
_selectedPageIndex = index;
notifyListeners();
}
} else {
shouldRebuildState = DrawerItemClick.NEED_TO_REBUILD_STATE;
Navigator.of(_context).pop(); // Close SubView
if (_selectedPageIndex != index) {
_selectedPageIndex = index;
notifyListeners();
}
}
}
}
final drawerItems = [
new DrawerItem(title: 'Item 1'),
new DrawerItem(title: 'Item 2'),
new DrawerItem(title: 'Item 3'),
];
class DrawerItem {
String title;
DrawerItem({this.title});
}
enum DrawerItemClick { NEED_TO_REBUILD_STATE, NEED_NOT_REBUILD_STATE }