LoginSignup
0
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-08-02

いよいよ本シリーズの最終篇になります。
本篇は、アプリのテーマ(Theme)切替、多言語化を通じてFlutterの状態管理のreduxパターンを説明します。

三、reduxでテーマ切替&多言語化

flutter_redux: ^0.6.0

Blocと同じのようにreduxにも3つのポイントがある。それは 状態StateアクションAction変化させる者Reducer

会社制に例えると、社長が、仕事を各部門に分与し、部門長ははまたそれぞれ部内の業務を部員に委託する。各々が責任を取って行動する。reduxにおいて、全ての状態はstoreによって集中管理し、社長AppStateから階層的に分配していく。

Simulator Screen Shot - iPhone 11 Pro - 2020-08-02 at 23.52.34.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-02 at 23.52.56.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-02 at 23.53.19.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-02 at 23.53.25.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-02 at 23.53.41.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-02 at 23.53.47.png

1.redux状態、アクション、変化させる者の準備

社長

appState_redux.dart
import 'themeState_redux.dart';
import 'localState_redux.dart';

class AppState {
  final ReduxThemeState themeState;//テーマ状態
  final ReduxLocaleState localeState;//言語状態
  AppState({this.themeState, this.localeState});

  factory AppState.initial()=> AppState(
      themeState: ReduxThemeState.initial(),
      localeState: ReduxLocaleState.initial()
  );
}

//集中的に状態を分配
AppState appReducer(AppState prev, dynamic action)=>
    AppState(
      themeState:themeDataReducer(prev.themeState, action),
      localeState: localReducer(prev.localeState, action),);

部門長① theme

themeState_redux.dart
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';

//テーマ状態の切り替え
class ReduxThemeState extends ChangeNotifier {
  ThemeData themeData; 
  int colorIndex; 

  ReduxThemeState(this.themeData,this.colorIndex);

  factory ReduxThemeState.initial()=> ReduxThemeState(ThemeData(primaryColor: Colors.green),1);
}

//テーマアクションの切り替え
class ActionSwitchTheme {
  final ThemeData themeData;
  final int colorIndex;
  ActionSwitchTheme(this.themeData,this.colorIndex);
}

//変化させる者の切り替え
var themeDataReducer = TypedReducer<ReduxThemeState, ActionSwitchTheme>((state, action) =>
    ReduxThemeState(action.themeData,action.colorIndex));

部門長② local

localState_redux.dart
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';

//言語状態の切り替え
class ReduxLocaleState{
  Locale locale;
  ReduxLocaleState(this.locale);
  factory ReduxLocaleState. initial()=> ReduxLocaleState(Locale('ja', 'JP'));
}

//言語アクションの切り替え
class ActionSwitchLocal {
  final Locale locale;
  ActionSwitchLocal(this.locale);
  factory ActionSwitchLocal.ja()=> ActionSwitchLocal(Locale('ja', 'JP'));
  factory ActionSwitchLocal.en()=> ActionSwitchLocal(Locale('en', 'US'));
}

//変化させる者の切り替え
var localReducer = TypedReducer<ReduxLocaleState, ActionSwitchLocal>((state,  action) => 
    ReduxLocaleState(action.locale,));

2.reduxプロパティーの使用

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; //providerパターンのpubspec.yaml配置を参照
import 'home_page.dart';
import 'provider/I18nDelegate.dart'; //providerパターン紹介時のファイル使用
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'redux/appState_redux.dart';

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

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

  @override
  Widget build(BuildContext context) {
    //StoreProviderによってラップする、storeプロパティーでAppStateを配置
    return StoreProvider(
        store: Store<AppState>(appReducer, initialState: AppState.initial()),
        child: MyApp());
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreBuilder<AppState>(builder: (context,store) => MaterialApp(
                  title: "状態管理Demo",
                  localizationsDelegates: [
                    GlobalMaterialLocalizations.delegate,
                    GlobalWidgetsLocalizations.delegate,
                    I18nDelegate.delegate //言語デリゲート シリーズ初編のproviderパターンのファイルを利用
                  ],
                  locale: store.state.localeState.locale,
                  supportedLocales: [store.state.localeState.locale],
                  debugShowCheckedModeBanner: false,
                  home: HomePage(),
                ));
  }
}

