LoginSignup
2
2

More than 3 years have passed since last update.

【Flutterの状態管理】テーマ切替&多言語化 〜provider,BLoC,reduxの3パターンで実現してみる②

Last updated at Posted at 2020-08-01

お待たせしました。プロジェクトの人員異動でバタバタしていました。
さて、前回ではproviderという実現方式でflutterの状態管理を説明しました。今回はBLoC(Business Logic Component)を利用し、アプリのテーマ(Theme)切替、多言語化を通じてFlutterの状態管理を説明します。

二、BLoCでテーマ切替&多言語化

Blocには3つのポイントがある。それは 業務ロジックユニット状態イベント

flutter_bloc: ^6.0.1

color1.png color2.png color5.png color6.png language3.png language4.png

1.テーマ切替のBloc

ハンバーガーメニューにあるテーマ色ボタンの押下により、グローバルにテーマ色を切替える。

1.1状態クラス

テーマ関連の状態量を抽象クラスに置きながら、他の状態はこれを継承し状態の細分化を実現する。

theme_state.dart
import 'package:flutter/material.dart';

@immutable
abstract class BlocThemeState {
  final ThemeData themeData;//テーマ
  final int colorIndex;//色ボタン選択中の状態を記録

  BlocThemeState(this.themeData, this.colorIndex);
}

class InitBlocThemeState extends BlocThemeState {
  //初期化テーマは緑にする
  InitBlocThemeState(ThemeData themeData, int colorIndex) : super(ThemeData(primaryColor: Colors.green), 1);
}

class BlocStateImpl extends BlocThemeState {
  BlocStateImpl(ThemeData themeData, int colorIndex) : super(themeData, colorIndex);
}

1.2イベントクラス

イベントをここで定義する。パラメーターの切り替え、状態のリセットなど。

theme_event.dart
import 'package:flutter/material.dart';

@immutable
abstract class BlocThemeEvent {}

class BlocEventSwitchTheme extends BlocThemeEvent{
  final ThemeData themeData;
  final int colorIndex;
  BlocEventSwitchTheme(this.themeData,this.colorIndex);
}

class BlocEventResetTheme extends BlocThemeEvent{}

1.3業務ロジッククラス

このクラスはBlocの核と言っても過言ではない。イベントを通じて状態を生成する。

theme_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:state_dancer/BLoC/theme_event.dart';
import 'package:state_dancer/BLoC/theme_state.dart';

class ThemeBloc extends Bloc<BlocThemeEvent,BlocThemeState> {

  //overrideメソッド、状態の初期化
  @override
  ThemeBloc(BlocThemeState initialState) : super(initialState);

  @override
  //async* yield 非同期処理関数を生成(あまり使わない) 一々setState()書かなくても済むのはメリット
  Stream<BlocThemeState> mapEventToState(BlocThemeEvent event) async* {
    //テーマ切替
    if(event is BlocEventSwitchTheme) {
      yield BlocStateImpl(event.themeData, event.colorIndex);
    }

    //テーマリセット
    if(event is BlocEventResetTheme) {
      yield InitBlocThemeState();
    }
  }

}

2.多言語化のBloc

ハンバーガーメニューにある言語ボタンの押下により、グローバルに言語を切替える。

2.1状態クラス

lang_state.dart
import 'package:flutter/material.dart';

@immutable
abstract class BlocLocaleState {
  final Locale locale;
  BlocLocaleState(this.locale);
}

class JpBlocLocaleState extends BlocLocaleState {
  JpBlocLocaleState() : super(Locale('ja', 'JP'));
}

class InitBlocLocaleState extends JpBlocLocaleState {}

class EnBlocLocaleState extends BlocLocaleState {
  EnBlocLocaleState() : super(Locale('en', 'US'));
}

2.2イベントクラス

lang_event.dart
import 'package:flutter/material.dart';

@immutable
abstract class BlocLocaleEvent {}

class BlocEventSwitch2JP extends BlocLocaleEvent{}

class BlocEventSwitch2EN extends BlocLocaleEvent{}

2.3業務ロジッククラス

lang_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:state_dancer/BLoC/lang_event.dart';
import 'package:state_dancer/BLoC/lang_state.dart';

class LocalBloc extends Bloc<BlocLocaleEvent,BlocLocaleState> {
  //初期化コンストラクター
  @override
  LocalBloc(BlocLocaleState initialState) : super(initialState);

  @override
  Stream<BlocLocaleState> mapEventToState(BlocLocaleEvent event) async* {
    if(event is BlocEventSwitch2JP) {
      yield JpBlocLocaleState();
    }

    if(event is BlocEventSwitch2EN) {
      yield EnBlocLocaleState();
    }
  }

}

3.Blocの使用

providerパターンと同様に、管理する必要のある状態をMultiBlocProviderの中に入れる。

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';//providerパターンのpubspec.yaml配置を参照
import 'package:state_dancer/BLoC/lang_state.dart';
import 'package:state_dancer/BLoC/theme_state.dart';
import 'home_page.dart';
import 'provider/I18nDelegate.dart';//providerパターン紹介時のファイルを使用
import 'BLoC/theme_bloc.dart';
import 'BLoC/lang_bloc.dart';

void main() {
  runApp(Wrapper(child: MyApp()));
}

