Flutterでproviderを利用したボトムナビゲーションの制御

[前回](https://qiita.com/amita/items/deb6a27c5510bb6b2b7a)はスプラッシュ表示→メインスクリーンでHelloWorldを表示するところまで進めました。

今回はアプリであるあるのボトムナビゲーションの実装をしていきます。

こんなやつです。:point_down_tone3:
![bottom_navigation_bar](https://flutter.github.io/assets-for-api-docs/assets/material/bottom_navigation_bar.png)


BottomNavigationBarクラスに関しては下記公式ドキュメントを読んでみてください。
https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html

今回はPrivider（状態管理パッケージ）を利用してボトムナビゲーションのindexを制御してWidgetツリー配下全てからbottomnavigationのindexを監視できるようにしてみます。

>package:provider とは
package:providerは、コミュニティによって開発されている DI (Dependency Injection) 及び 状態管理用のパッケージです。 実は Google 側でも似たようなパッケージ(provide)を開発していたのですが、provider の方がよいのでは？となり、今はproviderを推奨しているらしいです。

Flutterの状態管理に関しては下記が参考になりました。
https://ja.unflf.com/tech/flutter/provider/
私が採用した理由としてBlockパターンよりもProviderを利用したほうが学習コストも低いですし扱いやすいと感じた為です。



ではやっていきます。
## Providerパッケージをget
まずパッケージを入れたいのでpubspec.yamlにprovider追加。
https://pub.dev/packages/provider

```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  provider: ^4.0.2　//追加

dev_dependencies:
  flutter_test:
    sdk: flutter
```

## 状態クラスであるproviderファイルを作成する。

lib配下にproviderディレクトリを作成し、そこにbottom_navigation_bar_provider.dartを作成します。

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

// ミックスイン：クラスのコードを複数のクラス階層で再利用する方法です。
// ミックスインするにはwithキーワードに続けて1つ以上のつ以上のクラスを指定します。
// ChangeNotifierをmixinするだけで、ViewModelとして機能します。
// ロジック実行後にnotifyListeners()を呼ぶことで、View側に変更を通知できます。

class BottomNavigationBarProvider with ChangeNotifier {
  int _currentIndex = 0;
  get currentIndex => _currentIndex;
  set currentIndex(int index) {
    _currentIndex = index;
    // View側に変更を通知
    notifyListeners();
  }
}
```

## main.dartで状態クラスをアクセスできるようにする。
MultiProviderメソッドで複数利用出来るようにしているのはどうせ他にもproviderをセットするからです。

```main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:my_app/splash.dart';
import 'package:my_app/provider/bottom_navigation_bar_provider.dart';

void main() async {
  // main()の中で非同期処理を行う際には、下記を実行しなければいけないらしい
  WidgetsFlutterBinding.ensureInitialized();

  // iOS,androidともに縦向き固定
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
      .then((_) {
    runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider(
              create: (context) => BottomNavigationBarProvider()),
        ],
        child: MyApp(),
      ),
    );
  });
}
```

## AppScreenを修正する。

Provider.of(context) は動的な値の変化を自動的に監視して、 Widget ツリーをリビルドしてくれます。 リビルドは、Provider.of(context) に渡した BuildContext のスコープで行ってくれるので、最低限しか行われません。

``` app_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_app/provider/bottom_navigation_bar_provider.dart';
import 'package:my_app/screen/h_screen.dart';
import 'package:my_app/screen/o_screen.dart';
import 'package:my_app/screen/g_screen.dart';
import 'package:my_app/screen/e_screen.dart';

class AppScreen extends StatefulWidget {
  static const String routeName = '/app';

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

class _AppScreenState extends State<AppScreen> {
  var currentTab = [
    HScreen(title: '１番目'),
    OScreen(title: '2番目'),
    GScreen(title: '3番目'),
    EScreen(title: '4番目'),
  ];
  @override
  Widget build(BuildContext context) {
    final bottomNavigationBar =
        Provider.of<BottomNavigationBarProvider>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text("provider"),
      ),
      body: currentTab[bottomNavigationBar.currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.white,
        currentIndex: bottomNavigationBar.currentIndex,
        onTap: (index) {
          bottomNavigationBar.currentIndex = index;
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        items: [
          BottomNavigationBarItem(
            icon: Tooltip(
              message: 'ボトム1',
              child: Image.asset(
                'assets/images/logo.png',
              ),
            ),
            activeIcon: Image.asset(
              'assets/images/logo.png',
            ),
            title: const Text(
              'ボトム1',
              style: TextStyle(
                  fontSize: 12,
                  color: Colors.black,
                  fontWeight: FontWeight.bold),
            ),
          ),
          BottomNavigationBarItem(
            icon: Tooltip(
              message: 'ボトム2',
              child: Image.asset(
                'assets/images/logo.png',
              ),
            ),
            activeIcon: Image.asset(
              'assets/images/logo.png',
            ),
            title: const Text(
              'ボトム2',
              style: TextStyle(
                  fontSize: 12,
                  color: Colors.black,
                  fontWeight: FontWeight.bold),
            ),
          ),
          BottomNavigationBarItem(
            icon: Tooltip(
              message: 'ボトム3',
              child: Image.asset(
                'assets/images/logo.png',
              ),
            ),
            activeIcon: Image.asset(
              'assets/images/logo.png',
            ),
            title: const Text(
              'ボトム3',
              style: TextStyle(
                  fontSize: 12,
                  color: Colors.black,
                  fontWeight: FontWeight.bold),
            ),
          ),
          BottomNavigationBarItem(
            icon: Tooltip(
              message: 'ボトム4',
              child: Image.asset(
                'assets/images/logo.png',
              ),
            ),
            activeIcon: Image.asset(
              'assets/images/logo.png',
            ),
            title: const Text(
              'ボトム4',
              style: TextStyle(
                  fontSize: 12,
                  color: Colors.black,
                  fontWeight: FontWeight.bold),
            ),
          ),
        ],
      ),
    );
  }
}
```

計4つのscreenを作成し紐付けます。

- lib/screen/h_screen.dart
- lib/screen/o_screen.dart
- lib/screen/g_screen.dart
- lib/screen/e_screen.dart


``` h_screen.dart,o_screen.dart,g_screen.dart,e_screen.dart
import 'package:flutter/material.dart';

class HScreen extends StatefulWidget {
  HScreen({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _HScreenState createState() => _HScreenState();
}

class _HScreenState extends State<HScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          centerTitle: true,
          title: Text(widget.title),
        ),
        body: SafeArea(
          child: SingleChildScrollView(
              child: Container(
            child: const Text('index１のページです'),
          )),
        ));
  }
}
```

できました！！


![provider.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478758/9d711bc1-25f6-1496-ea61-336d9a71fc10.png)

画像が全部ハゼなのは気にしないでください。。