I have page called AddPatientView with BottomNavigationBar containing AddPatientInfo and AddPatientImages pages. All of these three are Stateful widgets.
By default the AddPatientInfo opens up which has bunch of TextFields (to enter patient info), in the AddPatientImages page the user can add Images.
The problem is if I fill the TextFields on AddPatientInfo then go to AddPatientImages and then go back, all the TextFields are empty. Rightfully so since the entire widget tree gets rebuild and I loose all my filled in data.
So I am implementing AutomaticKeepAliveClientMixin
so the state is maintained even if the tab is changed. But it does not seems to work:
Here is my code:
AddPatientView (the parent)
class AddPatientView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AddPatientViewState();
}
}
class _AddPatientViewState extends State<AddPatientView> {
int _currentIndex = 0;
List<Widget> _children;
List<File> _imageFileList = new List<File>();
@override
void initState() {
super.initState();
_children = [
AddPatientInfo(savePatient),
AddPatientImages(_imageFileList)
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("New Patient Record"),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text('Info')),
BottomNavigationBarItem(icon: new Icon(Icons.camera_alt), title: new Text('Images')),
],
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
AddPatientInfo
class AddPatientInfo extends StatefulWidget {
final Function savePatient;
AddPatientInfo(this.savePatient){
}
@override
State<StatefulWidget> createState() {
return _AddPatientInfoState();
}
}
class _AddPatientInfoState extends State<AddPatientInfo> with AutomaticKeepAliveClientMixin<AddPatientInfo> {
Function _savePatient;
String _firstName, _lastName, _gender, _phone, _email, _diabetesMeds, _hypertensionMeds, _others;
int _age, _diabetesYears, _hypertensionYears, _smokesPerDay, _smokerYears;
bool _diabetes = false, _hypertension = false, _smoker = false, _chestPain = false,
_cva = false, _ckd = false, _breathlessness = false,
_syncope = false, _sweating = false, _sweatingFeet = false;
List<String> _genderList = new List<String>();
List<String> _yesNoList = new List<String>();
List<File> _imageFileList = new List<File>();
@override
void initState() {
super.initState();
_savePatient = widget.savePatient;
_genderList.addAll(['Male', 'Female', 'Other']);
_yesNoList.addAll(['Yes', 'No']);
_gender = _genderList.elementAt(0);
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Container(
margin: EdgeInsets.all(10.0),
child: Form(
child: new ListView(
children: <Widget>[
TextField(
decoration: InputDecoration(
labelText: 'Patient First Name',
labelStyle: TextStyle(
color: Colors.black
),
hintText: 'Enter patients first name'
),
onChanged: (String value) {
setState(() {
_firstName = value;
});
},
),
TextField(
decoration: InputDecoration(
labelText: 'Patient Last Name',
labelStyle: TextStyle(
color: Colors.black
),
hintText: 'Enter patients last name'
),
onChanged: (String value) {
setState(() {
_lastName = value;
});
},
),
//other textfield widgets below
],
),
)
),
);
}
@override
bool get wantKeepAlive => true;
}
What am I missing here? Is there a more elegant way to do maintain the data in the form?
If you want AutomaticKeepAliveClientMixin to continue working, please use PageView to wrap the body, the code should like this
class AddPatientView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AddPatientViewState();
}
}
class _AddPatientViewState extends State<AddPatientView> {
int _currentIndex = 0;
List<Widget> _children;
List<File> _imageFileList = new List<File>();
// add
final pageController = PageController();
void onPageChanged(int index) {
setState(() {
_currentIndex = index;
});
}
@override
void initState() {
super.initState();
_children = [AddPatientInfo(savePatient), AddPatientImages(_imageFileList)];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("New Patient Record"),
),
// here!!!
body: PageView(
children: _children,
controller: pageController,
onPageChanged: onPageChanged,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.create), title: new Text('Info')),
BottomNavigationBarItem(
icon: new Icon(Icons.camera_alt), title: new Text('Images')),
],
onTap: (int index) {
// setState(() {
// _currentIndex = index;
// });
// update
pageController.jumpToPage(index);
},
),
);
}
}
but if you just want to keep page state, I suggest you use IndexedStack, it's very simple,no need AutomaticKeepAliveClientMixin, the code should like this
class AddPatientView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AddPatientViewState();
}
}
class _AddPatientViewState extends State<AddPatientView> {
int _currentIndex = 0;
List<Widget> _children;
List<File> _imageFileList = new List<File>();
@override
void initState() {
super.initState();
_children = [
AddPatientInfo(savePatient),
AddPatientImages(_imageFileList)
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("New Patient Record"),
),
body: IndexedStack(
index:_currentIndex,
children:_children
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text('Info')),
BottomNavigationBarItem(icon: new Icon(Icons.camera_alt), title: new Text('Images')),
],
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
From the documentation on AutomaticKeepAliveClientMixin:
/// A mixin with convenience methods for clients of [AutomaticKeepAlive]. Used
/// with [State] subclasses.
///
/// Subclasses must implement [wantKeepAlive], and their [build] methods must
/// call `super.build` (the return value will always return null, and should be
/// ignored).
So in your example, before you return the Scaffold just call super.build:
Widget build(BuildContext context) {
super.build(context);
return Scaffold(...);
}
So I wrapped the tab pages in Stack widget and now it keeps the state of the form intact. I am not sure if this is the right way to go but it does the job.
Here is the code:
AddPatientView (parent)
class AddPatientView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AddPatientViewState();
}
}
class _AddPatientViewState extends State<AddPatientView> {
int _currentIndex = 0;
List<File> _imageFileList = new List<File>();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("New Patient Record"),
),
body: new Stack(
children: <Widget>[
new Offstage(
offstage: _currentIndex != 0,
child: AddPatientInfo(savePatient),
),
new Offstage(
offstage: _currentIndex != 1,
child: AddPatientImages(_imageFileList),
)
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text('Info')),
BottomNavigationBarItem(icon: new Icon(Icons.camera_alt), title: new Text('Images')),
],
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
AutomaticKeepAliveMixin only usually for pageview/tabview/another view that's rebuild, which is preserved a state, that won't rebuild, obviously we need controller
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With