Related
I am trying to develop a survey form using Flutter and I have multiple dropdown fields in the form. I want to get the selected values from those dropdowns when I click the save button. But all I am getting is the value I initially set inside initState(). The code I am using is as below. Any help to get this sorted out is much appreciated.
class _EditSurveyState extends State<EditSurvey> {
String contactMethod;
String country;
List contactMethodList = ['phone', 'email', 'mail'];
List countryList = ['us', 'uk', 'germany'];
#override
void initState() {
super.initState();
contactMethod = surveryData['contact'];
country = surveryData['country'];
}
#override
Widget build(BuildContext context) {
return Scaffold(
return Scaffold(
children: [
Expanded(
flex: screenWidth(context) < 1300 ? 10 : 8,
child: SafeArea(
child: Column(
children: [
createDropdownField("Contact", contactMethod, contactMethodList),
createDropdownField("Country", country, countryList),
Row(mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () async {
print(contactMethod + country);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)
)
),
child: Text(
"UPDATE",
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
)
),
],
),
]
)
)
)
]
)
);
}
Row createDropdownField(String labelText, String _valueChoose, List valueList) {
return Row (
children: [
SizedBox(height: 25,),
Align(
alignment: Alignment.centerLeft,
child: Text(
'$labelText',
),
),
DropdownButtonFormField(
value: _valueChoose,
hint: Text("$labelText"),
icon: Icon(Icons.arrow_drop_down),
isExpanded: true,
onChanged: (newValue){
setState(() {
_valueChoose = newValue;
});
},
items: valueList.map((valueItem){
return DropdownMenuItem(
value: valueItem,
child: Text(valueItem),
);
}).toList(),
),
],
);
}
}
I don't understand why you using intitstate if you want to initialize value to String you can do it while declaring, try removing initstate and
Declare a variable first where you will store new value from dropdown onchange
i.e
class _EditSurveyState extends State<EditSurvey> {
String _currentValue;
DropdownButtonFormField(
onChanged: (val) =>
setState(() => _currentValue = val as String),
value: _currentValue ,
items: YourList.map((item) {
return DropdownMenuItem(
value: item,
child: Text('$item Items'),
);
}).toList(),
),
I just started learning flutter, I have programming knowledge before, but I am just getting used to the widget structure. What I want to do is add a button to the last element of the DropdownButton list and I used the DropdownMenuItem widget to do that. And I used Text as child and I put Button in the last element. The problem is, I can't give Text to the value property of the DropdownButton. That's why I'm getting an error because I've given a value that isn't in items [ ]. Is there any way I can get the value of the Text widget? Or can I do it another way? I hope I was explanatory.
code:
class _TodoPageState extends State<TodoPage> {
Text dropdownValue = Text('123'); // **FOR DEFAULT VALUE**
#override
Widget build(BuildContext context) {
return SafeArea(
child: Center(
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
MyTabBar(),
Row(
children: [
Expanded(
child: Container(
margin: EdgeInsets.only(left: 3, top: 5),
child: Row(
children: [
Ink(
width: 152,
height: 45,
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 2),
borderRadius: BorderRadius.circular(10),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: dropdownValue, **// CANT SET THE DEFAULT VALUE**
isExpanded: true,
icon: Image.asset('assets/down-list-arrow.png'),
iconSize: 10,
elevation: 16,
onChanged: (newValue) {
setState(() {
dropdownValue = newValue!; **// CANT SET THE DEFAULT VALUE**
});
},
items: [
DropdownMenuItem(child: Text('123'), value: ''),
DropdownMenuItem(child: Text('123'), value: ''),
DropdownMenuItem(
child: TextButton(
child: Text('Create'),
onPressed: () {},
))
],
),
),
)
],
),
),
),
],
),
MyListView()
],
),
));
}
}
I found 2 ways: Assigning create on values or check new value before assigning on onChanged and using FocusNode.
Test Widget
class TodoPage extends StatefulWidget {
TodoPage({Key? key}) : super(key: key);
#override
_TodoPageState createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
late String selectedValue; // **FOR DEFAULT VALUE**
late String selectedValue2;
List<String> dropDownItemValue = ['123', '2', '4', 'Create'];
List<String> dropDownItemValue2 = ['xx', '2', '4'];
late final dropDownKey2;
final FocusNode dropDownFocus = FocusNode();
#override
void initState() {
super.initState();
///selected value must be contain at dropDownItemValue
selectedValue = dropDownItemValue[0];
selectedValue2 = dropDownItemValue2[0];
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.deepPurple,
body: Center(
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
// MyTabBar(),
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: selectedValue, // CANT SET THE DEFAULT VALUE**
isExpanded: true,
// icon: Image.asset('assets/down-list-arrow.png'),
iconSize: 10,
elevation: 16,
onChanged: (newValue) {
print(newValue);
setState(() {
selectedValue = newValue!; // SET THE DEFAULT VALUE**
});
},
/// dont assing same value on multiple widget
items: List.generate(
dropDownItemValue.length,
(index) => DropdownMenuItem(
child: Text('${dropDownItemValue[index]}'),
value: '${dropDownItemValue[index]}'),
),
),
),
SizedBox(
height: 100,
),
DropdownButtonHideUnderline(
child: DropdownButton<String>(
focusNode: dropDownFocus,
value: selectedValue2, // CANT SET THE DEFAULT VALUE**
isExpanded: true,
// icon: Image.asset('assets/down-list-arrow.png'),
iconSize: 10,
elevation: 16,
onChanged: (newValue) {
print(newValue == null);
// if value doesnt contain just close the dropDown
if (newValue == null) {
dropDownFocus.unfocus();
} else
setState(() {
selectedValue2 = newValue; // SET THE DEFAULT VALUE**
});
},
/// dont assing same value on multiple widget
items: List.generate(
dropDownItemValue2.length + 1,
(index) => index < dropDownItemValue2.length
? DropdownMenuItem(
child: Text('${dropDownItemValue2[index]}'),
value: '${dropDownItemValue2[index]}')
: DropdownMenuItem(
child: TextButton(
child: Text('Create'),
onPressed: () {},
),
),
),
),
),
],
),
),
));
}
}
I have a Form in flutter, containing a column with rows with textFormFields.
Each row has a iconButton at the end for deleting said row. The rows is built with a Consumer.
I have a notifier class containing data that the consumer listens for.
When the delete button is pressed a function in the notifier class is called, responsible for deleting the row with the given index. The same function is printing out the dataset befor deletion and again after deletion. The print looks good, the result is as expected.
The Consumer also rebuilds the dataset with one row less. But the data that is presented in the ui is not as expected.
In the Widget that is responsible for building the rows I print out the data that is put in the initialValue of the textFormField. The print looks good, again as expected. But the ui does not render what the print is showing.
See pictures and code.
Ready for deleting the second row:
After deletion:
Code that deletes the row and prints the data.
void deletePoint({int switchpointIndex, int pointIndex, Point point}) {
for (Switchpoint switchpoint in _settings.switchpoints) {
for (Point point in switchpoint.points) {
print("Tag: ${point.tag}. And Type: ${point.type}");
}
}
_settings.switchpoints[switchpointIndex].points.removeAt(pointIndex);
for (Switchpoint switchpoint in _settings.switchpoints) {
for (Point point in switchpoint.points) {
print("Tag: ${point.tag}. And Type: ${point.type}");
}
}
notifyListeners();
}
Result of the print (as expected):
Tag: a. And Type: a
Tag: b. And Type: b
Tag: c. And Type: c
Tag: a. And Type: a
Tag: c. And Type: c
Code that prints and renders the data in the row Widget:
#override
Widget build(BuildContext context) {
print("PointIndex: $pointIndex, value: ${point.tag} and ${point.type}");
return Row(
...
TextFormField(
...
initialValue: point.tag ?? ''
...
TextFormField(
...
initialValue: point.type ?? ''
...
This also prints the expected:
PointIndex: 0, value: a and a
PointIndex: 1, value: c and c
Anyone who understands whats going on?
-- EDIT --
No TextEditingController.
Form code:
return Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Switchpoint Layout',
style: TextStyle(
color: Colors.teal,
fontSize: 20,
fontWeight: FontWeight.bold),
),
TextButton.icon(
onPressed: () {
context
.read<SettingsNotifier>()
.addEmptySwitchpoint();
},
icon: Icon(Icons.add),
label: Text('Add Switchpoint')),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Consumer<SettingsNotifier>(
builder: (context, notifier, child) {
List<SwitchpointRow> switchpointRows = [];
int switchpointIndex = 0;
for (Switchpoint switchpoint
in notifier.switchPoints) {
switchpointRows.add(SwitchpointRow(
switchpoint: switchpoint,
switchpointIndex: switchpointIndex,
formKey: _formKey,
));
switchpointIndex++;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: switchpointRows),
],
);
}),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () async {
print(context
.read<SettingsNotifier>()
.settings
.toJson());
if (_formKey.currentState.validate()) {}
},
child: Text('Submit'),
),
),
],
),
),
);
SwitchpointRow code:
class SwitchpointRow extends StatelessWidget {
const SwitchpointRow({
Key key,
#required this.switchpoint,
#required this.switchpointIndex,
#required this.formKey,
}) : super(key: key);
final int switchpointIndex;
final Switchpoint switchpoint;
final formKey;
List<PointRow> loadPoints(List<Point> points) {
List<PointRow> pointRows = [];
int pointIndex = 0;
for (Point point in points) {
pointRows.add(PointRow(
point: point,
switchpointIndex: switchpointIndex,
pointIndex: pointIndex,
last: points.last == point ? true : false));
pointIndex++;
}
return pointRows;
}
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AppBar(
backgroundColor: Colors.amberAccent,
title: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.fromLTRB(0, 2, 0, 4),
isDense: true,
hintText: 'Switchpoint name',
labelText: 'Switchpoint name',
),
initialValue: switchpoint.name ?? '',
onChanged: (value) {
context
.read<SettingsNotifier>()
.switchPoints[switchpointIndex]
.name = value;
},
validator: (value) {
if (value.isEmpty) {
return 'mandatory';
}
return null;
}),
actions: [
Padding(
padding: const EdgeInsets.only(right: 18.0),
child: TextButton.icon(
onPressed: () {
context.read<SettingsNotifier>().deleteSwitchpoint(
switchpointIndex: switchpointIndex);
},
icon: Icon(Icons.delete),
label: Text('Delete Switchpoint'),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: TextButton.icon(
onPressed: () {
context
.read<SettingsNotifier>()
.addPoint(switchpointIndex);
},
icon: Icon(Icons.add),
label: Text('Point'),
),
),
],
),
Padding(
padding: const EdgeInsets.only(left: 50.0),
child: Column(children: loadPoints(switchpoint.points)),
),
],
),
),
);
}
}
PointRow code:
class PointRow extends StatelessWidget {
const PointRow(
{Key key,
#required this.point,
#required this.switchpointIndex,
#required this.pointIndex,
#required this.last})
: super(key: key);
final Point point;
final int pointIndex;
final int switchpointIndex;
final bool last;
#override
Widget build(BuildContext context) {
print("PointIndex: $pointIndex, value: ${point.tag} and ${point.type}");
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 3,
child: TextFormField(
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 2, 0, 4),
isDense: true,
hintText: 'Tag',
labelText: 'Point tag',
),
onChanged: (value) {
context
.read<SettingsNotifier>()
.switchPoints[switchpointIndex]
.points[pointIndex]
.tag = value;
},
initialValue: point.tag ?? '',
validator: (value) {
if (value.isEmpty) {
return 'mandatory';
}
return null;
}),
),
SizedBox(width: 30.0),
Expanded(
flex: 3,
child: TextFormField(
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 2, 0, 4),
isDense: true,
hintText: 'Type',
labelText: 'Type of point',
),
onChanged: (value) {
context
.read<SettingsNotifier>()
.switchPoints[switchpointIndex]
.points[pointIndex]
.type = value;
},
initialValue: point.type ?? '',
),
),
Expanded(
child: IconButton(
padding: EdgeInsets.all(0.0),
icon: Icon(Icons.delete),
onPressed: () {
context.read<SettingsNotifier>().deletePoint(
switchpointIndex: switchpointIndex,
pointIndex: pointIndex,
point: point);
},
),
)
],
);
}
}
The whole Form picture:
So, after some trial and error the solution had to be a TextEditingController which I wanted to avoid since eventually there will be several fields.
So the solution is a inline controller like this:
TextFormField(
...
controller: TextEditingController()..text = 'the value',
...
);
In this way the UI behaves as I want.
I'm trying to create a dynamic form so I used the idea of using a listview builder to create it. I was able to successfully create it but I faced that I cannot discard changes made to the form by popping it off after editing it. The two textFormField Job name and rate per hour were able to discard changes as they were using onsaved but on the checkbox I can't do that as it has onChanged which wraps setstate to change its state.
You can take a look at the video at this link to see how it functions as of now - https://vimeo.com/523847256
As you can see that it is retaining the data even after popping the page and coming back which I don't want it to. I'm looking for a way to prevent that and make the form the same as before if the user didn't press save.
I have tried to reassign the variables() in onpressed of back button but that didn't work. I also tried push replacement to the same page to reset it but that also didn't work. I think the cuprit here is the sublist and the initialValueTextFormField and initialValueCheckbox which are used declared under ListView.builder but I don't know how to fix that without affecting the dynamic list functionality.
class EditJobPage extends StatefulWidget {
const EditJobPage({Key key, this.job}) : super(key: key);
final Job job;
static Future<void> show(BuildContext context, {Job job}) async {
await Navigator.of(context, rootNavigator: true).pushNamed(
AppRoutes.editJobPage,
arguments: job,
);
}
#override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
List<dynamic> _subList = [];
Set newSet = Set('', false);
#override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job?.name;
_ratePerHour = widget.job?.ratePerHour;
_subList = widget.job?.subList;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(widget.job == null ? 'New Job' : 'Edit Job'),
leading: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
FlatButton(
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
),
onPressed: () => _submit(),
),
],
),
body: _buildContents(),
backgroundColor: Colors.grey[200],
);
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
print(_subList);
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
keyboardAppearance: Brightness.light,
initialValue: _name,
validator: (value) =>
(value ?? '').isNotEmpty ? null : 'Name can\'t be empty',
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate per hour'),
keyboardAppearance: Brightness.light,
initialValue: _ratePerHour != null ? '$_ratePerHour' : null,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
onChanged: (value) {
setState(() {
_ratePerHour = int.tryParse(value ?? '') ?? 0;
});
},
),
Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: _subList?.length ?? 0,
itemBuilder: (context, index) {
String initialValueTextFormField =
_subList[index].subListTitle.toString();
bool initialValueCheckbox = _subList[index].subListStatus;
return Row(
children: [
Checkbox(
value: initialValueCheckbox,
onChanged: (bool newValue) {
setState(
() {
initialValueCheckbox = newValue;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
},
);
},
),
Expanded(
child: TextFormField(
minLines: 1,
maxLines: 1,
initialValue: initialValueTextFormField,
autofocus: false,
textAlign: TextAlign.left,
onChanged: (title) {
setState(() {
initialValueTextFormField = title;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
});
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
filled: true,
hintText: 'Write sub List here',
),
),
),
],
);
},
),
TextButton(
onPressed: () {
setState(() {
_subList.add(newSet);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.add),
Text('Add Sub Lists'),
],
),
),
],
),
];
}
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
} else {
final database = context.read<FirestoreDatabase>(databaseProvider);
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name ?? '',
ratePerHour: _ratePerHour ?? 0,
subList: _subList);
database.setJob(job);
Navigator.of(context).pop();
}
}
}
And this is the link to the full repository of the whole flutter app in case you want to look at any other part:- https://github.com/brightseagit/dynamic_forms . Thank you.
Note - This is the edited code of this repo - https://github.com/bizz84/starter_architecture_flutter_firebase.
When assigning the list we need to use _subList = List.from(widget.job.subList) instead of _subList = widget.job.subList.
Otherwise, the changes made in _subList will also be made in job.subList .
I have a form (second screen) which is used for CRUD. When i add data, it is saved to list view as you can see on the table.
The list view is passed to (first screen) where i can iterate and see the list data with updated content.
However, when i click on go to second screen, the list view data disappears. The given 3 lists are hard coded for testing purpose.
Now, my question is that, How can i keep the data in the table and not disappear, even if i change screen back and forth multiple times. My code is as below: -
**
Main.dart File
**
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _userInformation = 'No information yet';
void languageInformation() async {
final language = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Episode5(),
),
);
updateLanguageInformation(language);
}
void updateLanguageInformation(List<User> userList) {
for (var i = 0; i <= userList.length; i++) {
for (var name in userList) {
print("Name: " + name.name[i] + " Email: " + name.email[i]);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Testing List View Data From second page to first page"),
),
body: Column(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(_userInformation),
],
),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
languageInformation();
},
child: Text("Go to Form"),
),
],
),
);
}
}
2. Model.dart File:
class User {
String name;
String email;
User({this.name, this.email});
}
3. Episode5 File
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class _Episode5State extends State<Episode5> {
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
bool update = false;
int currentIndex = 0;
List<User> userList = [
User(name: "a", email: "a"),
User(name: "d", email: "b"),
User(name: "c", email: "c"),
];
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Email"), tooltip: "To Display Email"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
],
rows: userList
.map(
(user) => DataRow(
cells: [
DataCell(
Text(user.name),
),
DataCell(
Text(user.email),
),
DataCell(
IconButton(
onPressed: () {
currentIndex = userList.indexOf(user);
_updateTextControllers(user); // new function here
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
bodyData(),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: emailController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Email',
hintText: 'Email',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
form.currentState.save();
addUserToList(
nameController.text,
emailController.text,
);
},
),
TextButton(
child: Text("Update"),
onPressed: () {
form.currentState.save();
updateForm();
},
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
child: Text("Save and Exit"),
onPressed: () {
form.currentState.save();
addUserToList(
nameController.text,
emailController.text,
);
Navigator.pop(context, userList);
},
),
],
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
void updateForm() {
setState(() {
User user = User(name: nameController.text, email: emailController.text);
userList[currentIndex] = user;
});
}
void _updateTextControllers(User user) {
setState(() {
nameController.text = user.name;
emailController.text = user.email;
});
}
void addUserToList(name, email) {
setState(() {
userList.add(User(name: name, email: email));
});
}
}
So instead of passing data back and forth between pages, its better to implement a state management solution so that you can access your data from anywhere in the app, without having to manually pass anything.
It can be done with any state management solution, here's how you could do it with GetX.
I took all your variables and methods and put them in a Getx class. Anything in this class will be accessible from anywhere in the app. I got rid of setState because that's no longer how things will be updated.
class FormController extends GetxController {
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
int currentIndex = 0;
List<User> userList = [
User(name: "a", email: "a"),
User(name: "d", email: "b"),
User(name: "c", email: "c"),
];
void updateForm() {
User user = User(name: nameController.text, email: emailController.text);
userList[currentIndex] = user;
update();
}
void updateTextControllers(User user) {
nameController.text = user.name;
emailController.text = user.email;
update();
}
void addUserToList(name, email) {
userList.add(User(name: name, email: email));
update();
}
void updateLanguageInformation() {
// for (var i = 0; i <= userList.length; i++) { // ** don't need nested for loop here **
for (var user in userList) {
print("Name: " + user.name + " Email: " + user.email);
}
// }
}
}
GetX controller can be initialized anywhere before you try to use it, but lets do it main.
void main() {
Get.put(FormController()); // controller init
runApp(MyApp());
}
Here's your page, we find the controller and now all variables and methods come from that controller.
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class _Episode5State extends State<Episode5> {
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
// finding same instance if initialized controller
final controller = Get.find<FormController>();
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Email"), tooltip: "To Display Email"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
],
rows: controller.userList // accessing list from Getx controller
.map(
(user) => DataRow(
cells: [
DataCell(
Text(user.name),
),
DataCell(
Text(user.email),
),
DataCell(
IconButton(
onPressed: () {
controller.currentIndex =
controller.userList.indexOf(user);
controller.updateTextControllers(user);
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
// GetBuilder rebuilds when update() is called
GetBuilder<FormController>(
builder: (controller) => bodyData(),
),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: controller.nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: controller.emailController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Email',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
form.currentState.save();
controller.addUserToList(
controller.nameController.text,
controller.emailController.text,
);
},
),
TextButton(
child: Text("Update"),
onPressed: () {
form.currentState.save();
controller.updateForm();
},
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
child: Text("Save and Exit"),
onPressed: () {
form.currentState.save();
controller.updateLanguageInformation(); // all this function does is print the list
Navigator.pop(
context); // don't need to pass anything here
},
),
],
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
}
And here's your other page. I just threw in a ListView.builder wrapped in a GetBuilder<FormController> for demo purposes. It can now be stateless.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Testing List View Data From second page to first page"),
),
body: Column(
children: <Widget>[
Expanded(
child: GetBuilder<FormController>(
builder: (controller) => ListView.builder(
itemCount: controller.userList.length,
itemBuilder: (context, index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(controller.userList[index].name),
Text(controller.userList[index].email),
],
),
),
),
),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Episode5(),
),
);
},
child: Text("Go to Form"),
),
],
),
);
}
}
As your app expands you can create more controller classes and they're all very easily accessible from anywhere. Its a way easier and cleaner way to do things than manually passing data around everywhere.