How to rebuild only modified item in list with ChangeNotifier - flutter

I am trying to fetch the data from server. When the button is pushed, a dummy widget is added to the list and response is shown after data is fetched.
I called notifyListeners() when item is added to the list and when data is loaded, but all of items are rebuilt even unchanged items.
How can I prevent rebuilding unchanged item?
Here's my code.
class Item {
bool isLoaded;
String request;
String data;
Item(this.request) : isLoaded = false;
Future loadItemData() {
// dummy for api request
return Future.delayed(Duration(seconds: 3)).whenComplete(() {
data = "item get result";
isLoaded = true;
});
}
}
class ItemList extends ChangeNotifier {
List<Item> lists = [];
void addItem(String request) {
var item = Item(request);
lists.add(item);
item.loadItemData().whenComplete(() {
notifyListeners();
});
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ItemList(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyItems(),
),
);
}
}
class MyItems extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Items"),
),
body: Consumer<ItemList>(
builder: (context, value, child) {
return Column(
children: <Widget>[
RaisedButton(
child: const Text("Add Item"),
onPressed: () {
value.addItem("dummy request id");
},
),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
var item = value.lists[index];
return item.isLoaded
? ListTile(
title: Text(value.lists[index].data),
)
: ListTile(
leading: CircularProgressIndicator(),
);
},
itemCount: value.lists.length,
),
),
],
);
},
),
);
}
}

Make use of Unique keys with ListTile.
ListTile(
key: ValueKey(value.lists[index].data['id']),
...
)

Related

Handle Android system back button in Flutter app with nested navigators

I have a Flutter app composed of "sub-apps" with different theme colors.
In a sub-app, the AppBar's back button is working as expected: it navigates back to the previous sub-app page.
However the Android system's back button is NOT working as expected: it navigates directly to the root app.
The expected back navigation in the following example should be: B3 B2 B1 A3 A2 A1. But it's B3 A3 A2 A1.
In other words, I want the Android back button to work the same way as the Flutter back button.
The same problem happens with iOS "back swipe" gesture (iosPageTransition = true).
Please try the following code on an Android device or emulator and test with the system's back button.
Note that I use multiple MaterialApps to apply a color theme to all screens of a sub-app.
Also note that WillPopScope doesn't work since it's not triggered by the system's back button.
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
_ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
_AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
_AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _AppB(_PageB(1));
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
var scaffold = Scaffold(
appBar: AppBar(
leading: number == 1 ? BackButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop()) : null,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
return scaffold;
}
}
First of all you can't set two MaterialApp in single flutter app its wrong way to use it, in your code it initialize 2 times.Just change your theme from the page you want to update,
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final iosPageTransition = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _PageA(1),
);
}
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.red),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _PageB(1);
return _PageA(number + 1);
}));
},
),
),
);
}
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.blue),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: () => Navigator.pop(context)) ,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
}
),
);
}
}
As Dharini said you cannot use 2 material apps, but if you really want to, then here is the workaround for you
Issue: When you try to B(n) it pops the B as it is new material app.
|
|_ A
|_ A1
|_ A2
|_ B
|_ B1
|_B2
Resolution: Pass the BuildContext and Route from B to A and remove route on back tap of icon or android back key.
Navigator.removeRoute(context, route);
Workaround
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
const _ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
const _AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
const _AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
_PageA(this.number);
final bRoutes = <BuildContext, Route>{};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) {
return WillPopScope(
onWillPop: () async {
if (number > 2 && bRoutes.isNotEmpty) {
Navigator.removeRoute(bRoutes.entries.last.key, bRoutes.entries.last.value);
bRoutes.remove(bRoutes.entries.last.key);
return false;
} else {
return true;
}
},
child: _AppB(_PageB(1, bRoutes)),
);
}
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
final Map<BuildContext, Route> bRoutes;
const _PageB(this.number, this.bRoutes);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
leading: BackButton(onPressed: () {
if (bRoutes.isNotEmpty) {
final context = bRoutes.entries.last.key;
final route = bRoutes.entries.last.value;
bRoutes.remove(bRoutes.entries.last.key);
Navigator.removeRoute(context, route);
}
})
,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
final route = MaterialPageRoute(builder: (context) {
return _PageB(number + 1, bRoutes);
});
bRoutes[context] = route;
Navigator.push(context, route);
},
),
),
);
}
}

