Flutter theme switch to Flutter Redux

above

  1. Run the first Flutter App
  2. Flutter based Widgets – Text
  3. The Flutter scrolls the Widows-ListView
  4. Flutter theme switch to Flutter Redux
  5. Automatic installation of APK for Flutter plugin development
  6. Flutter development a GitHub client OpenGit and learning summary

This article describes in detail how to integrate and use Redux in FLUTTER. The concepts, principles and implementation of Redux are available for readers to explore.

Personal blog

flutter redux

Flutter redux of

Redux consists of Store, Action and Reducer

  • StoreUsed for storage and managementState
  • ActionA behavior triggered by the user
  • ReducerUsed to generate new ones based on ActionState

Flutter redux process

  1. WidgetthroughStoreConnectorThe bindingStoreIn theStatedata
  2. WidgetthroughActionTrigger a new behavior
  3. ReducerAccording to the receiptActionupdateState
  4. updateStoreIn theStateThe binding ofWidget

According to the above process, we realize the theme switch function in the project.

Project integration

Flutter redux library

Pub. dev Address Github address

Integrated flutter redux

Modify pubspec.yaml in the project root directory and add dependencies

Flutter_redux: ^ 0.5.3

Initialize the Store

Take a look at the Store constructor first, as shown in the code below

 Store(
    this.reducer, {
    State initialState,
    List<Middleware<State>> middleware = const[].bool syncStream: false.bool distinct: false,
  })
    : _changeController = new StreamController.broadcast(sync: syncStream) {
    _state = initialState;
    _dispatchers = _createDispatchers(
      middleware,
      _createReduceAndNotify(distinct),
    );
  }
Copy the code

According to the constructor above, we need to create the State first, and also need to complete the initialization of the State; Then you need to create a Reducer; Finally, you need to create Middleware (not for this article);

Create the State

Create a State object, AppState, to store the topic data that needs to be shared, and complete the AppState initialization, as shown in the code below

class AppState {
  ThemeData themeData;

  AppState({this.themeData});

  factory AppState.initial() => AppState(themeData: AppTheme.theme);
}
Copy the code

A default theme is defined in the AppTheme class, as shown in the code below

class AppTheme {
  static final ThemeData _themeData = new ThemeData.light();

  static get theme {
    return_themeData.copyWith( primaryColor: Colors.black, ); }}Copy the code

At this point, the operations related to State are complete.

Create the Reducer

Create a Reducer method, appReducer, and create a Reducer for each parameter in the AppState class, as shown in the code below

AppState appReducer(AppState state, action) {
  return AppState(
    themeData: themeReducer(state.themeData, action),
  );
}
Copy the code

ThemeReducer binds ThemeData to any behavior associated with switching themes, as shown in the code below

final themeReducer = combineReducers<ThemeData>([
  TypedReducer<ThemeData, RefreshThemeDataAction>(_refresh),
]);

ThemeData _refresh(ThemeData themeData, action) {
  themeData = action.themeData;
  return themeData;
}
Copy the code

Bind RefreshThemeDataAction and _refresh with TypedReducer via the combineReducers of Flutter Redux. When the user issues RefreshThemeDataAction each time, Both trigger _refresh to update themeData.

Create an Action

Create an Action object, RefreshThemeDataAction, as shown in the code below

class RefreshThemeDataAction{
  final ThemeData themeData;

  RefreshThemeDataAction(this.themeData);
}
Copy the code

The RefreshThemeDataAction argument themeData is used to receive the new switch theme.

Code integration

With all the preparations ready, let’s create a Store, as shown in the following code

 final store = new Store<AppState>(
    appReducer,
    initialState: AppState.initial(),
 );
Copy the code

The StoreProvider is then used to load the Store, and the MaterialApp is connected to the Store through StoreConnector. At this point we have completed the initialization of the Flutter Redux, as shown in the code below

