Flutter HookWidget AppBarとBottomNavigationBarでBody内部を画面遷移

ども、**Nyampass**の梅原です。今年の９月に入社して一ヶ月半鍛えられて参りました。とにかく身についたことはアウトプットするようにと頑張って記事を書いて参りました。いま見直してみると、恥ずかしい記事ばかりです。なのでこの一ヶ月半で身につけた`Flutter`の一部分を記事にしようと思います。正直、これだけで２周間は悩み通しました。

まず、Flutterで代表的なのが`StatefulWidget`や`StatelessWidget`ですよね。正直これに関しては多くの記事があるのでなんとなくですが、やりたいことは一通り出来ました。しかし、`Hooks`を用いて書き換えてと言われた時に、ReactでもHook慣れしてない私には、何をどうしていいのやら。相当模索しました。が、、、情報の少なさに病みました。　レイアウトや細かい部分は割愛させて頂きましたが。取り敢えず、こちらをどうぞ。

```main.dart
// フックを使うのでこちらをインポート
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'dart:async';

// エイシンクは後々の調整用なのでここは気にせず
Future<void> main() async {
  runApp(ProviderScope(
    child: App(),
  ));
}

// でましたHookWidget。。。MaterialAppは全体のデザインや画面遷移、アプリケーション全体にかかわるプロパティの管理をしてくれるのでここに初期画面を置いたりします。home: に初期画面を配置。
class App extends HookWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: new StartView(),
      routes: <String, WidgetBuilder>{
        '/ViewContainer': (BuildContext context) => new ViewContainer(),
      },
      debugShowCheckedModeBanner: false,
    );
  }
}

// 列挙型にしてこのTabTypeから呼ばれるようにします。
enum TabType {
  FirstView,
  SecondView,
  ThirdView,
  FourthView,
  FifthView,
  SixthView,
  SeventhView,
  EighthView
}

// 最初にくるページを指定。
final tabTypeProvider = StateProvider<TabType>((ref) => TabType.FirstView);

// 主となるページですね。
class ViewContainer extends HookWidget {
  const ViewContainer({Key? key}) : super(key: key);

// _viewsの配列の中にそれぞれのページの要素を入れます。
  @override
  Widget build(BuildContext context) {
    final tabType = useProvider(tabTypeProvider);
    final _views = [
      FirstView(),
      SecondView(),
      ThirdView(),
      FourthView(),
      FifthView(),
      SixthView(),
      SeventhView(),
      EighthView()
    ];

// 問題のAppBar〜BottomNavigationBarまでの実装です。body:の中の画面のみ遷移させます。画像やアイコンはそれぞれ入れてみて下さい。
    return Scaffold(
      appBar: AppBar(
          backgroundColor: Colors.white,
          title: Row(mainAxisAlignment: MainAxisAlignment.start, children: [
            IconButton(
                icon: Image.asset("assets/images/xxxx.png"),
                onPressed: () async {
                  tabType.state = TabType.FirstView;
                }),
            IconButton(
                icon: Image.asset("assets/images/xxxx.png"),
                onPressed: () async {
                  tabType.state = TabType.SecondView;
                }),
            IconButton(
                icon: Image.asset("assets/images/xxxx.png"),
                onPressed: () async {
                  tabType.state = TabType.ThirdView;
                }),
            IconButton(
                icon: Image.asset("assets/images/xxxx.png"),
                onPressed: () async {
                  tabType.state = TabType.FourthView;
                }),
            Text(
              "コメントなどあれば",
              style: new TextStyle(
                fontSize: 12.0,
                color: Colors.black,
              ),
            )
          ])),

// ここの部分は画像が来ます。
      body: _views[tabType.state.index],

// ボトムナビゲーションバーです。アップバーと区別させる為に三項演算子を使いました。
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: tabType.state.index >= 4 ? 0 : tabType.state.index,
        onTap: (int selectIndex) {
          tabType.state = TabType.values[selectIndex];
        },
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.black,
        showUnselectedLabels: true,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            icon: Image.asset('assets/images/xxxx.png',
                width: 20, height: 20),
            title: Text('○'),
            // backgroundColor: Colors.red,
          ),
          BottomNavigationBarItem(
            icon: Image.asset('assets/images/xxxx.png',
                width: 20, height: 20),
            title: Text('△'),
            // backgroundColor: Colors.green,
          ),
          BottomNavigationBarItem(
            icon: Image.asset('assets/images/xxxx.png', width: 20, height: 20),
            title: Text('×'),
            // backgroundColor: Colors.purple,
          ),
          BottomNavigationBarItem(
            icon: Image.asset('assets/images/xxxx.png',
                width: 20, height: 20),
            title: Text('□'),
            // backgroundColor: Colors.pink,
          ),
        ],
      ),
    );
  }
}

// 各画面です。本来なら別ファイルに分けてインポートさせますが、取り敢えずまとめちゃいます。
class FirstView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class SecondView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class ThirdView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class FourthView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class FifthView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class SixthView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class SeventhView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

class EighthView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

// このスタートページはあえてステートフルで書いてみます。試しにHookWidgetに変えてみて下さい。５秒後に自動で画面遷移される仕組みです。
class StartView extends StatefulWidget {
  @override
  _StartView createState() => new _StartView();
}

class _StartView extends State<StartView> {
  startTime() async {
    var _duration = new Duration(seconds: 5);
    return new Timer(_duration, navigationPageView);
  }

  void navigationPageView() {
    Navigator.of(context).pushReplacementNamed('/ViewContainer');
  }

  @override
  void initState() {
    super.initState();
    startTime();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/xxxxxxxx.png")),
    );
  }
}

```
だいぶ端折りました。