How to pass data to a class in a flutter app

I am tryin to pass data to a class in flutter but it is failing with this error and i dont know how to debug it, I am new to flutter:
errors.dart:187 Uncaught (in promise) Error: Invalid argument(s): feed not found
How do I know which feed it can not find?
I tried following this but ity is not working. Here is my code for main.dart:
void main() async {
final AtomFeed feed = await RssService().getFeed();
runApp(Start(feed));
}
class Start extends StatelessWidget {
final AtomFeed feed;
Start(this.feed);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 5,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(text: 'First', icon: Icon(Icons.music_note)),
Tab(text: 'Second', icon: Icon(Icons.music_video)),
],
),
title: Text('Start'),
backgroundColor: Colors.green,
),
body: TabBarView(
children: [
First(this.feed),
MyHomePage(title: 'Wowzers!'),
],
),
),
),
);
}
}
Then in First:
class First extends StatelessWidget {
final AtomFeed feed;
First(this.feed);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('RSS'),
),
body: ListView.builder(
itemCount: this.feed.items.length,
itemBuilder: (BuildContext ctxt, int index) {
final item = this.feed.items[index];
return ListTile(
title: Text(item.title),
subtitle: Text('Published at ' +
DateFormat.yMd().format(DateTime.parse(item.published))),
contentPadding: EdgeInsets.all(16.0),
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewContainer(
item.id.replaceFirst('http', 'https'))));
},
);
}),
);
}
}
I renamed all feed variables to feed1, feed2, ect. The problem was not from any of my variables, it was from a method to the rss service.

flutter int got to zero

I have a screen with a one button and anotherone with a Container to show a number. I declared a variable in the StatlessWidget class. The button adds 1 to the variable , however after leaving the Class with the container und return to it, I noticed the widgets get updated and my variable loses its value. I have tried initializing it in initState() but it still loses it's value.
import 'package:flutter/material.dart';
import 'package:generator/route_generator.dart';
import 'package:generator/main.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/menu',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class Menu extends StatelessWidget {
int data = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Menu'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/second', arguments: data);
},
child: Text('go to the second'),
),
],
),
));
}
}
class FirstPage extends StatelessWidget {
int data = 0;
void eins() {
data = data + 25;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
data.toString(),
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/second', arguments: data);
},
child: Text('go to the second'),
),
RaisedButton(
child: Text('25'),
onPressed: eins,
)
],
),
));
}
}
class SecondPage extends StatelessWidget {
int data = 0;
SecondPage({Key key, #required this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
data.toString(),
style: TextStyle(fontSize: 20),
),
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/first');
},
child: Text('go to the first'),
),
],
),
));
}
}
another class
import 'package:flutter/material.dart';
import 'package:generator/main.dart';
import './main.dart';
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case '/first':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/third':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/menu':
return MaterialPageRoute(
builder: (_) => Menu(),
);
case '/second':
// if (args is int) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
//}
// return _errorRoute();
//default:
//return _errorRoute();
}
}
static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('ERROR'),
),
);
});
}
}
The first thing that is weird about your program is that you want to preserve state, in your case a counter variable, but to do that, you select a StatelessWidget. At the very least you will need a StatefulWidget. It's in the name already.
That said, it's not that easy, you may want to look up the different approaches to state management in Flutter: https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
To expand on what #nvoigt said, pick a state management solution instead of passing around arguments from page to page. This way you can keep your widgets stateless, which is preferred but not possible to do what you want to do without a state management solution.
Here's a quick way using GetX state management. This can be done using Provider, RiverPod, Bloc/Cubit...pick your poison.
Here's a new controller class with your data and logic.
class DataController extends GetxController {
int data = 0;
void eins() {
data += 25;
update();
}
}
Then a couple small changes to the rest of your good and you're good to go.
void main() {
Get.put(DataController()); // initializing your controller
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/menu',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class Menu extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Menu'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/second');
},
child: Text('go to the second'),
),
],
),
));
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<DataController>(); // finding controller
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<DataController>( // wrap your text in GetBuilder to display variabe
builder: (_) {
return Text(
controller.data.toString(), // accessing variable via controller
);
},
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/second');
},
child: Text('go to the second'),
),
RaisedButton(
child: Text('25'),
onPressed: () {
controller.eins(); // accessing function via controller
}),
],
),
));
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<DataController>(); // finding same instance of controller on new page
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<DataController>(
builder: (_) {
return Text(
controller.data.toString(),
);
},
),
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/first');
},
child: Text('go to the first'),
),
],
),
));
}
}
// no longer need to pass anything in your router below
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/first':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/third':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/menu':
return MaterialPageRoute(
builder: (_) => Menu(),
);
case '/second':
// if (args is int) {
return MaterialPageRoute(
builder: (_) => SecondPage(),
);
//}
// return _errorRoute();
//default:
//return _errorRoute();
}
}