状態の切り替えは store.dispatch(~action) を使う

home_page.dart
import 'package:flutter/material.dart';
import 'provider/i18n.dart'; //providerパターン紹介時のファイル使用
import 'package:flutter_redux/flutter_redux.dart';
import 'redux/appState_redux.dart';
import 'redux/themeState_redux.dart';
import 'redux/localState_redux.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;

    //StoreBuilder<AppState>(builder: (context,store) を通じて状態ゲット
    return StoreBuilder<AppState>(
      builder: (context, store) => Scaffold(
        appBar: AppBar(
          backgroundColor: store.state.themeState.themeData.primaryColor,
          title: Text(
            I18N.of(context).title,
            style: TextStyle(color: Colors.white),
          ),
        ),
        drawer: Drawer(
          child: Column(
            children: <Widget>[
              Container(
                width: screenWidth,
                height: statusBarH + naviBarH,
                color: store.state.themeState.themeData.primaryColor,
              ),
              SizedBox(height: 10.0),
              Row(
                children: <Widget>[
                  Padding(padding: EdgeInsets.all(10)),
                  Wrap(
                    children: <Widget>[
                      RaisedButton(
                        color: Colors.green,
                        onPressed: () {
                          //イベントトリガー、重要
                          store.dispatch(ActionSwitchTheme(
                              ThemeData(primaryColor: Colors.green), 1));
                        },
                        child: Text(
                          I18N.of(context).greenBtn,
                          style: TextStyle(color: Colors.white),
                        ),
                        shape: CircleBorder(),
                      ),
                      RaisedButton(
                        color: Colors.red,
                        onPressed: () {
                          store.dispatch(ActionSwitchTheme(
                              ThemeData(primaryColor: Colors.red), 2));
                        },
                        child: Text(
                          I18N.of(context).redBtn,
                          style: TextStyle(color: Colors.white),
                        ),
                        shape: CircleBorder(),
                      ),
                      RaisedButton(
                        color: Colors.blue,
                        onPressed: () {
                          store.dispatch(ActionSwitchTheme(
                              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: store.state.themeState.themeData.primaryColor,
                    onPressed: () {
                      store.dispatch(ActionSwitchLocal.ja());
                    },
                    child: Text(
                      "日本語",
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                  SizedBox(
                    width: 15.0,
                  ),
                  RaisedButton(
                    color: store.state.themeState.themeData.primaryColor,
                    onPressed: () {
                      store.dispatch(ActionSwitchLocal.en());
                    },
                    child: Text(
                      "English",
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
        body: ListView(
          children: <Widget>[
            Center(
              child: Column(
                children: <Widget>[
                  SizedBox(height: 10.0),
                  Container(
                    width: 200,
                    height: 300,
                    color: store.state.themeState.themeData.primaryColor,
                  ),
                  SizedBox(height: 10.0),
                  Text(
                    I18N.of(context).analects,
                    style: TextStyle(
                        color: store.state.themeState.themeData.primaryColor,
                        fontSize: 18.0,
                        fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 30.0),
                  FloatingActionButton(
                      onPressed: () {},
                      backgroundColor:
                          store.state.themeState.themeData.primaryColor,
                      child: Icon(Icons.check)),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}


まとめ

いかがでしたか。本篇をもってflutterの状態管理シリーズを完了とします。総じて言えば、3パターンともに構造的に、実現方式的に似ているようなところがあります。providerはflutter公式が推奨するパターンとして使っても無難でしょう。Streamに対する理解を深めれば、Blocの使用は少し上級感が感じられます。reduxについては構造は明白で、このパターンを取る方も少なくないと思います。
どのパターンを採用するのかは正解がなく、プロジェクトのニーズに合わせて決めておけば良いでしょう。また、状態を変更する必要のあるwidgetがそれほど無ければ、基礎のsetState()というapiを使っては分かりやすいでしょう。

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