お待たせしました。プロジェクトの人員異動でバタバタしていました。
さて、前回ではproviderという実現方式でflutterの状態管理を説明しました。今回はBLoC(Business Logic Component)を利用し、アプリのテーマ(Theme)切替、多言語化を通じてFlutterの状態管理を説明します。
二、BLoCでテーマ切替&多言語化
Blocには3つのポイントがある。それは 業務ロジックユニット、状態、イベント。
flutter_bloc: ^6.0.1
1.テーマ切替のBloc
ハンバーガーメニューにあるテーマ色ボタンの押下により、グローバルにテーマ色を切替える。
1.1状態クラス
テーマ関連の状態量を抽象クラスに置きながら、他の状態はこれを継承し状態の細分化を実現する。
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イベントクラス
イベントをここで定義する。パラメーターの切り替え、状態のリセットなど。
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の核と言っても過言ではない。イベントを通じて状態を生成する。
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状態クラス
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イベントクラス
import 'package:flutter/material.dart';
@immutable
abstract class BlocLocaleEvent {}
class BlocEventSwitch2JP extends BlocLocaleEvent{}
class BlocEventSwitch2EN extends BlocLocaleEvent{}
2.3業務ロジッククラス
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の中に入れる。
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())は状態変更のトリガー
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パターンをご紹介しますので、引き続きお楽しみください。