flutter_blocでBlocパターンを実装する Cubit編

# Cubit
`Cubit` とは、Flutterで状態管理を簡素化するためのシンプルなクラスです。
`Cubit` を使用すると、イベントを定義する必要がなく、
直接メソッドを呼び出して状態を変更することができるようになります。

通常、Blocパターンを採用する際は `Bloc` を使用する必要がありますが、
`Cubit` は `Bloc` と異なり、イベントの概念がなく、状態を直接管理するので、
よりシンプルな実装になります。

:::note info
**BlocパターンのBlocとは？**
Blocパターンでは `States` と `Events` を使って状態を管理します。
その状態とイベントを管理する際に、使用されるのが `Bloc` です。
`Bloc` は、 `Events` を受け取り、結果を `States` に反映する役割を担っています。
:::

一方で、 `Cubit` は `Bloc` の軽量版で、 `Events` を受け取らず、
ビジネスロジックを含む関数を直接呼び出して `States` を更新します。

つまり、 `Cubit` は `Events` なしで `Bloc` の機能を提供しているということになります。

## インストール
`Cubit` を使用する際には `flutter_bloc` パッケージをインストールする必要があります。
以下のリンクからパッケージをインストールしましょう。

https://pub.dev/packages/flutter_bloc

```yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0
```

### flutter_blocパッケージとは
`flutter_bloc` パッケージを使うと、FlutterアプリケーションにBlocパターンを
簡単に導入することができるようになります。

:::note info
**Blocパターンとは?**
Blocパターンは、ビジネスロジックとUIを分離させ、
アプリケーションの状態管理を効率的に行うためのデザインパターンです。
:::


# Cubitの使い方

## 1. Cubitクラスを作成する

`Cubit` を使用するには、 `Cubit` クラスを継承して、
状態管理ロジックを定義する必要があります。
```dart: Cubitクラスの定義
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}
```

- `CounterCubit` クラスは、初期状態を `0` に設定するコンストラクタを持っています。
- `increment` メソッドでは、現在の状態に `1` を足して新しい状態を生成しています。
- `decrement` メソッドでは、現在の状態から `1` を引いて新しい状態を生成してます。

`emit` は、 `Cubit` を使って状態管理を行う際に、状態の変更を行うためのメソッドです。
新しい `Cubit` の状態を更新するために使われます。
`Cubit` クラスのインスタンスの状態が変わるたびに、このメソッドが呼び出され、
`Cubit` をリッスンしているウィジェットや他のオブジェクトに新しい状態を通知します。

## 2. Providerを使用してCubitを公開する

`Cubit` クラスを作成した後は `BlocProvider` を使用して、
作成した `Cubit` をWidgetツリー内に反映できるようにします。

:::note info
**BlocProvider**
`BlocProvider` は、 `flutter_bloc` パッケージの一部で、
`BLoC` や `Cubit` をウィジェットツリーに提供するために使用されます。

特定の `BLoC` や `Cubit` をウィジェットツリー内でアクセス可能なるため、
子ウィジェットから状態管理ロジックを共有できるようになります。
:::