void main() {
  final store = new Store<AppState>(
    appReducer,
    initialState: AppState.initial(),
  );

  runApp(OpenGitApp(store));
}

class OpenGitApp extends StatelessWidget {
  final Store<AppState> store;

  OpenGitApp(this.store);

  @override
  Widget build(BuildContext context) {
    return new StoreProvider<AppState>(
      store: store,
      child: StoreConnector<AppState, _ViewModel>(
        converter: _ViewModel.fromStore,
        builder: (context, vm) {
          return newMaterialApp( theme: vm.themeData, routes: AppRoutes.getRoutes(), ); },),); }}Copy the code

StoreConnector completes the binding of data and controls by converting store.state data in the _ViewModel via Converter and finally returning the controls that actually need to update the theme through Builder. The code for _ViewModel is shown below

class _ViewModel {
  final ThemeData themeData;

  _ViewModel({this.themeData});

  static _ViewModel fromStore(Store<AppState> store) {
    return_ViewModel( themeData: store.state.themeData, ); }}Copy the code

User behavior

Finally, just add the code for the toggle theme section, which is copied from the Style/Colors copy in the official gallery demo without too much analysis, as shown below

const double kColorItemHeight = 48.0;

class Palette {
  Palette({this.name, this.primary, this.accent, this.threshold = 900});

  final String name;
  final MaterialColor primary;
  final MaterialAccentColor accent;
  final int
      threshold; // titles for indices > threshold are white, otherwise black

  bool getisValid => name ! =null&& primary ! =null&& threshold ! =null;
}

