Cubit
Cubit
とは、Flutterで状態管理を簡素化するためのシンプルなクラスです。
Cubit
を使用すると、イベントを定義する必要がなく、
直接メソッドを呼び出して状態を変更することができるようになります。
通常、Blocパターンを採用する際は Bloc
を使用する必要がありますが、
Cubit
は Bloc
と異なり、イベントの概念がなく、状態を直接管理するので、
よりシンプルな実装になります。
BlocパターンのBlocとは?
Blocパターンでは States
と Events
を使って状態を管理します。
その状態とイベントを管理する際に、使用されるのが Bloc
です。
Bloc
は、 Events
を受け取り、結果を States
に反映する役割を担っています。
一方で、 Cubit
は Bloc
の軽量版で、 Events
を受け取らず、
ビジネスロジックを含む関数を直接呼び出して States
を更新します。
つまり、 Cubit
は Events
なしで 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
クラスを継承して、
状態管理ロジックを定義する必要があります。
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
パッケージの一部で、
BLoC
や Cubit
をウィジェットツリーに提供するために使用されます。
特定の BLoC
や Cubit
をウィジェットツリー内でアクセス可能なるため、
子ウィジェットから状態管理ロジックを共有できるようになります。
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
ウィジェットを使用します。
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 の使い分け
BlocBuilder
と BlocConsumer
はどちらも状態に基づいて UI を
ビルドするために使用されますが、以下の点で異なります
-
BlocBuilder:
- 状態に応じて UI をビルドするために使用します。
- 状態が変化するたびに UI を再描画しますが、副作用を持つことはできません。
- 状態の変化に応じた追加の処理(ダイアログの表示やナビゲーションの実行など)が
必要ない場合に使用します。
-
BlocConsumer:
- 状態に応じた UI のビルドと副作用を組み合わせて実行するために使用します。
-
builder
とlistener
の両方のプロパティを持ち、状態の変化に応じて UI を
再描画するだけでなく、状態の変化に対して特定の処理を実行することができます。 - 状態の変化に応じて副作用が必要な場合に使用します。
Cubitでカウンタアプリを作成する
ここでは、カウンターアプリを例にして、 Cubit
の使い方を説明します。
通常、 Cubit
では、状態はシンプルな型(int
や String
など)を使用しますが、
複雑な状態を管理する場合はカスタムの状態クラスを定義することもできます。
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. 処理の流れ
-
アプリ起動時に、
CounterCubit
インスタンスが生成され、
初期状態(count: 0)
が保持されます。 -
CounterPage
がBlocProvider
経由でCounterCubit
インスタンスに
アクセスし、ユーザーがボタンを押すと、onPressed
イベントが発生し、
increment
メソッドまたはdecrement
メソッドが呼び出されます。 -
CounterCubit
内で、count
を更新した新しいCounterState
が生成され、
emit
メソッドで発行されます。 -
BlocBuilder
がCounterCubit
から発行された状態変化を検出し、
builder
関数が呼び出されます。 -
builder
関数内で、新しいstate.count
を元に Text ウィジェットが更新され、
UI に反映されます。
まとめ
Cubit
は、シンプルな状態管理を行うためのツールです。
Blocパターンの一部として、イベントベースではなくメソッドベースで
状態管理を行いたい場合に使用します。
Cubitのポイント
-
シンプルな構造: イベントを定義する必要がなく、直接メソッドを
呼び出して状態を変更できます。 -
状態管理:
emit
メソッドを使用して新しい状態を発行し、UIに反映させます。
Cubit
を使用すると、アプリの状態管理が容易に行えて、コードの見通しも良くなります。
上手く活用して、効率的な状態管理を行いましょう。
告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。
みなさまからのご応募をお待ちしております。