How to change state of MaterialPageRoute?

I was following the tutorial from the Flutter docs where you create a Startup naming app. The app consists in two pages: one where there's an infinite list of randomly generated startup names that you can add to your favorites, and a favorites page where you can see the names you saved.
After completing the tutorial, I tried to add some functionality of my own, I wanted to be able to Unfavorite a name by tapping it on the "Favorites" page. Below is the code that pushes the Favorites page to the navigator:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// Code I added //
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_saved.remove(pair);
});
},
// End //
);
},
);
final List<Widget> divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved suggestions'),
),
body: ListView(children: divided),
);
},
),
);
}
But it didn't worked as it should: you can indeed unsave names by tapping them, but the changes will only be shown on the screen after you go back to the main page and then to the favorites page again (or in other words, when Builder is called?).
So how do I fix this? Do I need to create a Stateful widget for the favorites page? If yes, how do I pass the _saved set to my new widget?
If anybody needs the whole code:
https://pastebin.com/asLneaKe
Wrap with StatefulBuilder works fine.
You can see full code and working demo
code snippet
MaterialPageRoute<void>(
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
final Iterable<ListTile> tiles = _saved.map(
working demo
full code
import 'package:english_words/english_words.dart' as prefix0;
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
primaryColor: Colors.white,
),
home: RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
RandomWordsState createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final Set<WordPair> _saved = Set<WordPair>();
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Startup Name Generator'), actions: <Widget>[
// Icone 3 linhas
IconButton(
icon: Icon(Icons.list),
onPressed: _pushSaved,
),
]),
body: _buildSuggestions(),
);
}
Widget _buildRow(WordPair pair) {
final bool alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
});
}
Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// Code I added //
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_saved.remove(pair);
});
},
// End //
);
},
);
final List<Widget> divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved suggestions'),
),
body: ListView(children: divided),
);
});
},
),
);
}
}

Best practices for layouts with AppBar and Drawer: re-use vs "copy/paste"

