MENU

【Flutter】カウンターアプリでCubit の状態管理を理解しよう

この記事を読むのに必要な時間は約 22 分です。

この記事では、お馴染みのデフォルトカウンターアプリを題材にbloc/cubit を使った状態管理を説明します。

全体のコードはGitHub に上げています。clone して、記事と照らし合わせながら使って下さい。

GitHub
magoblo/counter_cubit_likedemo at main · chiyo03/magoblo magoblo all code. Contribute to chiyo03/magoblo development by creating an account on GitHub.
目次

Cubit とは

ざっくり言うと…

Cubit とは、setState で行ってきた状態更新の処理をUI部分と切り分け、利用しやすくするためのクラスであり、状態管理の手法を指します。

Set Stateを使う状態管理と比べて、Cubitを使った状態管理のほうが画面描画するWidget と状態管理の処理を切り分けることが出来るため、可読性も上がるし、変更を修正するのも簡単です。複数の状態を持つ場合、特に威力を発揮します。

大規模な開発ではBloc(cubit) やriverpod といった状態管理手法が使われますね。

下記の図のようにUI は関数によってcubitを呼び出し、cubitは状態更新をstateとして渡します。

改めてメリットと使い所をまとめるとこんな感じ⬇︎

BLoC ( Business Logic Component)パターンのメリット
・可読性が高まる。
・メンテナンス、テストが容易になる。
・状態の把握が容易になる
・Widget 間のデータのやり取りが可能になる

BLoC パターンを使う場合
・API通信など非同期処理を含む場合
・Widget 間でデータを共有する場合
・可読性を上げたい場合

公式サイトにも説明があるため、参考にして下さい。

実装するアプリの概要

flutter create するとデフォルトで設定されているシンプルなカウンターアプリを例に、statefulwidget の代わりにcubit を使ったシンプルな状態管理を実装します。

出来上がるアプリはこんな感じ⬇︎

カウンターアプリにcubitを実装

flutter_bloc パッケージの設定

まずcubit を使用するため、こちらのインストールページから最新版のパッケージ をコピーし、pubspec.yaml にflutter_bloc を追加します。

dependencies に追加したら保存し、pub getしましょう。右上の矢印ボタンからもpub get が実行できます。

Cubitの作成

さて、今回僕たちがやりたいのはカウンターアプリでSetState によって状態更新(状態管理)している部分をcubit に置き換えるということです。

まずはcounter_cubit.dart というファイルに今回必要なCubit を作っていきます。

import 'package:flutter_bloc/flutter_bloc.dart';

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

  void increase() => emit(state + 1);
  void decrease() => emit(state - 1);
}

Cubit クラスを継承したCounterCubit クラスを実装しています。

ここでCubit には状態管理するstate の型を与える必要があります。
今回はカウントアップダウンの数字の状態を管理するのでint ですね。
また、初期値としてsuperに0 を与えておきましょう。

Cubit にはstate という特別なプロパティがあります。このstate をemit() で処理することにより状態更新がされます。ここではカウンターを足し引きするincrease()、decrease() の中に実装しましょう。

BlocProviderでCubitを渡す

Cubit の実装が終わったところで、こちらをmain.dart に渡します。その役割を担うのがBlocProvider です。

気をつけるべきは、Widget のツリー構造の中で、使用したい箇所より上の階層でCubit を渡すことです。

//...
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  BlocProvider(
        create: ((_) => CounterCubit()),
        child: MyHomePage(title: 'Flutter Demo Home Page')),
    );
  }
}
//...

ここではMyHomePage() をBlocProvider で囲うことにより、
create: プロパティにCounterCubit のインスタンスを渡しています。

※(_) => CounterCubit() がわからない方
flutterでは引数を特に使わない場合、(_) とする慣習があります。ここには実際BuildContext が入っていますがCounterCubit()では使っていませんよね。そう分かると無名関数をアロー関数の形で書いていてだけです。

無名関数、アロー関数がわからない場合は
チートシートにまとめたのでよかったら確認してみて下さい。

BlocBuilderで状態更新時にビルドする

BulocBuilder は状態更新される度、つまりemit() が行われる度にBlocBuilder内のビルドを行い更新内容を画面に反映します。

//...
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            BlocBuilder<CounterCubit, int>(
              builder: ((context, state) => Text(
                    '$state',
                    style: Theme.of(context).textTheme.headline4,
                  )),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
//...

BlocBuilder<CounterCubit,Int> でcubitstateの型 を指定します。
BlocBuilder でTextウィジェットを囲い、画面表示する値としてstate を渡します。

ここまでで状態管理の役割はSetState から Cubit に移りました。不要になったStateful Widget をStateless Widget に変更し、SetStateなどの余分な関数を削除しましょう。

import 'package:counter_cubit_likedemo/bloc/counter_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
          create: ((_) => CounterCubit()),
          child: const MyHomePage(title: 'Flutter Demo Home Page')),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            BlocBuilder<CounterCubit, int>(
              builder: ((context, state) => Text(
                    '$state',
                    style: Theme.of(context).textTheme.headline4,
                  )),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,  // エラー
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

SetStateの処理を削除したのでfloatingActionButton の_increamentCounter でエラーが起きていますね。この部分を修正しましょう。

context.read<Cubit>(). で状態を更新する

context.read はcontextに入っているBlocProviderがcreateしたCubit() 、つまりインスタンスのデータを受け取り、Cubit にある関数を呼び出します。ここではCounterCubit のインスタンスを見にいき、increaseを呼び出すことでemit() が行われ

、状態が更新されます。

//...
floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: context.read<CounterCubit>().increase,
            tooltip: 'Increase',
            child: const Icon(Icons.add),
          ),
          const SizedBox(
            height: 10,
          ),
          FloatingActionButton(
            onPressed: context.read<CounterCubit>().decrease,
            tooltip: 'Decrease',
            child: const Icon(Icons.remove),
          ),
//...

context.read が受け取るcubitを指定し、Cubit 内で実装したincrease を呼び出しています。
ここではfloatingActionButton をもう一つ増やしdecrease の実装もしています。

実装完了

これで全ての実装終了です。改めてmain.dart の完成形はこちらになります。

import 'package:counter_cubit_likedemo/bloc/counter_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
          create: ((_) => CounterCubit()),
          child: const MyHomePage(title: 'Flutter Demo Home Page')),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            BlocBuilder<CounterCubit, int>(
              builder: ((context, state) => Text(
                    '$state',
                    style: Theme.of(context).textTheme.headline4,
                  )),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: context.read<CounterCubit>().increase,
            tooltip: 'Increase',
            child: const Icon(Icons.add),
          ),
          const SizedBox(
            height: 10,
          ),
          FloatingActionButton(
            onPressed: context.read<CounterCubit>().decrease,
            tooltip: 'Decrease',
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

まとめ

今回はカウンターアプリを使ってシンプルなcubitの使い方を紹介しました。

BlocBuilder, BlocProvider, context.read など扱いましたがまだまだ使い方がわかりにくいですよね。

こちらの記事でcubitの使い方をまとめていますのでもっと詳しく知りたい方は是非見て下さい。

カテゴリー

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメント一覧 (5件)

コメントする

目次