5
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を使ってStateを管理する

5
Posted at

Blocとは?

BlocはFlutterアプリケーションのState管理ライブラリです。B.L.o.Cとは Business Logic Component の略で、Eventを入力として受け取り、Stateを出力として返します。BlocはRxDartをベースに構築されています。

Flutterアプリケーションのアーキテクチャは、以下の3層に分けることができます。

  1. Presentation Layer(View層)
  2. Bloc
  3. Data Layer

つまり、BlocはViewとData、2つの層の間に位置します。


各レイヤーの役割

1. Data Layer

あらゆるソースからデータを提供する役割を担います。Data Provider が生のデータを提供し、Repository が1つまたは複数のData Providerをラップします。

2. Bloc(Business Logic)

Presentation層からEventを受け取り、新しいStateを返す役割を担います。Data層とPresentation層の橋渡しとして機能します。

3. Presentation Layer

1つまたは複数のBlocのStateに基づいて自身を描画する役割を担います。ユーザーの入力イベントやアプリケーションのライフサイクルイベントも処理します。


Blocの主要コンセプト

1. Events

EventはBlocに渡される入力です。Reduxにおける action に相当します。Presentation層では、ボタンクリックなどのユーザー操作によってEventが生成され、Blocに渡されます。Eventには追加データを含めることもできます。

2. States

StateはアプリケーションStateの一部であり、Blocの出力です。Stateが変化すると、UIコンポーネントに通知が送られ、現在のStateに基づいて再描画が行われます。

3. Transition

あるStateから別のStateへの変化を Transition と呼びます。Transitionには、現在のState・Event・次のStateが含まれます。


Blocを使うにあたっての要点

  • BlocはcoreパッケージのBlocベースクラスを extends する必要があります。
  • Blocは initial state(初期State) を定義する必要があります。
  • mapEventToState 関数を実装する必要があります。この関数はEventをパラメータとして受け取り、新しいStateの Stream を返します。
  • BlocのcurrentStateには currentState プロパティでアクセスできます。
  • Blocは dispatch メソッドを持ち、Eventを受け取って mapEventToState をトリガーします。dispatchはPresentation層からも呼び出せます。
  • onTransition はBlocのState更新前に呼ばれます。
  • onError をオーバーライドすることで、Blocの例外を検知できます。

コードで学ぶBlocの実装手順

Step 1: Eventを作成する

part of 'settings_bloc.dart';

@immutable
abstract class SettingsEvent {
  final dynamic payload;
  SettingsEvent(this.payload);
}

class FontSize extends SettingsEvent {
  FontSize(double payload) : super(payload);
}

class Bold extends SettingsEvent {
  Bold(bool payload) : super(payload);
}

class Italic extends SettingsEvent {
  Italic(bool payload) : super(payload);
}

Step 2: Stateを作成する

part of 'settings_bloc.dart';

@immutable
abstract class SettingsState {
  final double sliderFontSize;
  final bool isBold;
  final bool isItalic;

  SettingsState({this.sliderFontSize, this.isBold, this.isItalic});

  double get fontSize => sliderFontSize * 30;
}

class InitialSettingsState extends SettingsState {
  InitialSettingsState()
      : super(sliderFontSize: 0.5, isBold: false, isItalic: false);
}

class NewSettingState extends SettingsState {
  NewSettingState.fromOldSettingState(SettingsState oldState,
      {double sliderFontSize, bool isBold, bool isItalic})
      : super(
          sliderFontSize: sliderFontSize ?? oldState.sliderFontSize,
          isBold: isBold ?? oldState.isBold,
          isItalic: isItalic ?? oldState.isItalic,
        );
}

Step 3: Blocを作成する

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'settings_event.dart';
part 'settings_state.dart';

class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
  @override
  SettingsState get initialState => InitialSettingsState();

  @override
  Stream<SettingsState> mapEventToState(SettingsEvent event) async* {
    if (event is FontSize) {
      yield NewSettingState.fromOldSettingState(currentState,
          sliderFontSize: event.payload);
    } else if (event is Bold) {
      yield NewSettingState.fromOldSettingState(currentState,
          isBold: event.payload);
    } else if (event is Italic) {
      yield NewSettingState.fromOldSettingState(currentState,
          isItalic: event.payload);
    }
  }
}

Step 4: main.dartでBlocProviderを作成する

import 'package:flutter/material.dart';
import 'package:states_bloc/home.dart';
import 'package:states_bloc/about.dart';
import 'package:states_bloc/settings.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:states_bloc/bloc/settings/settings_bloc.dart';

void main() {
  final BlocProvider<SettingsBloc> blocProvider = BlocProvider<SettingsBloc>(
    builder: (_) => SettingsBloc(),
    child: MyApp(),
  );

  runApp(blocProvider);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => Home(),
        '/about': (context) => About(),
        '/settings': (context) => Settings(),
      },
    );
  }
}

Step 5: BlocのStateを使用してEventをdispatchする

import 'package:flutter/material.dart';
import 'package:states_bloc/drawer_menu.dart';
import 'package:states_bloc/bloc/settings/settings_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class Settings extends StatelessWidget {
  double _value = 0.5;
  bool isBold = false;
  bool isItalic = false;

  @override
  Widget build(BuildContext context) {
    final SettingsBloc settingsBloc = BlocProvider.of<SettingsBloc>(context);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.teal,
        title: Text('Settings'),
      ),
      drawer: DrawerMenu(),
      body: BlocBuilder<SettingsBloc, SettingsState>(
        builder: (context, state) {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(left: 20, top: 20),
                child: Text(
                  'Font Size: ${state.fontSize.toInt()}',
                  style: TextStyle(
                      fontSize: Theme.of(context).textTheme.headline.fontSize),
                ),
              ),
              Slider(
                  min: 0.5,
                  value: state.sliderFontSize,
                  onChanged: (newValue) {
                    settingsBloc.dispatch(FontSize(newValue));
                  }),
              Container(
                margin: EdgeInsets.symmetric(horizontal: 8),
                child: Row(
                  children: <Widget>[
                    Checkbox(
                      value: state.isBold,
                      onChanged: (newVal) {
                        settingsBloc.dispatch(Bold(newVal));
                      },
                    ),
                    Text(
                      'Bold',
                      style: getStyle(state.isBold, false),
                    ),
                  ],
                ),
              ),
              Container(
                margin: EdgeInsets.symmetric(horizontal: 8),
                child: Row(
                  children: <Widget>[
                    Checkbox(
                        value: state.isItalic,
                        onChanged: (newVal) {
                          settingsBloc.dispatch(Italic(newVal));
                        }),
                    Text(
                      'Italic',
                      style: getStyle(false, state.isItalic),
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  TextStyle getStyle([bool isBold = false, bool isItalic = false]) {
    return TextStyle(
      fontSize: 18,
      fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
      fontStyle: isItalic ? FontStyle.italic : FontStyle.normal,
    );
  }
}

まとめ

この記事では、FlutterにおけるBlocの概念と基本的な使い方をできる限りシンプルにまとめました。

Blocを活用することで、UIとビジネスロジックを明確に分離でき、保守性の高いアプリケーションを構築できます。ぜひ実際のプロジェクトで試してみてください!

参考

5
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
5
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?