I'm pretty new to Flutter and am looking for some "best practice" advice when it comes to building page layouts. I come from a Java background where I've always re-used as much as possible, but I'm not sure that's really the best approach here. I have several pages that will all have an Appbar but with their own actions. Each of these pages will share a common Drawer. Initially, I started going down the path of creating a common root page Widget where, when selecting an item in the drawer, the body of the common page changes, like this:
class HomePage extends StatefulWidget {
final BaseAuth auth;
final Function onSignedOut;
const HomePage({Key key, this.auth, this.onSignedOut}) : super(key: key);
#override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
final drawerItems = [
new DrawerItem("Home", Icons.home),
new DrawerItem("Pantry", Icons.store),
new DrawerItem("Barcode Scanner", Icons.scanner)
];
int _selectedDrawerIndex = 0;
bool _isEmailVerified;
_getDrawerItemWidget(int pos) {
switch (pos) {
case 0:
return new HomePageFragment();
case 1:
return new UserPantryFragment();
case 2:
return new BarcodeScannerFragment();
default:
return new Text("Error");
}
}
_onSelectItem(int index) {
setState(() => _selectedDrawerIndex = index);
Navigator.of(context).pop(); // close the drawer
}
#override
Widget build(BuildContext context) {
var drawerOptions = <Widget>[];
for (var i = 0; i < drawerItems.length; i++) {
var d = drawerItems[i];
drawerOptions.add(new ListTile(
leading: new Icon(d.icon),
title: new Text(d.title),
selected: i == _selectedDrawerIndex,
onTap: () => _onSelectItem(i),
));
}
AuthenticationContext authenticationContext =
AuthenticationContext.of(context);
return new FutureBuilder<FirebaseUser>(
future: authenticationContext.auth.getCurrentUser(),
initialData: null,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> data) {
var name = data.data != null ? data.data.displayName : "";
var email = data.data != null ? data.data.email : " ";
var photoUrl = data.data != null && data.data.photoUrl != null
? data.data.photoUrl
: null;
return new Scaffold(
appBar: new AppBar(
title: new Text(drawerItems[_selectedDrawerIndex].title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
},
),
// overflow menu
PopupMenuButton<String>(
// onSelected: _signOut,
itemBuilder: (BuildContext context) {
return ['Logout'].map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
)
]),
drawer: new Drawer(
child: new Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text(name != null ? name : ""),
accountEmail: Text(email),
currentAccountPicture: CircleAvatar(
// backgroundImage: FadeInImage.memoryNetwork(
// placeholder: kTransparentImage,
// image: photoUrl != null ? photoUrl : "",
// ).image,
child: new Text(
photoUrl == null ? email[0].toUpperCase() : ""),
),
),
new Column(children: drawerOptions)
],
),
),
body: _getDrawerItemWidget(_selectedDrawerIndex));
});
}
However, I'm now wondering if it would be better to just create the Scaffold from scratch in each screen and not try to use a shared root page as I'm running into issues with easily customizing the AppBar for each page. I was initially thinking I could just create some "buildAppBar" function on each of the page Widgets and have the root page use that, but that does not seem to be an easily achievable solution...at least not in an elegant way that I can find.
You could extend StatelessWidget to add custom params to the class and return a customized Scaffold in the build method. Something along the lines of:
class MyScaffold extends StatelessWidget {
final Widget option1;
final Widget option2;
final Widget body;
const MyScaffold({
this.option1,
this.option2,
this.body,
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: option1,
drawer: option2,
body: body,
);
}
}
You can also copy other properties from the Scaffold class and add them as members to MyScaffold (remember to initialize them in the constructor like the body and options params).
Another option to pass state (read: variables) down the widget tree is InheritedWidget
Create a separate widget for the drawer and just use in anywhere you need to.
Manage the Drawer State with a Provider to Manage State
class DrawerStateInfo with ChangeNotifier {
int _currentDrawer = 0;
int get getCurrentDrawer => _currentDrawer;
void setCurrentDrawer(int drawer) {
_currentDrawer = drawer;
notifyListeners();
}
void increment() {
notifyListeners();
}
}
Adding State Management to the Widget tree
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider<DrawerStateInfo>(
builder: (_) => DrawerStateInfo()),
],
);
}
}
Creating The Drawer Widget for reuse in application
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
#override
Widget build(BuildContext context) {
var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text(
"Home",
style: currentDrawer == 0
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "Home") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(0);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) =>
MyHomePage(title: "Home")));
},
),
ListTile(
title: Text(
"About",
style: currentDrawer == 1
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "About") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => MyAboutPage()));
},
),
],
),
);
}
}
Use of Drawer in one of your pages
class MyAboutPage extends StatefulWidget {
#override
_MyAboutPageState createState() => _MyAboutPageState();
}
class _MyAboutPageState extends State<MyAboutPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About Page'),
),
drawer: MyDrawer("About"),
);
}
}