3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

Cubit

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

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

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

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

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

インストール

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

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0

flutter_blocパッケージとは

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

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

Cubitの使い方

1. Cubitクラスを作成する

Cubit を使用するには、 Cubit クラスを継承して、
状態管理ロジックを定義する必要があります。

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ツリー内に反映できるようにします。

BlocProvider
BlocProvider は、 flutter_bloc パッケージの一部で、
BLoCCubit をウィジェットツリーに提供するために使用されます。

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

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 を使用することで、 BLoCCubit をウィジェットツリー内で共有し、
グローバルにアクセスできます。これにより、特定の状態管理ロジックを
再利用可能にします。

依存関係の注入:

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

状態管理の分離:

BLoCCubit を使用することで、ビジネスロジックとUIロジックを明確に分離できます。
これにより、コードの可読性と保守性が向上します。

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

Widgetから Cubit にアクセスするには、 BlocConsumer ウィジェットを使用します。

BlocConsumer<CounterCubit, int>(
    listener: (context, state) {
        // 処理を入れたい場合はここに追加
    },
    builder: (context, count) {
      return Text(
        'Count: $count',
        style: const TextStyle(fontSize: 24),
      );
    },
),

BlocConsumer は、 Cubit または Bloc の状態を監視し、
状態が変更された際にUIを更新する目的で使用されます。

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 の使い分け

BlocBuilderBlocConsumer はどちらも状態に基づいて UI を
ビルドするために使用されますが、以下の点で異なります

  • BlocBuilder:

    • 状態に応じて UI をビルドするために使用します。
    • 状態が変化するたびに UI を再描画しますが、副作用を持つことはできません。
    • 状態の変化に応じた追加の処理(ダイアログの表示やナビゲーションの実行など)が
      必要ない場合に使用します。
  • BlocConsumer:

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

Cubitでカウンタアプリを作成する

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

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

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. CounterPageBlocProvider 経由で CounterCubit インスタンスに
    アクセスし、ユーザーがボタンを押すと、 onPressed イベントが発生し、
    increment メソッドまたは decrement メソッドが呼び出されます。

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

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

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

まとめ

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

Cubitのポイント

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

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

告知

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

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

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?