final List<Palette> allPalettes = <Palette>[
  new Palette(
      name: 'RED',
      primary: Colors.red,
      accent: Colors.redAccent,
      threshold: 300),
  new Palette(
      name: 'PINK',
      primary: Colors.pink,
      accent: Colors.pinkAccent,
      threshold: 200),
  new Palette(
      name: 'PURPLE',
      primary: Colors.purple,
      accent: Colors.purpleAccent,
      threshold: 200),
  new Palette(
      name: 'DEEP PURPLE',
      primary: Colors.deepPurple,
      accent: Colors.deepPurpleAccent,
      threshold: 200),
  new Palette(
      name: 'INDIGO',
      primary: Colors.indigo,
      accent: Colors.indigoAccent,
      threshold: 200),
  new Palette(
      name: 'BLUE',
      primary: Colors.blue,
      accent: Colors.blueAccent,
      threshold: 400),
  new Palette(
      name: 'LIGHT BLUE',
      primary: Colors.lightBlue,
      accent: Colors.lightBlueAccent,
      threshold: 500),
  new Palette(
      name: 'CYAN',
      primary: Colors.cyan,
      accent: Colors.cyanAccent,
      threshold: 600),
  new Palette(
      name: 'TEAL',
      primary: Colors.teal,
      accent: Colors.tealAccent,
      threshold: 400),
  new Palette(
      name: 'GREEN',
      primary: Colors.green,
      accent: Colors.greenAccent,
      threshold: 500),
  new Palette(
      name: 'LIGHT GREEN',
      primary: Colors.lightGreen,
      accent: Colors.lightGreenAccent,
      threshold: 600),
  new Palette(
      name: 'LIME',
      primary: Colors.lime,
      accent: Colors.limeAccent,
      threshold: 800),
  new Palette(
      name: 'YELLOW', primary: Colors.yellow, accent: Colors.yellowAccent),
  new Palette(name: 'AMBER', primary: Colors.amber, accent: Colors.amberAccent),
  new Palette(
      name: 'ORANGE',
      primary: Colors.orange,
      accent: Colors.orangeAccent,
      threshold: 700),
  new Palette(
      name: 'DEEP ORANGE',
      primary: Colors.deepOrange,
      accent: Colors.deepOrangeAccent,
      threshold: 400),
  new Palette(name: 'BROWN', primary: Colors.brown, threshold: 200),
  new Palette(name: 'GREY', primary: Colors.grey, threshold: 500),
  new Palette(name: 'BLUE GREY', primary: Colors.blueGrey, threshold: 500)];class ColorItem extends StatelessWidget {
  const ColorItem(
      {Key key,
      @required this.index,
      @required this.color,
      this.prefix = ' '.this.onChangeTheme})
      : assert(index ! =null),
        assert(color ! =null),
        assert(prefix ! =null),
        super(key: key);

  final int index;
  final Color color;
  final String prefix;
  final Function(Color) onChangeTheme;

  String colorString() =>
      "#${color.value.toRadixString(16).padLeft(8.'0').toUpperCase()}";

  @override
  Widget build(BuildContext context) {
    return new Semantics(
      container: true,
      child: new Container(
        height: kColorItemHeight,
        padding: const EdgeInsets.symmetric(horizontal: 16.0),
        color: color,
        child: new SafeArea(
          top: false,
          bottom: false,
          child: FlatButton(
            child: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                new Text('$prefix$index'),
                newText(colorString()), ], ), onPressed: () { onChangeTheme(color); },),),),); }}class PaletteTabView extends StatelessWidget {
  static const List<int> primaryKeys = const <int> [50.100.200.300.400.500.600.700.800.900
  ];
  static const List<int> accentKeys = const <int> [100.200.400.700];

  PaletteTabView({Key key, @required this.colors, this.onChangeTheme})
      : assert(colors ! =null && colors.isValid),
        super(key: key);

  final Palette colors;
  final Function(Color) onChangeTheme;

  @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;
    final TextStyle whiteTextStyle =
        textTheme.body1.copyWith(color: Colors.white);
    final TextStyle blackTextStyle =
        textTheme.body1.copyWith(color: Colors.black);
    final List<Widget> colorItems = primaryKeys.map((int index) {
      return new DefaultTextStyle(
        style: index > colors.threshold ? whiteTextStyle : blackTextStyle,
        child: new ColorItem(
            index: index,
            color: colors.primary[index],
            onChangeTheme: onChangeTheme),
      );
    }).toList();

    if(colors.accent ! =null) {
      colorItems.addAll(accentKeys.map((int index) {
        return new DefaultTextStyle(
          style: index > colors.threshold ? whiteTextStyle : blackTextStyle,
          child: new ColorItem(
              index: index,
              color: colors.accent[index],
              prefix: 'A',
              onChangeTheme: onChangeTheme),
        );
      }).toList());
    }

    return newListView( itemExtent: kColorItemHeight, children: colorItems, ); }}class ThemeSelectPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, _ViewModel>(
        converter: _ViewModel.fromStore,
        builder: (context, vm) {
          return new DefaultTabController(
            length: allPalettes.length,
            child: new Scaffold(
              appBar: new AppBar(
                elevation: 0.0,
                title: const Text("Theme color"),
                bottom: new TabBar(
                  isScrollable: true,
                  tabs: allPalettes
                      .map((Palette swatch) => new Tab(text: swatch.name))
                      .toList(),
                ),
              ),
              body: new TabBarView(
                children: allPalettes.map((Palette colors) {
                  return newPaletteTabView( colors: colors, onChangeTheme: vm.onChangeTheme, ); }).toList(), ), ), ); }); }}class _ViewModel {
  final Function(Color) onChangeTheme;

  _ViewModel({this.onChangeTheme});

  static _ViewModel fromStore(Store<AppState> store) {
    return_ViewModel( onChangeTheme: (color) { SharedPrfUtils.saveInt(SharedPrfKey.SP_KEY_THEME_COLOR, color.value); store.dispatch(RefreshThemeDataAction(AppTheme.changeTheme(color))); }); }}Copy the code

Running effect

Execute the code with the following effect

The source code

Project address -OpenGit client

Refer to the article

www.jianshu.com/p/34a6224e0…