in debug I see that the data is saved, but the UI is not updated with the data. strange thing is that if I press CMD S to update the simulator, the data pops out… so the data saves it for me. but i can't understand why i don't update the UI
class _GiornoSettimanaleState extends State<GiornoSettimanale> {
List<AperturaWrapper> aperture = [];
bool _isClosed = false;
Future _selectTimeApertura(BuildContext context, {int i = 0}) async {
final TimeOfDay? _pickedTime =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (_pickedTime != null) {
setState(() {
widget.orari[i].apertura = '${_pickedTime.hour}:${_pickedTime.minute}';
if (i > 0) aperture[i - 1].orario.apertura = widget.orari[i].apertura;
});
}
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
AutoSizeText(
widget.giornoSettimanale!.toUpperCase(),
style: TextStyle(
fontWeight: Fonts.bold,
color: AppColors().black,
),
),
Checkbox(
value: _isClosed,
onChanged: (value) {
if (value != null) {
setState(() {
_isClosed = value;
aperture.forEach((element) => element.isClosed = value);
});
}
},
),
AutoSizeText('SHOPS_CLOSE_ALL'.tr),
],
),
AperturaWidget(
onTapApertura: _isClosed ? null : () => _selectTimeApertura(context),
onTapChiusura: _isClosed ? null : () => _selectTimeChiusura(context),
valoreApertura: widget.orari[0].apertura,
valoreChiusura: widget.orari[0].chiusura,
),
Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView.separated(
itemCount: aperture.length,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, i) => aperture[i],
separatorBuilder: (context, index) {
return const SizedBox(height: 5);
},
),
),
MaterialButton(
onPressed: () {
final Orario nuovoOrario = Orario();
widget.orari.add(nuovoOrario);
final lastIndex = widget.orari.length - 1;
final apertura = AperturaWrapper(
key: UniqueKey(),
onTapApertura: () => _selectTimeApertura(context, i: lastIndex),
onTapChiusura: () => _selectTimeChiusura(context, i: lastIndex),
orario: widget.orari[lastIndex],
);
apertura.onRemove = () => _removeApertura(nuovoOrario, apertura);
setState(() {
aperture.add(apertura);
});
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.add),
SizedBox(
child: AutoSizeText(
'SHOP_ADD'.tr,
),
),
],
),
),
],
);
}
UPDATE the code with the widget build where i call ListView generate
class AperturaWrapper extends StatefulWidget {
AperturaWrapper({
required this.onTapApertura,
required this.onTapChiusura,
required this.orario,
Key? key,
this.onRemove,
this.isClosed = false,
}) : super(key: key);
VoidCallback? onRemove;
VoidCallback onTapApertura;
VoidCallback onTapChiusura;
Orario orario
bool? isClosed;
#override
_AperturaWrapperState createState() => _AperturaWrapperState();
}
class _AperturaWrapperState extends State<AperturaWrapper> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
child: AperturaWidget(
onTapApertura: widget.onTapApertura,
onTapChiusura: widget.onTapChiusura,
valoreApertura: widget.orario.apertura,
valoreChiusura: widget.orario.chiusura,
),
),
IconButton(
onPressed: widget.onRemove,
icon: const Icon(Icons.remove),
),
],
);
}
}
UPDATE WITH AperturaWidget class, class that is used by my list, which does not update the ui
the first portion, that is [0], the ui is always updated, while the rest is not, although in debug I see that the data is saved.the ui is not updated only for the new part of the list that I create, while for [0] it is updated immediately
Related
I have a list of report observations in flutter for a ListView.separated widget
Map _report = {
'observations': <Observation>[
const Observation(observation: ''),
]
};
I added a function to add new observations to the list. That works well. But when I try to remove an observation from the list, instead of removing the observation from the selected index form I always removes the last observation on the list.
This is my full code
class AddReportScreen extends StatefulWidget {
const AddReportScreen({Key? key}) : super(key: key);
static const routeName = '/add-report';
#override
State<AddReportScreen> createState() => _AddReportScreenState();
}
class _AddReportScreenState extends State<AddReportScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Map _report = {
'observations': <Observation>[
const Observation(observation: ''),
]
};
// Add a new observation to the list
void _addNewObservation() {
setState(() {
_report['observations'].add(Observation(observation: ''));
});
}
// REset the form
void _resetForm() {
setState(() {
_formKey.currentState!.reset();
_report = {
'observations': <Observation>[
const Observation(observation: ''),
]
};
});
}
// Remove observation function
void _removeObservation(int index) {
setState(() {
final obs = _report['observations'];
obs.removeAt(index);
_report['observations'] = obs;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Report'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Submit the form
print('Valid');
print([
for (var observation in _report['observations'])
'${observation.observation}'
]);
_resetForm();
}
},
child: const Icon(Icons.add),
),
body: Form(
key: _formKey,
child: Column(
children: [
Flexible(
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (BuildContext context, index) {
return Row(
key: Key(index.toString()),
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
flex: 4,
child: TextFormField(
onChanged: (value) {
_report['observations'][index] =
Observation(observation: value);
},
decoration: InputDecoration(
hintText: "Observation ${index + 1}",
),
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Required field.';
}
return null;
},
),
),
if (_report['observations'].length > 1)
Expanded(
child: ElevatedButton(
onPressed: () {
// Remove the observation
_removeObservation(index);
},
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.red),
shape: MaterialStateProperty.all<OutlinedBorder>(
const CircleBorder()),
),
child: const Icon(Icons.remove),
))
],
);
},
itemCount: _report['observations'].length,
separatorBuilder: (BuildContext context, index) =>
const Divider(),
),
),
const SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
_addNewObservation();
},
style: ButtonStyle(
minimumSize: MaterialStateProperty.all<Size>(
const Size(
double.infinity,
50.0,
),
),
),
child: const Text('Add Observation'),
),
const SizedBox(
height: 10.0,
),
const ElevatedButton(onPressed: null, child: Text("Add Images")),
],
),
),
);
}
}
When I print the list of observations after removal, it shows that the selected observation was removed from the list. But when flutter sets state it removes the last observation from the list.
Your item's key in the itemBuilder should be connected somehow with your observation object and not index
Try changing:
key: Key(index.toString()),
to something like:
key: Key(_report['observations'][index].id),
More info about keys in Flutter: https://www.youtube.com/watch?v=kn0EOS-ZiIc
So I have two screens:
-Book_screen to display all the books(click on any book to go to article_screen)
-article_screen to display articles
In article_screen, I can click on article to save it as favorites.
but when I go back to book_screen then come back to article_screen, those favorited articles doesn't show the favorited status(icon red heart).
this is my article screen code:
class ArticleENPage extends ConsumerStatefulWidget{
final String bookName;
const ArticleENPage({Key? key,#PathParam() required this.bookName,}) : super(key: key);
#override
ArticleENScreen createState()=> ArticleENScreen();
}
class ArticleENScreen extends ConsumerState<ArticleENPage> {
late Future<List<Code>> codes;
#override
void initState() {
super.initState();
codes = fetchCodes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.bookName,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold),),backgroundColor: Colors.white,foregroundColor: Colors.black,elevation: 0,),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
margin: const EdgeInsets.only(top:10),
height: 43,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(
color: Colors.black.withOpacity(0.32),
),
),
child: Consumer(
builder: (context,ref,_) {
return TextField(
onChanged: (value) {
searchStringController controller = ref.read(searchStringProvider.notifier);
controller.setText(value.toLowerCase());
},
decoration: const InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.search,size:18),
hintText: "Search Here",
hintStyle: TextStyle(color: Color.fromRGBO(128,128, 128, 1)),
),
);
}
),
),
),
const SizedBox(height: 10),
Expanded(
child: FutureBuilder(
builder: (context, AsyncSnapshot<List<Code>> snapshot) {
if (snapshot.hasData) {
return Center(
child: Consumer(
builder: (context,ref,child) {
final searchString = ref.watch(searchStringProvider);
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Consumer(
builder: (context,ref,child) {
final favlist = ref.watch(FavoriteListController.favoriteListProvider);
print(favlist);
final alreadySaved = favlist.contains(snapshot.data![index]);
return Card(
child:Padding(
padding: const EdgeInsets.all(10),
child:ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
theme: const ExpandableThemeData(hasIcon: true),
header: RichText(text: TextSpan(children: highlight(snapshot.data![index].name, searchString,'title')),),
collapsed: RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true, maxLines: 3, overflow: TextOverflow.ellipsis,),
expanded: Column(
children: [
RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
semanticLabel: alreadySaved ? 'Remove from saved' : 'Save',
),
onPressed: () {
FavoriteListController controller = ref.read(FavoriteListController.favoriteListProvider.notifier);
if (alreadySaved) {
controller.toggle(snapshot.data![index]);
} else {
controller.toggle(snapshot.data![index]);
}
},
),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () {
setState(() {
Clipboard.setData(ClipboardData(text: snapshot.data![index].name+"\n"+snapshot.data![index].description))
.then((value) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Copied')));
},);
});
},
),],),],)),),)));})
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
);
}
),
);
} else if (snapshot.hasError) {
return const Center(child: Text('Something went wrong :('));
}
return const Align(alignment:Alignment.topCenter,child:CircularProgressIndicator());
},
future: codes,
),
),
],
),
);
}
//read from files
Future<List<Code>> fetchCodes() async {
final response =
await rootBundle.loadString('assets/articles.json');
var CodeJson = json.decode(response)[widget.bookName] as List<dynamic>;
return CodeJson.map((code) => Code.fromJson(code)).toList();
}
}
I tried using riverpod for provider and save to sharedpreference the list of code that I favorited.
final sharedPrefs =
FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
static final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am not sure what is the cause of this but I think it might be because of futurebuilder? I am confused to how to solve this issue...
I am stuck in a dead end so any help or advice would be really appreciated
edit 1-
this is my source code in case I have not include all the necessary codes
https://github.com/sopheareachte/LawCode
edit-2
do I need to change "late Future<List> codes;" that fetch all the codes for futurebuilder to riverpod futureprovider too for it to work?
Maybe the problem is, that you define a static provider inside of your controller class. Try this code:
final sharedPrefs = FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am building a chatbot with text and button responses. I want to hide the button container when one of the buttons is pressed.These buttons are dynamic. I am trying to use a boolean array to save the visibility of the list of the elements. Now, the buttons disappear whenever i press them, but they appear again when the next response is loaded.
class Body extends StatefulWidget {
final List<Map<String, dynamic>> messages;
final mycallback? hi;
final callback? showdate;
Body({
Key? key,
this.messages = const [],
this.hi,
this.showdate,
}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
List<bool> demo = List<bool>.filled(50, true);
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (context, i) {
var obj = widget.messages[widget.messages.length - 1 - i];
Message message = obj['message'];
bool isUserMessage = obj['isUserMessage'] ?? false;
List<dynamic> label = obj['label'];
bool show = obj['show'];
//if (show) show1();
return Column(
crossAxisAlignment:
isUserMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: isUserMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_MessageContainer(
message: message,
isUserMessage: isUserMessage,
),
//if (label.length > 0) _ButtonContainer(label: label),
if (label.length > 0)
if (label[0] != "calendar")
!demo[i]
? SizedBox(
width: 2,
)
: Visibility(
child: Container(
constraints: BoxConstraints(maxWidth: 250),
padding: EdgeInsets.only(top: 10, left: 35),
child: ListView.builder(
itemCount: label.length,
shrinkWrap: true,
itemBuilder: (context, j) {
return ElevatedButton(
child: Text(label[j]),
onPressed: () => {
print(i),
setState(() {
hide(i + 1);//hiding here
print(i);
widget.hi!(label[j], true);//sending message
})
},
style: ButtonStyle(
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(
color: Colors.black,
width: 2.0,
),
),
),
),
);
},
),
),
)
],
);
},
separatorBuilder: (_, i) => Container(height: 10),
itemCount: widget.messages.length,
reverse: true,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 20,
),
);
}
void hide(i) {
print("I am inside hide");
print(demo[i]);
inspect(demo);
setState(() {
demo[i] = false;
if (demo[i]) {
debugPrint("true");
} else {
debugPrint("False");
}
});
inspect(demo);
}
}
When clicking the add button, the same widget is replicated. The widget contains the list of checkboxes that are multi selectable. I am able to replicate the widget but I got problem to handle the checkboxes according to the index of the widget. In image below the checkbox checked state is replicated along with the new add widget.
I have implemented as follows:
Build the widget according to the addbutton click
ListView.builder(
itemCount: counting,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (_, index) {
return _buildLayout(context, index);
});
//counting is number of **blueplus** icon is clicked
Widget _buildLayout(BuildContext context, int i) {
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
addContainer,
style: TextStyle(color: Colors.blueGrey),
),
Container(
width: 64.0,
alignment: Alignment.center,
child: IconButton(
onPressed: () => {i == 0 ? addRow(i) : deleteRow(i)},
icon: Icon(
i == 0
? Icons.add_circle_outline
: Icons.remove_circle_outline,
color: i == 0 ? Theme.of(context).primaryColor : Colors.red,
)),
),
],
),
_buildCheckBoxes()
],
);
}
Widget _buildCheckBoxes() {
return
Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
showHide();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
productionmareketway,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
showHidee
? Icon(Icons.keyboard_arrow_up)
: Icon(Icons.keyboard_arrow_down)
])),
SizedBox(
width: 20,
),
showHidee
? ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: widget.responseMarket.length,
itemBuilder: (ctx, i) {
return _buildSingleCheckBox(
context,
widget.responseMarket[i].name,
widget.responseMarket[i].isChecked,
widget.responseMarket[i].id,
widget.responseMarket[i].identifier,
i);
})
: Container()
])
);
}
Widget _buildSingleCheckBox(BuildContext context, String name, bool isChecked,
int i, String identifier, int j) {
return Container(
child: new CheckboxListTile(
title: new Text(name),
value: isChecked,
activeColor: Theme.of(context).primaryColor,
checkColor: Colors.white,
onChanged: (bool value) {
setState(() {
widget.responseMarket[i].isChecked = value;
print(value);
print(i);
widget._onChecked(
value,
widget.responseMarket[i].id,
widget.responseMarket[i].name,
widget.responseMarket[i].identifier,
counting);
});
},
),
);
}
Add and delete widget function
addRow(int i) {
setState(() {
counting = counting + 1;
});
}
deleteRow(int i) {
setState(() {
counting = counting - 1;
});
}
My callback function
onMarketChecked(var value, int i, String name, String identifier, int j) {
setState(() {
if (responseMarket[i].isChecked == true) {
nonMarketRepated.add(name);
} else {
nonMarketRepated.remove(responseMarket[i].name);
}
});
}
This issue is because you control all replica by counting and widget.responseMarket. If you want all replicas work individually, you need to Replica it actually.
I suggest to create a new StatefulWidget to replace _buildSingleCheckBox() & _buildCheckBoxes() function. I also put showHidee inside it.
class CheckBoxesWidget extends StatefulWidget {
final responseMarket;
CheckBoxesWidget({this.responseMarket, Key key}) : super(key: key);
#override
_CheckBoxesWidgetState createState() => _CheckBoxesWidgetState();
}
class _CheckBoxesWidgetState extends State<CheckBoxesWidget> {
bool showHidee;
#override
void initState() {
showHidee = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
showHide();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'productionmareketway',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
showHidee
? Icon(Icons.keyboard_arrow_up)
: Icon(Icons.keyboard_arrow_down)
],
),
),
SizedBox(
width: 20,
),
if (showHidee)
Column(
children: widget.responseMarket
.map(
(e) => CheckboxListTile(
title: Text(e.name),
value: e.isChecked,
activeColor: Theme.of(context).primaryColor,
checkColor: Colors.white,
onChanged: (bool value) {
setState(() {
e.isChecked = value;
});
},
),
)
.toList(),
),
],
),
);
}
void showHide() {
setState(() {
showHidee = !showHidee;
});
}
}
Second, beyond control the replica by counting, you should use a List to store all replica of responseMarket in the original class.
List<List<Market>> responseMarkets;
#override
void initState() {
responseMarkets = [widget.responseMarket];
super.initState();
}
...
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: responseMarkets.length,
itemBuilder: (_, index) {
return _buildLayout(context, index);
});
}
...
Widget _buildLayout(BuildContext context, int i) {
...
// replace _buildCheckBoxes() with this line
CheckBoxesWidget(responseMarket: responseMarkets[i],),
...
}
Finally, you have to modify the addRow, deleteRow function. Each time create a new ResponseMarkets Object.
addRow(int i) {
setState(() {
responseMarkets.add(responseMarkets[0]
.map((e) => ResponseMarkets(
id: e.id,
name: e.name,
identifier: e.identifier,
isChecked: e.isChecked,
))
.toList());
});
}
deleteRow(int i) {
setState(() {
responseMarkets.removeAt(i);
});
}
I am having a problem with SharedPreferences and multiple Modules I am generating on a Form using ListView.builder
The form is basically asking for some parents details and their childs details - by default the form assumes the parent has one child, but more can be added by clicking a button. The ChildModule has the ability to "close" but when "re-opened" the data doesn't persist, hence using SharedPreferences, it works fine with one Child, but once a second child is added it seems to be creating multiple Instances of SharedPreferences.
I have cut out everything to show what I am trying to achieve. NOTE this is being used as a Web App if it matters.
Oh and ChildModule needs to have its own state because it has a widget which requires it (not shown)
ENQUIRY FORM
final GlobalKey<FormBuilderState> _enquiryFormKey = GlobalKey<FormBuilderState>();
class EnquiryForm extends StatefulWidget {
static List<int> numberOfChildren = [1];
#override
_EnquiryFormState createState() => _EnquiryFormState();
}
class _EnquiryFormState extends State<EnquiryForm> {
int defaultNumberOfChildren = 1;
removeModule(){
setState(() {});
}
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _enquiryFormKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: CustomTextField(
label: 'Parent First Name',
isRequired: true,
),
),
),
//ChildModuleList
ListView.builder(
shrinkWrap: true,
itemCount: EnquiryForm.numberOfChildren.length,
itemBuilder: (context,int index){
return ChildModule(EnquiryForm.numberOfChildren[index], removeModule);
}
),
SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ThemedButton(
onPressed: (){
setState(() {
defaultNumberOfChildren++;
EnquiryForm.numberOfChildren.add(defaultNumberOfChildren);
});
},
child: Text(
'Add Additional Child',
style: TextStyle(color: Colors.white),)
),
SizedBox(width: 10,),
ThemedButton(
onPressed: (){},
child: Text('Enquire Now', style: TextStyle(color: Colors.white),))
],
)
],
));
}
}
CHILD MODULE
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
String firstName;
bool loading = true;
bool isOpen;
#override
void initState() {
print('this module number is ${widget.number}');
_spInstance();
isOpen = true;
super.initState();
}
Future<void> _spInstance() async {
if(childModuleData == null && widget.number == 1) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
} else {
print('broken');
print(childModuleData);
};
String _testValue = childModuleData.getString('Child First Name');
if(_testValue == null){
childModuleData.setString('Child First Name', '');
loading = false;
} else {
childModuleData.clear();
_spInstance();
}
}
#override
Widget build(BuildContext context) {
return loading ? Loading() : Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
if (isOpen == false) {
isOpen = true;
} else {
isOpen = false;
}
});
},
child: Container(
height: 40,
padding: EdgeInsets.only(left: 10, right: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
color: Colors.blue[50],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Child Details',
style: TextStyle(color: Colors.blue[300], fontSize: 16),
),
Row(
children: <Widget>[
isOpen
? Icon(
Icons.arrow_drop_down,
size: 30,
)
: Transform.rotate(
angle: math.pi / 2,
child: Icon(
Icons.arrow_drop_down,
size: 30,
),
),
widget.number > 1
? IconButton(icon: Icon(Icons.clear), onPressed: () async {
await FormFunctions().removeModule(widget.number, EnquiryForm.numberOfChildren);
widget.callback();
})
: Container(),
],
),
],
),
),
),
AnimatedContainer(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
height: isOpen ? null : 0,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
CustomTextField(
label: fieldFirstName,
isRequired: true,
initalValue: childModuleData.getString(fieldFirstName) ?? '',
onChanged: (value){
childModuleData.setString(fieldFirstName, value);
},
),
],
),
],
),
),
],
);
}
}
CONSOLE ERROR AFTER SECOND MODULE IS CREATED
Launching lib/main.dart on Chrome in debug mode...
Syncing files to device Chrome...
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
this module number is 1
got instance
Instance of 'SharedPreferences'
null
this module number is 2 //Number 2 Module is created
broken
null
null
TypeError: Cannot read property 'getString' of null
at child_module._ChildModule.new._spInstance$ (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3704:47)
at _spInstance$.next (<anonymous>)
at runBody (http://localhost:58433/dart_sdk.js:43121:34)
at Object._async [as async] (http://localhost:58433/dart_sdk.js:43149:7)
at child_module._ChildModule.new.[_spInstance] (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3694:20)
at child_module._ChildModule.new.initState (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3689:24)
at framework.StatefulElement.new.[_firstBuild] (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:41219:58)
at framework.StatefulElement.new.mount (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:12605:24)
at framework.SingleChildRenderObjectElement.new.inflateWidget (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:11420:16)
in your case the concern is at the level of recording data in shared preferences. one solution would be to add the widget number in the key to save like this (await SharedPreferences.getInstance()).setString("Your Key${widgetNumber}");
and edit your function _spInstance
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
...
Future<void> _spInstance() async {
if(childModuleData == null) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
}
String _testValue = childModuleData.getString('Child First Name${widget.number}');
//NB: do not clear data on chlidModuleData any more.
...
}
...
}