ここからは追加の記事です。やはり、実際どういうものができるか想像つかないと思いますので
ファイルを分けて(一部省略)書き直します。


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

Future<void> main() async {
  runApp(ProviderScope(
    child: App(),
  ));
}

class App extends HookWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: new StartView(),
      routes: <String, WidgetBuilder>{
        '/ViewContainer': (BuildContext context) => new ViewContainer(),
      },
      debugShowCheckedModeBanner: false,
    );
  }
}

enum TabType {
  FirstView,
  SecondView,
  ThirdView,
  FourthView,
  FifthView,
  SixthView,
  SeventhView,
  EighthView
}

final tabTypeProvider = StateProvider<TabType>((ref) => TabType.FirstView);

class ViewContainer extends HookWidget {
  const ViewContainer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final tabType = useProvider(tabTypeProvider);
    final _views = [
      FirstView(),
      SecondView(),
      ThirdView(),
      FourthView(),
      FifthView(),
      SixthView(),
      SeventhView(),
      EighthView()
    ];

    return Scaffold(
      appBar: AppBar(
          backgroundColor: Colors.blue,
          title: Row(mainAxisAlignment: MainAxisAlignment.start, children: [
            IconButton(
                icon: const Icon(Icons.coffee_outlined),
                onPressed: () async {
                  tabType.state = TabType.FirstView;
                }),
            IconButton(
                icon: const Icon(Icons.dining_outlined),
                onPressed: () async {
                  tabType.state = TabType.SecondView;
                }),
            IconButton(
                icon: const Icon(Icons.dinner_dining_outlined),
                onPressed: () async {
                  tabType.state = TabType.ThirdView;
                }),
            IconButton(
                icon: const Icon(Icons.favorite_border),
                onPressed: () async {
                  tabType.state = TabType.FourthView;
                }),
            Text(
              "お気に入り画像",
              style: new TextStyle(
                fontSize: 12.0,
                color: Colors.black,
              ),
            )
          ])),

      body: _views[tabType.state.index],

      bottomNavigationBar: BottomNavigationBar(
        currentIndex: tabType.state.index >= 4 ? 0 : tabType.state.index,
        onTap: (int selectIndex) {
          tabType.state = TabType.values[selectIndex];
        },
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.black,
        showUnselectedLabels: true,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.camera_alt_outlined),
            title: Text('撮影'),
            // backgroundColor: Colors.red,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.duo),
            title: Text('動画'),
            // backgroundColor: Colors.green,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.facebook),
            title: Text('投稿'),
            // backgroundColor: Colors.purple,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.border_color_outlined),
            title: Text('メモ'),
            // backgroundColor: Colors.pink,
          ),
        ],
      ),
    );
  }
}
```
ViewContainer内の最初のView(画面)FirstViewのページです。viewsのフォルダを作ってその中で管理します。

```lib/views/First.dart
class FirstView extends HookWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Image.asset("assets/images/sample2.png")),
    );
  }
}

               //〜　省略　〜

```
ウェルカムページに当たる部分です。上記例で、StatefulWidgetで書いたものをHookWidgetに直しました。サンプルのcounterを利用した文献しか見当たらないので私も相当苦労しました。ReactでHooksが慣れていればそうでもないのでしょうが。兎に角、オープニングページらしく一度しか呼ばないので、useEffect使用します。上記Statefulに比べ、相当スッキリ書けてます。

```lib/views/Start.dart
class StartView extends HookWidget {
  void navigationPageView(context) {
    Navigator.of(context).pushReplacementNamed('/ViewContainer');
  }

  Widget build(BuildContext context) {
    useEffect(() {
      final timer = Timer.periodic(Duration(seconds: 5), (timer) {
        navigationPageView(context);
      });
      return timer.cancel;
    }, []);

    return Scaffold(
      body: Center(child: Image.asset("assets/images/sample.JPG")),
    );
  }
}
```
こちらが追加で書かせて頂いた部分を使って出来た画面です。


![Screenshot_20211016_121002_com.nyampass.preview.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1134006/27b320a8-f3ce-0a38-091e-64eb83ba59e1.jpeg)
私は以前、料理人であったこともあって写真や動画レシピ管理のアプリを簡単に作ってみました。現在では、弊社コワーキングスペースにて毎月美味しい料理を提供する交流会を開かせて頂いてます。

https://www.halake.com/