```dart
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterCubit(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Counter'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              BlocConsumer<CounterCubit, int>(
                listener: (context, state) {
                  // 処理を入れたい場合はここに追加
                },
                builder: (context, count) {
                  return Text(
                    'Count: $count',
                    style: const TextStyle(fontSize: 24),
                  );
                },
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: () => context.read<CounterCubit>().increment(),
                    child: const Text('Increment'),
                  ),
                  const SizedBox(width: 16),
                  ElevatedButton(
                    onPressed: () => context.read<CounterCubit>().decrement(),
                    child: const Text('Decrement'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

上記のように、 `BlocProvider` を使用して `Cubit` インスタンスを
ウィジェットツリー全体に提供することができます。

### BlocProviderの引数
- **create 引数:** Bloc インスタンスを生成する関数です。
- **child 引数:** Bloc インスタンスを利用するウィジェットです。

### BlocProviderの特徴
#### グローバルアクセス:
`BlocProvider` を使用することで、 `BLoC` や `Cubit` をウィジェットツリー内で共有し、
グローバルにアクセスできます。これにより、特定の状態管理ロジックを
再利用可能にします。

#### 依存関係の注入:
`BlocProvider` は依存関係の注入を容易にし、異なるウィジェット間で
依存関係の管理を簡素化します。

#### 状態管理の分離:
`BLoC` や `Cubit` を使用することで、ビジネスロジックとUIロジックを明確に分離できます。
これにより、コードの可読性と保守性が向上します。


## 3. WidgetからCubitにアクセスする

Widgetから `Cubit` にアクセスするには、 `BlocConsumer` ウィジェットを使用します。
```dart
BlocConsumer<CounterCubit, int>(
    listener: (context, state) {
        // 処理を入れたい場合はここに追加
    },
    builder: (context, count) {
      return Text(
        'Count: $count',
        style: const TextStyle(fontSize: 24),
      );
    },
),
```
`BlocConsumer` は、 `Cubit` または `Bloc` の状態を監視し、
状態が変更された際にUIを更新する目的で使用されます。

:::note warn
`BlocConsumer` は状態が変更されるたびに `builder` 関数を呼び出すため、
頻繁な状態変更があると非効率的になる可能性があります。

`builder` 関数内での重い処理は避け、 `listener` 関数内で
行うようにしましょう。状態変更時の副作用のない処理のみが必要な場合は、
`BlocBuilder` を使用した方が効率的です。
:::

### BlocConsumerの特徴
- **状態に応じたUIのビルディング:**
builderプロパティを使って、状態に基づいてUIをビルドします。状態が変更されるたびに、このビルダー関数が呼び出されてUIを再描画します。

- **状態変化に対する処理の実行:**
listenerプロパティを使って、状態が変更されたときに実行したい処理を定義します。例えば、状態に基づいてダイアログを表示したり、ナビゲーションを行ったりする場合に使用します。

### BlocConsumerの引数

- **builder:** `Cubit` の状態が変更された際にUIを再構築するための関数。
状態の変化に応じて、この `builder` 関数が呼び出されます。

- **listener:** `Cubit` で定義した状態が変更された際に実行される関数。
副作用のある処理(非同期処理、ナビゲーションなど)をここに記述します。

- **bloc:** 使用する `BLoC` または `Cubit` のインスタンスを指定する。
省略可能で、省略した場合は `context` から自動的に取得されます。

- **buildWhen:** `builder` 関数が呼び出されるかどうかを決定する条件を
定義するための関数。

- **listenWhen:** `listener` 関数が呼び出されるかどうかを決定する条件を
定義するための関数。

### BlocBuilder
状態の変化に応じて、動的にUIを更新させる方法として、 
`BlocConsumer` ウィジェットの他に `BlocBuilder` があります。

`BlocConsumer` と同様に、 `Bloc` または `Cubit` の状態に応じて
 UI をビルドするためのウィジェットです。
 
`BlocBuilder` を使用することで、状態の変化に応じて動的に UI を
更新することができます。

### BlocBuilderの引数
- **bloc:** 使用する `BLoC` または `Cubit` のインスタンスを指定する。
省略可能で、省略した場合は context から自動的に取得されます。
- **builder:** 現在の状態をもとに UI をビルドするための関数。
- **buildWhen:** `builder` 関数が呼び出されるかどうかを決定する条件を
定義するための関数。

### BlocBuilder と BlocConsumer の使い分け
`BlocBuilder` と `BlocConsumer` はどちらも状態に基づいて UI を
ビルドするために使用されますが、以下の点で異なります
- **BlocBuilder:**
  - 状態に応じて UI をビルドするために使用します。
  - 状態が変化するたびに UI を再描画しますが、副作用を持つことはできません。
  - 状態の変化に応じた追加の処理（ダイアログの表示やナビゲーションの実行など）が
必要ない場合に使用します。

- **BlocConsumer:**
  - 状態に応じた UI のビルドと副作用を組み合わせて実行するために使用します。
  - `builder` と `listener` の両方のプロパティを持ち、状態の変化に応じて UI を
再描画するだけでなく、状態の変化に対して特定の処理を実行することができます。
  - 状態の変化に応じて副作用が必要な場合に使用します。

# Cubitでカウンタアプリを作成する
ここでは、カウンターアプリを例にして、 `Cubit` の使い方を説明します。

通常、 `Cubit` では、状態はシンプルな型（`int` や `String` など）を使用しますが、
複雑な状態を管理する場合はカスタムの状態クラスを定義することもできます。

```dart: 
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';