class Wrapper extends StatelessWidget {
  final Widget child;
  Wrapper({this.child});

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(providers: [
      BlocProvider<ThemeBloc>(create: (context) => ThemeBloc(InitBlocThemeState()),),
      BlocProvider<LocalBloc>(create: (context) => LocalBloc(InitBlocLocaleState()),),
    ], child: MyApp());
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ThemeBloc, BlocThemeState>(
        builder: (_, theme) => BlocBuilder<LocalBloc, BlocLocaleState>(
            builder: (_, local) => MaterialApp(
                  title: "状態管理Demo",
                  localizationsDelegates: [
                    GlobalMaterialLocalizations.delegate,
                    GlobalWidgetsLocalizations.delegate,
                    I18nDelegate.delegate //言語デリゲート 前回providerパターンのファイルを利用
                  ],
                  locale: local.locale,
                  supportedLocales: [local.locale],
                  debugShowCheckedModeBanner: false,
                  home: HomePage(),
                )));
  }
}


BlocBuilder<~Bloc, ~State>(builder: (_,~State) を通じて状態ゲット;
BlocProvider.of<~Bloc>(context).add(~Event())は状態変更のトリガー

home_page.dart
import 'package:flutter/material.dart';
import 'package:state_dancer/BLoC/lang_bloc.dart';
import 'BLoC/theme_bloc.dart';
import 'BLoC/theme_state.dart';
import 'provider/i18n.dart';//providerパターン紹介時のファイルを使用
import 'package:flutter_bloc/flutter_bloc.dart';
import 'BLoC/theme_event.dart';
import 'BLoC/lang_event.dart';


class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    var screenWidth = MediaQuery.of(context).size.width;
    var statusBarH = MediaQuery.of(context).padding.top;
    var naviBarH = kToolbarHeight;

    //BlocBuilder<ThemeBloc, BlocThemeState>(builder: (_,blocState) を通じて状態ゲット
    return BlocBuilder<ThemeBloc, BlocThemeState>(builder: (_,blocState) =>Scaffold(
        appBar: AppBar(
          backgroundColor: blocState.themeData.primaryColor,
          title: Text(I18N.of(context).title,style: TextStyle(color: Colors.white),),
        ),
        drawer: Drawer(
          child: BlocBuilder<ThemeBloc, BlocThemeState>(builder: (_,blocState) {
            return Column(
              children: <Widget>[
                Container(
                  width: screenWidth,
                  height: statusBarH + naviBarH,
                  color: blocState.themeData.primaryColor,
                ),
                SizedBox(height: 10.0),
                Row(
                  children: <Widget>[
                    Padding(padding: EdgeInsets.all(10)),
                    Wrap(children: <Widget>[
                      RaisedButton(
                        color: Colors.green,
                        onPressed: () {
                          //イベントトリガー、重要
                          BlocProvider.of<ThemeBloc>(context).add(BlocEventSwitchTheme(ThemeData(primaryColor: Colors.green), 1));
                        },
                        child: Text(I18N.of(context).greenBtn,style: TextStyle(color: Colors.white),),
                        shape: CircleBorder(),
                      ),
                      RaisedButton(
                        color: Colors.red,
                        onPressed: () {
                          BlocProvider.of<ThemeBloc>(context).add(BlocEventSwitchTheme(ThemeData(primaryColor: Colors.red), 2));
                        },
                        child: Text(I18N.of(context).redBtn,style: TextStyle(color: Colors.white),),
                        shape: CircleBorder(),
                      ),
                      RaisedButton(
                        color: Colors.blue,
                        onPressed: () {
                          BlocProvider.of<ThemeBloc>(context).add(BlocEventSwitchTheme(ThemeData(primaryColor: Colors.blue), 3));
                        },
                        child: Text(I18N.of(context).blueBtn,style: TextStyle(color: Colors.white),),
                        shape: CircleBorder(),
                      ),
                    ],),

                  ],
                ),
                Divider(),
                SizedBox(height: 10,),
                Row(children: <Widget>[

                  Padding(padding: EdgeInsets.all(10)),
                  SizedBox(width: 15,),
                  RaisedButton(
                    color: blocState.themeData.primaryColor,
                    onPressed: () {
                      BlocProvider.of<LocalBloc>(context).add(BlocEventSwitch2JP());
                    },
                    child: Text("日本語",style: TextStyle(color: Colors.white),),
                  ),
                  SizedBox(width: 15.0,),
                  RaisedButton(
                    color: blocState.themeData.primaryColor,
                    onPressed: () {
                      BlocProvider.of<LocalBloc>(context).add(BlocEventSwitch2EN());
                    },
                    child: Text("English",style: TextStyle(color: Colors.white),),
                  ),
                ],),
              ],
            );
          }),
        ),
        body: ListView(
          children: <Widget>[
            BlocBuilder<ThemeBloc, BlocThemeState>(builder: (_,blocState) {
              return Center(
                child: Column(
                  children: <Widget>[
                    SizedBox(height: 10.0),
                    Container(
                      width: 200,
                      height: 300,
                      color: blocState.themeData.primaryColor,
                    ),
                    SizedBox(height: 10.0),
                    Text(I18N.of(context).analects,
                      style: TextStyle(
                          color: blocState.themeData.primaryColor,
                          fontSize: 18.0,
                          fontWeight: FontWeight.bold),
                    ),
                    SizedBox(height: 30.0),
                    FloatingActionButton(onPressed: (){

                    },
                        backgroundColor: blocState.themeData.primaryColor,
                        child: Icon(Icons.check)),
                  ],
                ),
              );
            })
          ],
        ),
      ),

    );
  }

}

まとめ

本文はBLocでテーマや多言語の切替という多状態を説明しました。次回ではreduxパターンをご紹介しますので、引き続きお楽しみください。

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