Nullable class instance with Provider - flutter

I have a class named User which basically has two variables. name and age. Also, I have another class named UserList.The aim of this class is to add the user objects to a list and return that list.
User model
class User with ChangeNotifier{
late String? name;
late int? age;
//set user name;
setName(String name){
this.name=name;
}
//set user age
setAge(int age){
this.age=age;
}
}
UserList class
class UserList with ChangeNotifier{
final List<User> _list=[];
//add a new user to the list.
void addUserToList(User user){
_list.add(user);
notifyListeners();
}
//return the private list of users.
List<User>getUsers() =>_list;
}
Here is the scenario. I want to insert a value from one page and view that inserted value on the second page. Look at the pictures below. On the first page,
I insert the age and name values
Assign those values in a User object instance (user.setName, user.setAge)
Add that user object instance in a UserList list
Use provider to provide that list.(Provider.of<UserList>(context).addUserToList(user);
First Page
First Page Code
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserList()),
ChangeNotifierProvider(create: (context) => User()),
],
child: const MyApp())
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
TextEditingController nameController = TextEditingController();
TextEditingController ageController = TextEditingController();
#override
Widget build(BuildContext context) {
User user =User();
return Scaffold(
backgroundColor: Colors.grey[300],
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(
hintText: "Name",
),
),
TextField(
controller: ageController,
decoration: const InputDecoration(
hintText: "Age",
),
),
ElevatedButton(onPressed: (){
//providing userList
user.setName(nameController.text); //setting user name
user.setAge(int.parse(ageController.text)); // setting user age
Provider.of<UserList>(context,listen: false).addUserToList(user); //providing the list of users
nameController.text=""; //after insertion, clearing the text field
ageController.text="";//after insertion, clearing the text field
user=User(); // instantiating a new user object for the following insertion.
Navigator.push(context, MaterialPageRoute(builder: (context)=>const ViewUser()));
}, child: const Text("Submit"))
],
),
),
);
}
}
On the second page, display those inserted values in a ListView. Let's head to the main problem. Each item of ListViewis wrapped with a GestureDetector to be able to gain functionality.I hope everything is clear up to now.
The problem is that; When I click on each item I want to return to the first page. But, this time the TextField shouldn't be empty. It must be replaced with the value of that item. For exmple. If I click on John Nash and 43. The First page must pop up with field value of that list item. To be able to do that I use another Provider to provide User object. But, I couldn't do it because of null safety it gives an error about the user object is null. Is there any idea that could be helpful.
Second Page
Second Page code
class ViewUser extends StatelessWidget {
const ViewUser({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<User> userList = Provider.of<UserList>(context).getUsers();
return Scaffold(
appBar: AppBar(
title: const Text("Users List"),
),
body: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: userList.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: [
const SizedBox(height: 15,),
GestureDetector(
onTap: (){
Provider.of<User>(context,listen: false)
.setName(userList[index].name!);
Provider.of<User>(context,listen: false)
.setAge(userList[index].age!);
Navigator.push(context, MaterialPageRoute(builder: (context)=>MyHomePage()));
},
child: Container(
height: 50,
color: Colors.amber,
child: Center(
child: Row(
children: [
Text("Name: " + userList[index].name!),
const SizedBox(
width: 40,
),
Text("Age:" + userList[index].age.toString()),
],
),
),
),
),
],
);
}),
);
}
}

