【追記あり】BottomNavigationBarでWidgetの状態を維持する方法。（Flutter）

#はじめに
 　FlutterのBottomNavigationBarはタブバーを使ってページの切り替えを支援するWidgetです。実はこのWidget、ページを切り替えるときにいちいち初期化します。スクロールしていた位置などを記憶したいときには手を加える必要があります。PageStoreやAutomaticKeepAliveClientMixinを使う方法などがあるようですが、今回はその中で最も手軽であろうIndexedStackを使って対処する方法を共有します。IndexedStackの詳細は公式ページを参考にするのがおすすめです。
https://api.flutter.dev/flutter/widgets/IndexedStack-class.html

※追記あり
コードその１でWidgetによってはエラーが生じる場合があります。
`inheritFromWidgetOfExactType() or inheritFromElement() was called before initState() completed`　
というような内容です。その場合はinitStateの外で_pageListにWidgetを追加する必要があります。追加するWidgetがStatelessWidgetであれば簡単に解決できます。コードその２がその方法です。

StatefulWidgetでもScoped Modelを使うとStatelessWidgetにすることができます。あとはコードその２と同じように書けば解決できます。書いたものがコードその３となります。


#コードその１
　３つのタブとそれぞれのページでListViewを表示するコードです。

```dart:main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Named Routes Demo',
    initialRoute: '/',
    routes: {
      '/': (context) => MainPage(),
    },
    theme: ThemeData(
        primarySwatch: Colors.deepOrange,
        accentColor: Colors.deepOrangeAccent
    ),
  ));
}

class MainPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() => MainState();
}
class MainState extends State<MainPage>{

  int _selectedIndex=0;
  List<Widget> _pageList=[];
  
  @override
  void initState(){
    super.initState();
    _pageList.add(
      ListView.builder(
        itemBuilder: (context,index){
          return Text('aaa   $index');
        },
      )
    );
    _pageList.add(
      ListView.builder(
        itemBuilder: (context,index){
          return Text('bb   $index');
        },
      ),
    );
    _pageList.add(
      ListView.builder(
        itemBuilder: (context,index){
          return Text('c   $index');
        },
      )
    );
  }

  @override
  Widget build(context){
    return Scaffold(
      body:IndexedStack(
        index: _selectedIndex,
        children:_pageList,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              title: Text('A'),
              icon: Icon(Icons.home)
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Text('B'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            title: Text('C'),
          ),
        ],
        selectedItemColor: Colors.amber[300],
        currentIndex: _selectedIndex,
        onTap: (int index){
          setState(() {
            _selectedIndex=index;
          });
        },
      ),
    );
  }
}

```

#コードその２
initStateでWidgetの追加をしないようにしたものです。StatelessWidgetしか追加できない難点があります。

```dart:main.dart
void main() {
  runApp(MaterialApp(
    title: 'Named Routes Demo',
    initialRoute: '/',
    routes: {
      '/': (context) => MainPage(),
    },
    theme: ThemeData(
        primarySwatch: Colors.deepOrange,
        accentColor: Colors.deepOrangeAccent
    ),
  ));
}

class MainPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() => MainState();
}
class MainState extends State<MainPage>{

  int _selectedIndex=0;
  List<Widget> _pageList=[PageA(),PageB(),PageC()];

  @override
  Widget build(context){
    return Scaffold(
      body:IndexedStack(
        index: _selectedIndex,
        children:_pageList,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              title: Text('A'),
              icon: Icon(Icons.home)
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Text('B'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            title: Text('C'),
          ),
        ],
        selectedItemColor: Colors.amber[300],
        currentIndex: _selectedIndex,
        onTap: (int index){
          setState(() {
            _selectedIndex=index;
          });
        },
      ),
    );
  }
}

class PageA extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('A   $index');
      },
    );
  }
}

class PageB extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('B   $index');
      },
    );
  }
}

class PageC extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('C   $index');
      },
    );
  }
}
```
#コードその３
コードその２をScoped ModelにしてStatefulWidgetにも対応できるようにしたものです。この例では恩恵が分かりにくいかもしれません。StatefulWidgetも追加するときに効果を発揮してくれるはずです。個人的には３つの中で１番おすすめです。

```dart:main.dart
class MainPage extends StatelessWidget{

  final List<Widget> _pageList=[PageA(),PageB(),PageC()];

  @override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<IndexModel>(
      builder: (context,child,model){
        return Scaffold(
          body:IndexedStack(
            index: model.selectedIndex,
            children: _pageList,
          ),
          bottomNavigationBar: BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                  title: Text('A'),
                  icon: Icon(Icons.home)
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.business),
                title: Text('B'),
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.school),
                title: Text('C'),
              ),
            ],
            selectedItemColor: Colors.amber[300],
            currentIndex: model.selectedIndex,
            onTap: (int index){
              model.selectedIndex=index;
            },
          ),
        );
      }
    );
  }
}

class PageA extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('A   $index');
      },
    );
  }
}

class PageB extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('B   $index');
      },
    );
  }
}

class PageC extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context,index){
        return Text('C   $index');
      },
    );
  }
}

class IndexModel extends Model {
  int _selectedIndex = 0;
  get selectedIndex=>_selectedIndex;
  set selectedIndex(value){
    _selectedIndex=value;
    notifyListeners();
  }
}
```

#実行結果

![ezgif-2-bc58f89ff1cd.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532483/16aff33a-dd2f-939f-358c-059306de485c.gif)