codeinprogress
codeinprogress

Reputation: 3501

Flutter: AutomaticKeepAliveClientMixin is not working with BottomNavigationBar

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?

Upvotes: 50

Views: 43745

Answers (6)

Chor Channaseang
Chor Channaseang

Reputation: 39

class YouTubeWidget extends StatefulWidget {
  const YouTubeWidget({Key? key}) : super(key: key);
  @override
  _YouTubeWidgetState createState() => _YouTubeWidgetState();
}

class _YouTubeWidgetState extends State<YouTubeWidget> with 
AutomaticKeepAliveClientMixin {
  @override
Widget build(BuildContext context) {
    super.build(context);
    Size size = MediaQuery.of(context).size;
    return SingleChildScrollView(
      child: Column(
        children: [
          Notice(),
          Container(
            height: size.height * 0.9,
            child: InAppWebView(
              initialUrlRequest: URLRequest(url: 
Uri.parse("https://www.youtube.com/embed/NZov8TSiLV0")),
            ),
          ),
          SizedBox(
            height: size.height * 0.01,
          ),
          Container(
            height: size.height * 0.9,
            child: InAppWebView(
              initialUrlRequest: URLRequest(url: 
Uri.parse("https://www.youtube.com/embed/QWyAFNjq33s"),),
            ),
          ),
        ],
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

Upvotes: -1

ISHAN KALANSOORIYA
ISHAN KALANSOORIYA

Reputation: 187

put "bool get wantKeepAlive =>true " right after the subclass.

class _AddPatientInfoState extends State<AddPatientInfo> with AutomaticKeepAliveClientMixin<AddPatientInfo> {
 @override
  bool get wantKeepAlive => true;

othercode....
}

Upvotes: 0

Steve Harris
Steve Harris

Reputation: 41

AutomaticKeepAliveMixin only usually for pageview/tabview/another view that's rebuild, which is preserved a state, that won't rebuild, obviously we need controller

Upvotes: 3

Jonah Williams
Jonah Williams

Reputation: 21441

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(...);
  }

Upvotes: 27

hfimy
hfimy

Reputation: 811

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;
          });
        },
      ),
    );
  }
}


Upvotes: 81

codeinprogress
codeinprogress

Reputation: 3501

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;
          });
        },
      ),
    );
  }
}

Upvotes: 6

Related Questions