flutter_blocを使ってみる その6 - BLoCでTabViewを作成する

# BLoCでTabViewを作成する方法

FlutterでTabViewを作るときは[DefaultTabController](https://api.flutter.dev/flutter/material/DefaultTabController-class.html)を使うことが多いですが、BLoCを使うこともできます。

[`flutter_bloc`](https://pub.dev/packages/flutter_bloc)を用いる場合、以下のように作ります。

- Tab情報をenum等のStateとしてして持つ。
- Tabが選択された際にEventをBLoCに送る。EventはTab情報（State）をプロパティとして持つ。
- Eventを受け取ったBlocが選択されたTabをStateとして通知する。
- View側がStateを受け取り、選ばれたTabの内容を描画する。


# 実際のコード
`flutter_bloc`を使って以下のようなコードを作成します。

## BLoC
```dart
// Bloc
// BlocState
enum AppTab { a, b }

// BlocEvent
abstract class TabEvent extends Equatable {
  const TabEvent();
}

class UpdateTab extends TabEvent {
  final AppTab tab;

  const UpdateTab(this.tab);

  @override
  List<Object> get props => [tab];

  @override
  String toString() => 'UpdateTab { tab: $tab }';
}

// Bloc
class TabBloc extends Bloc<TabEvent, AppTab> {
  TabBloc() : super(AppTab.a);

  @override
  Stream<AppTab> mapEventToState(TabEvent event) async* {
    if (event is UpdateTab) {
      yield event.tab;
    }
  }
}

```

## View

```dart
void main() {
  runApp(MyApp());
}


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider<TabBloc>(
        create: (context) => TabBloc(),
        child: TabBlocPage(),
      ),
    );
  }
}

class TabBlocPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<TabBloc, AppTab>(
      builder: (context, activeTab) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('TabBlocExample'),
          ),
          // 選ばれたTab(State)によって描画する
          body: activeTab == AppTab.a ? TabA() : TabB(),
          bottomNavigationBar: TabSelector(
            activeTab: activeTab,
            // ユーザーが選んだTabをEventに持たせて送るFunctionをTabSelectorのメソッドに割り当てる
            onTabSelected: (tab) => BlocProvider.of<TabBloc>(context).add(UpdateTab(tab)),
          ),
        );
      },
    );
  }
}

// TabView
class TabA extends StatelessWidget {
  const TabA({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(child: Text('TabA')),
    );
  }
}

class TabB extends StatelessWidget {
  const TabB({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(child: Text('TabB')),
    );
  }
}

// BottomNavigationBar
class TabSelector extends StatelessWidget {
  const TabSelector({
    Key key,
    this.activeTab,
    this.onTabSelected,
  }) : super(key: key);
  final AppTab activeTab;
  final Function(AppTab) onTabSelected;

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.circle),
          label: 'TabA',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.crop_square),
          label: 'TabB',
        ),
      ],
      currentIndex: AppTab.values.indexOf(activeTab),
      selectedItemColor: Colors.amber[800],
      // ユーザーがTabを選択した際にBLoCにEventを送る
      onTap: (index) => onTabSelected(AppTab.values[index]),
    );
  }
}
```