Flutter TabBarView + ListViewのステート保存パターン

# はじめに

Tab + ListViewの画面構成時に Widget の状態 (State) を保存する方法をまとめておきます。
<img width="300" alt="Screenshot_1582990818.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/2d90113b-cd3b-40d5-aece-9f1dcb5e16fa.png">

# [方法その1] PageStorageKeyを利用

Widget の`Key`に`PageStorageKey`のインスタンスを指定するだけです。`PageStorageKey`自体についての解説は[こちら](https://qiita.com/kurun_pan/items/0517fb62f1b47c90882c#pagestoragekey)を参照してください。

```Dart:サンプルコード
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          bottom: TabBar(
            tabs: <Widget>[
              Tab(
                child: Text('Tab-1'),
              ),
              Tab(
                child: Text('Tab-2'),
              ),
            ],
          ),
        ),
        body:
        TabBarView(
          children: <Widget>[
            // Stateが保存されないパターン
            //_TabPage(tab: 0),
            //_TabPage(tab: 1),

            // PageStorageKeyを利用してStateを保存するパターン
            _TabPage(key: PageStorageKey(0), tab: 0),
            _TabPage(key: PageStorageKey(1), tab: 1),
          ],
        ),
      ),
    );
  }
}

class _TabPage extends StatefulWidget {
  _TabPage({Key key, this.tab}) : super(key: key);

  final int tab;

  @override
  _TabPageState createState() => _TabPageState();
}

class _TabPageState extends State<_TabPage> {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 100,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          title:
            Text('${widget.tab}: Item $index'),
        );
      },
    );
  }
}
```

# [方法その2] AutomaticKeepAliveClientMixinを利用

`PageStorageKey`を使わず、Stateインスタンスに対して[AutomaticKeepAliveClientMixin](https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html)をMix-inすることでもいけます。

ソースコードは[こちら](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/automatic_keep_alive.dart)

```Dart:編集箇所
class _TabPageState extends State<_TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true; // 追加！

  @override
  Widget build(BuildContext context) {
    super.build(context); // 追加！
```

```Dart:サンプルコード
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          bottom: TabBar(
            tabs: <Widget>[
              Tab(
                child: Text('Tab-1'),
              ),
              Tab(
                child: Text('Tab-2'),
              ),
            ],
          ),
        ),
        body:
        TabBarView(
          children: <Widget>[
            // AutomaticKeepAliveClientMixinを利用してStateを保存するパターン
            _TabPage(tab: 0),
            _TabPage(tab: 1),
          ],
        ),
      ),
    );
  }
}

class _TabPage extends StatefulWidget {
  _TabPage({Key key, this.tab}) : super(key: key);

  final int tab;

  @override
  _TabPageState createState() => _TabPageState();
}

class _TabPageState extends State<_TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true; // 追加！

  @override
  Widget build(BuildContext context) {
    super.build(context); // 追加！

    return ListView.builder(
      itemCount: 100,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          title:
            Text('${widget.tab}: Item $index'),
        );
      },
    );
  }
}
```