While you are using Navigator.push you can pass user as arguments.
GestureDetector(
onTap: () {
final user = userList[index];
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(),
settings: RouteSettings(
arguments: user,
)),
);
},
MyHomePage build method will be
#override
Widget build(BuildContext context) {
User user = User();
User? args = ModalRoute.of(context)?.settings.arguments as User?;
if (args != null) {
nameController.text = args.name ?? "";
ageController.text = args.age.toString();
}
return Scaffold(....
You can also use Navigator.pop in this case.
GestureDetector(
onTap: () {
final user = userList[index];
Navigator.pop(context, user);
},
And to receive data on MyHomePage
onPressed: () async {
//....
final data = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ViewUser(),
),
);
if (data != null) {
nameController.text = data.name ?? "";
ageController.text = data.age.toString();
}
More about navigate-with-arguments.

Related

I take the data from Firestore and show it with a list (card). When I click on the card, I want it to detail the data on a new page

I get the data from Firestore, but because a text is too long, I cannot show it with a card. I want to be directed to a new page using ontap and see the information on that card in more detail on that page.
how do i do this
dynamic resultant = await DatabaseManager().getUsersList();
if (resultant == null) {
print('Unable to retrieve');
} else {
setState(() {
userProfilesList = resultant;
});
}
}
updateData(String sesAd, String sesKayit, int time, String userID,
String kelimesayisi) async {
await DatabaseManager().updateUserList(sesAd, sesKayit, time, userID);
fetchDatabaseList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Langrec'),
),
body: ListView.builder(
itemCount: userProfilesList.length,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
);
},
title: Padding(
padding: const EdgeInsets.all(16.0),
child: Stack(
children: <Widget>[
Container(
Text(
userProfilesList[index]()['sesAd'],
style: TextStyle(
color: Colors.white,
fontFamily: 'Avenir',
),
),'''
You can do so by
DetailPage
class DetailPage extends StatefulWidget {
final DocumentSnapshot doc;
DetailPage({this.doc});
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Container();
}
}
Now from your onTap you can navigate to the detail page by the following code
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage(doc: userProfilesList,),));
To access the document you can use widget.doc which is a document snapshot object.
Ex:
Text('${widget.doc["Text"]}'),

How to filter list when use FutureBuilder?

I use FutureBuilder in IndexedListView to show a phonebook list,
Widget _buildBody(context) {
String url = api_url_phonebook;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
TextField(
onTap: () => onTextChanged(_controller.text),
...
),
Expanded(
child: IndexedListView(
100.0,
_getData(context, url),
...
),
),
],
),
);
}
Future<Either<ResponseError, List<dynamic>>> _getData(context, url) {
final result = RequestApi(url).fetchList();
return result;
}
onSearchTextChanged(String text) {
...
}
Now I want filter this phonebook when I input text in TextField, what can I do?
I suggest you to use the Delegate pattern to do that.
Make a class like the following:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mywallet/model/user_account.dart';
import 'package:mywallet/ui/account_item.dart';
class AccountListSearch extends SearchDelegate {
final List<UserAccount> newItems;
String selectedResult = '';
AccountListSearch(this.newItems);
bool _match(UserAccount account, String text) {
return account.name.toLowerCase().contains(text.toLowerCase()) ||
account.user.toLowerCase().contains(text.toLowerCase());
}
#override
List<Widget> buildActions(BuildContext context) => [
IconButton(
icon: Icon(Icons.close),
onPressed: () {
query = '';
},
),
];
#override
Widget buildLeading(BuildContext context) => IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
);
#override
Widget buildResults(BuildContext context) {
return Container(
child: Center(
child: Text(selectedResult),
));
}
#override
Widget buildSuggestions(BuildContext context) {
var suggestionList = <UserAccount>[];
query.isEmpty
? suggestionList = newItems
: suggestionList.addAll(
newItems.where((item) {
return _match(item, query);
}),
);
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context, index) => AccountItem(
account: suggestionList[index],
),
);
}
}
Copy and paste the AccountListSearch class, only change the UserAccount class with your data class ( PhoneBook Item), and change the AccountItem stateless widget with your Widget used to display your PhoneBook item ( something like class PhoneBookItemWidget extends StatelessWidget......)
Then in the main page like this class:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<AccountCubit, AccountState>(
builder: (_, state) {
var currentState = state;
if (currentState is InitializedState) {
var accounts = currentState.userAccounts;
return Scaffold(
appBar: AppBar(
title: Text('All Accounts'),
actions: [
IconButton(
onPressed: () {
showSearch(
context: context,
delegate: AccountListSearch(accounts));
},
icon: Icon(
Icons.search,
),
)
],
),
drawer: AppDrawer(),
body:
ListView.builder(
itemCount: accounts.length,
itemBuilder: (context, counter) => AccountItem(
account: accounts[counter],
)
....
}
As you can see, the 'magic' is in the method :
showSearch( context: context, delegate: AccountListSearch(accounts));
The method showSearch(...) is inherited from StatelessWidget and make all the work for you.
I used the Bloc pattern, but it is not important right now, only to tell where to find the list of items, but it is not directly related to your question.

Save Multi Select Choice Chips selected/unselected state in an alert-dialog

I am filtering results in my application based off of the multichoice chips that are selected. I have it filtering results properly, however when I select the done button in the alert dialog it does not save the selected state of the choice chips. It also does not clear the selected states of the choice chips when I hit the clear button. Any recommendations?
MultiFilterChoiceChips Class:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class MultiFilterChips extends StatefulWidget {
final List<String> filterList;
final Function(List<String>) onSelectionChanged;
MultiFilterChips(this.filterList, {this.onSelectionChanged});
#override
_MultiFilterChipsState createState() => _MultiFilterChipsState();
}
class _MultiFilterChipsState extends State<MultiFilterChips> {
List<String> selectedFilters = List();
_buildFilterList() {
List<Widget> filters = List();
widget.filterList..forEach((item){
filters.add(Container(
padding: const EdgeInsets.all(2.0),
child: ChoiceChip(
label: Text('$item'),
selected: selectedFilters.contains(item),
onSelected: (selected) {
setState(() {
selectedFilters.contains(item)
? selectedFilters.remove(item)
: selectedFilters.add(item);
widget.onSelectionChanged(selectedFilters);
});
},
),
));
});
return filters;
}
#override
Widget build(BuildContext context) {
return Wrap(
children: _buildFilterList(),
);
}
}
Filter Pressed (App Bar Icon) Alert Dialog:
_filterPressed() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
title: Text('Filter Scouts'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Rank:'),
_multiFilterRankChipState(),
Padding(padding: EdgeInsets.all(5)),
Text('Patrol:'),
_multiFilterPatrolChipState(),
],
)),
actions: <Widget>[
FlatButton(
child: Text("Clear"),
onPressed: () {
filter = ""; //ranks filter string to send to the sqlite database
pfilter = ""; //patrol filter string to send to the sqlite database
setState(() {
selectedRanks.clear(); //List<String> that holds the selected ranks
selectedPatrols.clear(); //List<String> that holds the selected patrols
//sends the query to the database and resets the future list builder state
// back to initial state without filters
_searchResults(searchText);
});
},
),
FlatButton(
child: Text("Done"),
onPressed: () {
Navigator.of(context).pop();
})
],
);
});
}
Rank MultiFilter Call:
_multiFilterRankChipState() {
return MultiFilterChips(ranks, onSelectionChanged: (selectedList) {
setState(() {
//selectedList = selectedRanks;
selectedRanks = selectedList;
debugPrint("SELECTED LIST ${selectedRanks.toString()}");
_RanksFilterSet();
});
});
}
For getting the list of Patrols I am getting the distinct list from the sqlite database as the list patrols change overtime thus using a future builder to get the list of strings:
Patrol MultiFilter Call:
_multiFilterPatrolChipState() {
return Container(
child: FutureBuilder<List<String>>(
future: patrols(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MultiFilterChips(snapshot.data,
onSelectionChanged: (selectedList) {
setState(() {
selectedPatrols = selectedList;
_PatrolFilterSet();
});
});
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
},
),
);
}
Let me know if you need more code! Thanks!
You can store the selected items in a Map. In this sample, multi-select mode will start on long press of an item. Multi-select mode will stop when there's no selected items left.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var selectMode = false;
Map<String, bool> listItemSelected = {
'List 1': false,
'List 2': false,
'List 3': false,
'List 4': false,
'List 5': false,
'List 6': false,
'List 7': false,
'List 8': false,
'List 9': false,
};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView(
children: listItemSelected.keys.map((key) {
return Card(
child: GestureDetector(
onTap: () {
// if multi-select mode is true, tap should select List item
if (selectMode && listItemSelected.containsValue(true)) {
debugPrint('onTap on $key');
setState(() {
listItemSelected[key] = !listItemSelected[key];
});
} else {
// Stop multi-select mode when there's no more selected List item
debugPrint('selectMode STOP');
selectMode = false;
}
},
// Start List multi-select mode on long press
onLongPress: () {
debugPrint('onLongPress on $key');
if (!selectMode) {
debugPrint('selectMode START');
selectMode = true;
}
setState(() {
listItemSelected[key] = !listItemSelected[key];
});
},
child: Container(
// Change List item color if selected
color: (listItemSelected[key])
? Colors.lightBlueAccent
: Colors.white,
padding: EdgeInsets.all(16.0),
child: Text(key),
),
),
);
}).toList(),
),
),
);
}
}
Demo

How can i get text value from TextField while using a ViewModelProvider?

I have a form (a bunch of TextField widgets), i get the data from a REST API and load it to the view using a ViewModelProvider.
Now i want to press a button "Save", get the text value of the TextField widgets and pass the data to the REST API.
I searched for how to get the value of the TextField to use it elsewhere and all i found was similar to this :
class _MyCustomFormState extends State<MyCustomForm> {
// Create a TextEditingController as a variable
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
// Set the created TextEditingController as the controller of the Text Field
controller: myController,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
// Use the text attribute of the TextEditingController variable
content: Text(myController.text),
);
},
);
},
tooltip: 'Show me the value!',
child: Icon(Icons.text_fields),
),
);
}
}
(This code snippet was taken from the Flutter website)
My problem is that i use a ViewModelProvider and i need to create the TextEditingController inside the
builder attribute of the ViewModelProvider to set the text that was retreived from the HTTP call.
Here is my current code, i left only one TextField because they are practically the same.
class SettingsView extends StatelessWidget {
const SettingsView({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ViewModelProvider<SettingsViewModel>.withConsumer(
viewModel: SettingsViewModel(),
onModelReady: (model) => model.getSettings(),
builder: (context, model, child) => Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextField(
decoration: new InputDecoration(
labelText: 'Host',
),
controller: TextEditingController.fromValue(
TextEditingValue(
text: model.settings == null? '' :model.settings.host,
),
),
),
GestureDetector(
child: CallToAction("Save"),
onTap: saveData,
),
],
),
),
),
);
}
void saveData() {
}
}
So the question is, how to pass the text value of the TextField to the saveData method?
Thank you.
Are you trying to init the TextEditingController?
If so, you can initialize the text of the controler on onModelReady, and declare the controler outside of build.
class _SettingsViewState extends State<SettingsView> {
TextEditingController myController = TextEditingController();
#override
Widget build(BuildContext context) {
return ViewModelProvider<SettingsViewModel>.withConsumer(
viewModel: SettingsViewModel(),
onModelReady: (model) {
myController.text =
model.settings == null ? '' : model.settings.host;
model.getSettings();
},
builder: (context, model, child) => Center(
child: Column(
children: <Widget>[
Expanded(
child: TextField(
decoration: new InputDecoration(labelText: 'Host'),
controller: myController,
),
),
Expanded(
child: GestureDetector(
child: CallToAction("Save"),
onTap: saveData,
),
),
],
),
),
);
}
void saveData() {
print('save data ${myController.text}');
}
}
Thanks to Thiago Dong Chen i managed to find the solution to my problem.
Thiago suggested that i create a TextEditingController variable and set its text value in onModelReady as follows
onModelReady: (model) {
myController.text = model.settings == null ? '' : model.settings.host;
model.getSettings();
},
The problem is even in onModelReady the model.settings is still null. But his solution put me in the right path. I'm new to Flutter, Dart and the anonymous functions. Seeing that i can change the => to a more classic {} made me realize that i can do the same in builder and do more that just setting the widget tree.
Here is the full code :
class SettingsView extends StatefulWidget {
const SettingsView({Key key}) : super(key: key);
#override
_SettingsViewState createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
TextEditingController _hostController = TextEditingController();
#override
Widget build(BuildContext context) {
return ViewModelProvider<SettingsViewModel>.withConsumer(
viewModel: SettingsViewModel(),
onModelReady: (model) => model.getSettings(),
builder: (context, model, child) {
_hostController.text = model.settings == null ? null : model.settings.host;
return Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextField(
decoration: new InputDecoration(
labelText: "Host",
),
controller: _hostController,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
),
SizedBox(
height: 30,
),
GestureDetector(
child: CallToAction("Save"),
onTap: saveData,
)
],
),
),
);
}
);
}
void saveData() async{
var _api = locator<Api>();
bool b = await _api.setSettings(SettingItemModel(
host: _hostController.text));
String message = '';
if (b) {
message = 'Data saved to the database!';
} else {
message = 'Couldn\'t save the data';
}
final snackBar = SnackBar(content: Text(message));
Scaffold.of(context).showSnackBar(snackBar);
}
}

How to update stateful widget in Navigation drawer while keeping same class like fragments in Android?

I want to update stateful widget of my class while returning same class after getting data from server, from navigation drawer. I am having issue that class loads data only one time and remain the same if I navigate to another item of my navigation drawer. Because the state is created only once.
Here is my code:
class CategoryFilter extends StatefulWidget {
int productIndex;
String category_name;
CategoryFilter(this.productIndex, this.category_name)
{
print("CategoryFilter");
print(productIndex);
print(category_name);
new _CategoryFilterState(productIndex, category_name);
}
#override
_CategoryFilterState createState() => new
_CategoryFilterState(productIndex, category_name);
}
class _CategoryFilterState extends State<CategoryFilter> {
int productIndex;
List<ResponseDataProducts> productList;
List data;
String category_name;
_CategoryFilterState(this.productIndex, this.category_name)
{
print("CategoryFilter");
print(productIndex);
print(category_name);
}
#override
void initState(){
super.initState();
Future<String> status = getData(productIndex);
status.then((onValue){
if(onValue.toString() == "Success")
{
Navigator.pop(context);
}
});
// this.getData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
color: Colors.white30,
child: new ListView.builder(
itemCount: productList == null ? 0 : productList.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
margin: const EdgeInsets.only( bottom: 10.0),
constraints: new BoxConstraints.expand(
height: 200.0
),
alignment: Alignment.bottomLeft,
decoration: new BoxDecoration(
image: new DecorationImage(image:
new NetworkImage
("http://myurl.com/"+productList[index].thumbnail),
fit: BoxFit.cover)
),
child:new Container(
child: new Text(
productList[index].name,
style: new TextStyle(color: Colors.white, fontSize: 30.0),
),
color: Colors.black54,
alignment: new FractionalOffset(0.5, 0.0),
height: 35.0,
// margin: const EdgeInsets.symmetric(vertical: 30.0),
),
);
})
),
) ;
}
void _onLoading()
{
showDialog(context: context,
barrierDismissible: false,
child: progress);
new Future.delayed(new Duration(seconds: 2), (){
// Navigator.pop(context);
});
}
Future<String> getData(int productIndex) async {
productList = new List<ResponseDataProducts>();
_onLoading();
http.Response response = await http.get(
Uri.encodeFull(CommonMethods.base_url + 'product/$productIndex'),
headers: {"Accept": "application/json"});
print(response.body);
setState(() {
var convertDataToJson = JSON.decode(response.body);
data = convertDataToJson["responseData"];
for(int i=0; i<data.length; i++)
{
ResponseDataProducts responseData = new ResponseDataProducts(
data[i]["id"],
data[i]["name"], data[i]["description"],
data[i]["title"], data[i]["thumbnail"]);
productList.add(responseData);
}
//Navigator.pop(context);
});
return "Success";
}
}
Here is how I am calling this categoryFilter class from Navigation Drawer:
_getDraserItemWidget(int pos)
{
switch(pos)
{
case 0:
return new Home(bar_id);
case 1:
return new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);
case 2:
return new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);
case 3:
return new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);
case 4:
return new OpeningTime();
case 5:
break;
}
}
I would suggest that instead of calling the method to load data within the initState method of your class, that you use a FutureBuilder widget. If you return a new FutureBuilder from your Navigation Drawer, that should call your service each time a new one is created, and is generally a better way of performing asynchronous requests anyways.
Here's a very simple example. It doesn't do the drawer very well (or a few other things - there's only so much time to spend on things like this), but it should illustrate the concept.
Note that rather than 'updating the widget' it simply creates a new widget. Because of the way flutter does things, this should be relatively performant, especially because you're not doing it all the time but rather only when the user selects something from the navigation menu.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new TextPage(text: "Home!"),
);
}
}
Map<int, int> _nums = Map();
class TextPage extends StatelessWidget {
final String text;
const TextPage({Key key, #required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new PreferredSize(
child: new Container(),
preferredSize: Size.fromHeight(10.0),
),
body: new Center(
child: new Text(text),
),
drawer: new Builder(
builder: (context) => Material(
child: new SafeArea(
child: Column(
children: <Widget>[
new FlatButton(
onPressed: () {
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => _getDrawerItemWidget(1)));
},
child: Text("First item"),
),
new FlatButton(
onPressed: () {
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => _getDrawerItemWidget(2)));
},
child: Text("Second item"),
),
],
),
),
),
),
);
}
_getDrawerItemWidget(int i) {
return new FutureBuilder<String>(
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.data != null) {
return new TextPage(text: snapshot.data);
} else {
return new TextPage(text: "Loading.");
}
},
future: () async {
var num = _nums.putIfAbsent(i, () => 0);
_nums[i] = num + 1;
String toReturn = "You retrieved number $i for the $num time";
return await Future.delayed<String>(Duration(seconds: 1), () => toReturn);
}(),
);
}
}
You could theoretically do something different with keeping GlobalKey references and using those to call a method on the child widget if it matches the current selection to have it update, but that's generally a bad idea in flutter - best practices encourage you to pass data downwards in the widget tree rather than call functions downwards. If you have to use GlobalKeys, you can generally refactor to do something better.