TextEditingController makes widget lose its previous state

Viewed 2879

When I use TextEditingController in CupertinoTextField, and change to another widget(page) and return, the previous state in that page is lost.

When I uncomment //controller: textController, everything works fine.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'test',
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
            body: TabBarView(
              children: [new Search(), new Setting(),
              ],
            ),
            bottomNavigationBar: Container(
              height: 60,
              child: new TabBar(
                tabs: [
                  Tab(icon: new Icon(Icons.search)),
                  Tab(icon: new Icon(Icons.settings)),
                ],
                labelColor: Colors.blue,
                unselectedLabelColor: Colors.grey,
              ),
            )
        ),
      ),
    );
  }
}

class Setting extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return IconButton(
        icon: Icon(Icons.check),
        onPressed: () {
          Navigator.push(context, CupertinoPageRoute(
              builder: (context) =>
              new Scaffold(
                appBar: AppBar(title: Text('3'),),
              )));
        });
  }

}

class Search extends StatefulWidget {
  @override
  createState() => new SearchState();
}

class SearchState extends State<Search> {

  String currentWord = '';
  final TextEditingController textController = new TextEditingController();

  @override
  void dispose() {
    textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Row(
            children: <Widget>[
              new Expanded(
                child: new CupertinoTextField(
                  style: TextStyle(color: Colors.white),
                  cursorColor: Colors.white,
                  //controller: textController,
                  maxLines: 1,
                  clearButtonMode: OverlayVisibilityMode.editing,
                  onChanged: (text) {
                    setState(() {
                      currentWord = text;
                    });
                  },
                ),
              ),
            ],
          ),
        ),
        body: ListView.builder(
            itemCount: 5,
            itemBuilder: (context, i) {
              return Text(currentWord);
            })
    );
  }

}

The expected result(without controller set):get back and the state keeps the same.

expected

Actual results(with controller set): get back and the state lost

actual

1 Answers

The explanation for the observed behavior is the following:

CupertinoTextField uses an internal TextEditingController for which the framework automatically sets an AutomaticKeepAlive. This keepAlive is responsible for keeping the state.

If you use your own controller, you are in charge of attaching the AutomaticKeepAlive because the framework doesn't do it for you.

The following snippet adds the keepAlive to your code:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'test',
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
            body: TabBarView(
              children: [
                new Search(),
                new Setting(),
              ],
            ),
            bottomNavigationBar: Container(
              height: 60,
              child: new TabBar(
                tabs: [
                  Tab(icon: new Icon(Icons.search)),
                  Tab(icon: new Icon(Icons.settings)),
                ],
                labelColor: Colors.blue,
                unselectedLabelColor: Colors.grey,
              ),
            )),
      ),
    );
  }
}

class Setting extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return IconButton(
        icon: Icon(Icons.check),
        onPressed: () {
          Navigator.push(
              context,
              CupertinoPageRoute(
                  builder: (context) => new Scaffold(
                        appBar: AppBar(
                          title: Text('3'),
                        ),
                      )));
        });
  }
}

class Search extends StatefulWidget {
  @override
  createState() => new SearchState();
}

class SearchState extends State<Search> with AutomaticKeepAliveClientMixin {
  String currentWord = '';
  final TextEditingController textController = new TextEditingController();

  @override
  void initState() {
    super.initState();
    textController?.addListener(updateKeepAlive);
  }

  @override
  void dispose() {
    textController?.removeListener(updateKeepAlive);
    textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context); // See AutomaticKeepAliveClientMixin.
    return new Scaffold(
        appBar: new AppBar(
          title: new Row(
            children: <Widget>[
              new Expanded(
                child: new CupertinoTextField(
                  style: TextStyle(color: Colors.white),
                  cursorColor: Colors.white,
                  controller: textController,
                  maxLines: 1,
                  clearButtonMode: OverlayVisibilityMode.editing,
                  onChanged: (text) {
                    setState(() {
                      currentWord = text;
                    });
                  },
                ),
              ),
            ],
          ),
        ),
        body: ListView.builder(
            itemCount: 5,
            itemBuilder: (context, i) {
              return Text(currentWord);
            }));
  }

  @override
  bool get wantKeepAlive => textController?.text?.isNotEmpty == true;
}
Related