0
0

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の仕組み

Posted at

こんにちは!
アカウントを変えたのですが、今後も変わらず備忘録と新しく勉強したのをアウトプットしていく目的で更新していきたいです:point_up:

今回は、Flutterの状態管理のBloCについて詳しく書いていこうと思います。
今までの状態管理はRiverpodを使用していたのですが、blocを使う機会があったので忘れないように備忘録です!
Riverpodは比較的シンプルに導入しやすく、学びやすいと言われている一方でBloCは設計ルールが多く難易度は上がるそう。

設計図

スクリーンショット 2025-05-17 22.54.26.png

わかりやすい画像があったので、お借りしました。

1. UI Screen(UI画面)

役割:ユーザーが直接触れる部分(ボタン、フォーム、リストなど)

説明:ユーザーに表示される画面。ユーザーの操作はBlocに伝わり、結果は画面に反映される

👉 ユーザーに見える層

2. BLoC

役割:UIとビジネスロジックの仲介(Presenter / ViewModel)

説明:イベントを受け取り、ロジックを処理して状態(State)を発行する。UIはこのStateを監視して再描画される

👉 ロジックを管理する中心的な層

3. Repository(リポジトリ)

役割:データ取得・保存の仲介(Data Handler)

説明:Blocとデータソース(APIやDBなど)の間でやりとりを担当する層。複数のデータソースをまとめて隠蔽する

👉 「どこからデータを取るか」をBlocに意識させない

4. Network Provider(ネットワークプロバイダー)

役割:データを提供する層(Data Provider)

説明:実際にAPI通信やDBアクセスを行う層。外部とやり取りするコードがここに集約される

👉 最下層。外部データとの接続役

試作

簡単なカウントアプリを実装します

階層

スクリーンショット 2025-05-17 23.33.18.png

lib/
├── screens/              ← 画面単位の機能をまとめたディレクトリ
│   ├── bloc/             ← 状態管理(BLoC)関連をまとめる
│   │   ├── counter_bloc.dart     ← イベントを受けて状態を変えるロジック(Presenter相当)
│   │   ├── counter_event.dart    ← どんな操作が起きたかを表す(例:Increment, Decrement)
│   │   └── counter_state.dart    ← 今の状態を表す(例:カウント値)
│   ├── model/
│   │   └── counter.dart          ← データ構造の定義(ここではカウント値のモデルなど)
│   ├── repository/
│   │   └── counter_repository.dart ← データ取得・保存などの処理(今回は加算・減算のロジック)
│   ├── widget/
│   │   └── counter_display.dart   ← UI部品を分離(表示専用Widget)
├── main.dart              ← アプリ起動&ルートWidget

1. Model作成

lib/screens/model/counter.dart

class Counter {
  final int value;
  Counter(this.value);
}

2. Repository作成

lib/screens/repository/counter_repository.dart

class CounterRepository {
  int increment(int current) => current + 1;
  int decrement(int current) => current - 1;
}

3. Event・Stateを定義

lib/screens/bloc/counter_event.dartlib/screens/bloc/counter_event.dart

abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

lib/screens/bloc/counter_state.dart

class CounterState {
  final int count;
  const CounterState(this.count);
}

4. BloC本体を実装

lib/screens/bloc/counter_bloc.dart

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  final CounterRepository repository;

  CounterBloc(this.repository) : super(const CounterState(0)) {
    on<Increment>((event, emit) {
      emit(CounterState(repository.increment(state.count)));
    });
    on<Decrement>((event, emit) {
      emit(CounterState(repository.decrement(state.count)));
    });
  }
}

5. UIパーツを作る

lib/screens/widget/counter_display.dart

class CounterDisplay extends StatelessWidget {
  final int count;
  const CounterDisplay({super.key, required this.count});

  @override
  Widget build(BuildContext context) {
    return Text('カウント: $count', style: TextStyle(fontSize: 32));
  }
}

6. 呼び出し

/lib/main.dart

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

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(CounterRepository()),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Bloc Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return CounterDisplay(count: state.count);
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => context.read<CounterBloc>().add(Increment()),
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => context.read<CounterBloc>().add(Decrement()),
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

結果

カウンターgif.gif

まとめ

BlocはUIと動きの部分を別ファイルの分けられるので、綺麗にまとまっていて見やすいと実感してます。
あと状態管理のながれも見やすいので、テストしやすい!

いいお勉強になりました:relaxed:

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?