// 状態クラスの定義
class CounterState extends Equatable {
  final int count;

  const CounterState(this.count);

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

// Cubitクラスの定義（カスタム状態クラスを使用）
class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(const CounterState(0));

  void increment() => emit(CounterState(state.count + 1));

  void decrement() => emit(CounterState(state.count - 1));
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => CounterCubit(),
        child: const CounterPage(),
      ),
    );
  }
}

// CounterPageクラス
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    final counterCubit = context.read<CounterCubit>();

    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: BlocBuilder<CounterCubit, CounterState>(
          builder: (context, state) {
            return Text('${state.count}', style: const TextStyle(fontSize: 24));
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: counterCubit.increment,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 8),
          FloatingActionButton(
            onPressed: counterCubit.decrement,
            tooltip: 'Decrement',
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}
```

### 1. 状態クラス (CounterState)
`CounterState` クラスは、アプリの状態を保持するクラスです。
`Equatable` を継承しており、 `props` メソッドで比較対象のプロパティを
定義しています。 `count` プロパティは、現在のカウンタ値を保持します。

### 2. Cubit クラス (CounterCubit)
`CounterCubit` クラスは、アプリの状態を管理する `Cubit` クラスです。
`CounterState` クラスをジェネリック型としています。
コンストラクタ (`CounterCubit()`) では、初期状態として `count: 0` を設定しています。

- `increment()` メソッドで、カウンタを `1` 増やし、新しい状態を 
`emit` メソッドで発行しています。
- `decrement()` メソッドは、カウンタを `1` 減らし、新しい状態を
`emit` メソッドで発行します。

### 3. カウンターページ (MyApp / CounterPage)

`MyApp` クラスは、アプリ全体のルートとなる `StatelessWidget` です。

`build` メソッドでは、 `MaterialApp` ウィジェットを使用して `MaterialApp` を
構築しており、 `BlocProvider` ウィジェットを使用して、 `CounterCubit` インスタンスを
ウィジェットツリー全体に提供しています。

`CounterPage` クラスは、カウンタを UI に表示する `StatelessWidget` です。
`BlocBuilder` ウィジェットを使用して、 `CounterCubit` から発行された
状態変化を監視しています。

`builder` 関数では、現在の状態 (`state`) を引数として受け取り、
関数内で、 `state.count` を元に Text ウィジェットでカウンタ値を表示します。

### 4. 処理の流れ

1. アプリ起動時に、 `CounterCubit` インスタンスが生成され、
初期状態 `(count: 0)` が保持されます。

2. `CounterPage` が `BlocProvider` 経由で `CounterCubit` インスタンスに
アクセスし、ユーザーがボタンを押すと、 `onPressed` イベントが発生し、
`increment` メソッドまたは `decrement` メソッドが呼び出されます。

3. `CounterCubit` 内で、 `count` を更新した新しい `CounterState` が生成され、
`emit` メソッドで発行されます。

4. `BlocBuilder` が `CounterCubit` から発行された状態変化を検出し、
`builder` 関数が呼び出されます。

5. `builder` 関数内で、新しい `state.count` を元に Text ウィジェットが更新され、
UI に反映されます。

# まとめ
`Cubit` は、シンプルな状態管理を行うためのツールです。
Blocパターンの一部として、イベントベースではなくメソッドベースで
状態管理を行いたい場合に使用します。

**Cubitのポイント**

- **シンプルな構造:** イベントを定義する必要がなく、直接メソッドを
呼び出して状態を変更できます。
- **状態管理:** `emit` メソッドを使用して新しい状態を発行し、UIに反映させます。

`Cubit` を使用すると、アプリの状態管理が容易に行えて、コードの見通しも良くなります。
上手く活用して、効率的な状態管理を行いましょう。

# 告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。

https://eda-inc.jp/recruit/

みなさまからのご応募をお待ちしております。