I have a app that I am working for in flutter that right now has 2 pages a form page for people to enter data and a summary page that allows them to look over that data the entered one more before they submit the information to a sql database. I am storing data in local storage and then having the summary page load that data in from local storage. my issue is the following:
it is only loading information from the 1st data column. it should be loading to each data column.
here is the code
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<MyCustomForm> forms = [];
String? _selectedSchool;
final List<String> _schools = [
'Gentry Middle School',
'Oakland Middle School',
'Jefferson Middle School',
'West Middle School',
];
@override
void initState() {
super.initState();
forms.add(MyCustomForm(onCpsNumberTyped: addNewForm));
}
void addNewForm() {
setState(() {
forms.add(MyCustomForm(onCpsNumberTyped: addNewForm));
});
}
void reviewData(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
List<String> formDataList = [];
forms.forEach((form) {
formDataList.add('${_selectedSchool ?? 'No School Selected'}|${form.cpsNumber}|${form.itemDescription}|${form.selectedReason}');
});
await prefs.setStringList('formData', formDataList);
Navigator.push(context, MaterialPageRoute(builder: (context) => SummaryPage()));
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Surplus Asset Disposal Form'),
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: DropdownButtonFormField<String>(
value: _selectedSchool,
decoration: InputDecoration(
labelText: 'Select School',
border: OutlineInputBorder(),
),
onChanged: (String? newValue) {
setState(() {
_selectedSchool = newValue;
});
},
items: _schools.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
Expanded(
child: ListView.builder(
itemCount: forms.length,
itemBuilder: (context, index) => forms[index],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () => reviewData(context),
child: Text('REVIEW'),
),
),
],
),
),
);
}
}
class MyCustomForm extends StatefulWidget {
final VoidCallback onCpsNumberTyped;
MyCustomForm({required this.onCpsNumberTyped});
@override
_MyCustomFormState createState() => _MyCustomFormState();
String get cpsNumber => '';
String get itemDescription => '';
String get selectedReason => '';
}
class _MyCustomFormState extends State<MyCustomForm> {
final TextEditingController _cpsNumberController = TextEditingController();
final TextEditingController _itemDescriptionController = TextEditingController();
String? selectedReason;
bool isFirstTime = true;
final List<String> _reasonsForSurplus = [
'Obsolete Equipment',
'Replacement',
'Damage',
'Others',
];
String get cpsNumber => _cpsNumberController.text;
String get itemDescription => _itemDescriptionController.text;
// ignore: recursive_getters
@override
void initState() {
super.initState();
_cpsNumberController.addListener(() {
if (isFirstTime && _cpsNumberController.text.isNotEmpty) {
widget.onCpsNumberTyped();
isFirstTime = false;
}
});
}
@override
void dispose() {
_cpsNumberController.dispose();
_itemDescriptionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
controller: _cpsNumberController,
decoration: InputDecoration(
labelText: 'CPS Number',
border: OutlineInputBorder(),
),
),
SizedBox(height: 24.0),
TextFormField(
controller: _itemDescriptionController,
decoration: InputDecoration(
labelText: 'Item Description',
border: OutlineInputBorder(),
),
maxLines: null,
),
SizedBox(height: 24.0),
DropdownButtonFormField<String>(
value: selectedReason,
decoration: InputDecoration(
labelText: 'Reason for Surplus',
border: OutlineInputBorder(),
),
onChanged: (String? newValue) {
setState(() {
selectedReason = newValue;
});
},
items: _reasonsForSurplus.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
),
),
);
}
}
class SummaryPage extends StatefulWidget {
@override
_SummaryPageState createState() => _SummaryPageState();
}
class _SummaryPageState extends State<SummaryPage> {
Future<List<DataRow>> _loadData() async {
final prefs = await SharedPreferences.getInstance();
final List<String>? formDataList = prefs.getStringList('formData');
List<DataRow> rows = (formDataList ?? []).map((dataString) {
List<String> dataParts = dataString.split('|');
if (dataParts.length >= 4) {
return DataRow(
cells: [
DataCell(Text(dataParts[0])),
DataCell(Text(dataParts[1])),
DataCell(Text(dataParts[2])),
DataCell(Text(dataParts[3])),
],
);
} else {
return null;
}
}).where((row) => row != null).map<DataRow>((row) => row!).toList();
return rows;
}
// Make sure this method is inside the _SummaryPageState class
Future<void> clearFormData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('formData');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Summary'),
),
body: FutureBuilder<List<DataRow>>(
future: _loadData(),
builder: (BuildContext context, AsyncSnapshot<List<DataRow>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(child: Text('Error loading data'));
}
return ListView(
children: <Widget>[
DataTable(
columns: const [
DataColumn(label: Text('School')),
DataColumn(label: Text('CPS Number')),
DataColumn(label: Text('Item Description')),
DataColumn(label: Text('Reason For Surplus')),
],
rows: snapshot.data ?? [],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
child: ElevatedButton(
onPressed: () async {
await clearFormData();
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: Text('Submit & Start Over'),
),
),
],
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}
Future<List<DataRow>> _loadData() async {
final prefs = await SharedPreferences.getInstance();
final List<String>? formDataList = prefs.getStringList('formData');
List<DataRow> rows = (formDataList ?? []).map((dataString) {
List<String> dataParts = dataString.split('|');
if (dataParts.length >= 4 && dataParts[1].isNotEmpty) {
return DataRow(
cells: [
DataCell(Text(dataParts[0])),
DataCell(Text(dataParts[1])),
DataCell(Text(dataParts[2])),
DataCell(Text(dataParts[3])),
],
);
} else {
return null;
}
}).where((row) => row != null).map<DataRow>((row) => row!).toList();
return rows;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Summary'),
),
body: FutureBuilder<List<DataRow>>(
future: _loadData(),
builder: (BuildContext context, AsyncSnapshot<List<DataRow>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(child: Text('Error loading data'));
}
return ListView(
children: <Widget>[
DataTable(
columns: const [
DataColumn(label: Text('School')),
DataColumn(label: Text('CPS Number')),
DataColumn(label: Text('Item Description')),
DataColumn(label: Text('Reason For Surplus')),
],
rows: snapshot.data ?? [],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
child: ElevatedButton(
onPressed: () async {
await clearFormData();
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: Text('Submit'),
),
),
],
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
clearFormData() {
}
any help would be amazing! still new to flutter so I am sorry if this is a dumb question to ask…thank you in advance for your time and help 🙂
2
Answers
I see the beginning of your problem because a StatefulWidget attribute cannot change its value:
if you want to be able to record dynamically send a function instead
if this is too vague please tell me for further explanation.
The reason for your code was provided by Mr.Munsch. Your values were always empty strings. I’ve changed your code a bit, so instead of keeping a list of Widgets (forms), now I keep a list of
FormData
(I’ve created a class to keep the data). Based on that list, I create a list of Widgets. With that change, you always have access to your data, which is updated after every change in the form. It is not the best solution overall, but requires the smallest amount of changes in your code.I’ve also changed how
onCpsNumberTyped
is called, by removingisFirstTime
, and replacing it with a listener which removes itself. It was not required as your solution was also fine.I don’t know your requirements, but I think you can skip the use of SharedPreferences, and then your code is more simple.
To do that, replace reviewData with:
Now your SummaryPage looks like this:
Hope this helps