redux is mainly composed of Store, Action and Reducer
- Store is used to store and manage State
- Action is a kind of behavior triggered by the user
Reducer , is used to generate a new , State according to the Action
Fluent Redux process
1. The Widget binds the "State" data in the "Store" through the "StoreConnector"
2. Widget triggers a new behavior through Action
3. Reducer updates the State according to the received Action
4. Update the State bound Widget in the Store
According to the above process, we realize the theme switching function in the project.Project integration
Fluent Redux Library
Integrated fluent Redux
Modify pubspec. In the root directory of the project Yaml and add dependencies
flutter_redux: ^0.5.3
Initialize Store
First, take a look at the constructor of {Store}, as shown in the following code
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), ); }
According to the above constructor, we first need to create a State and complete the initialization of the State; Then you need to create a} Reducer; Finally, you need to create} Middleware (not the content to be explained in this article for the time being);
Create State
Create a "State" object "AppState", which is used to store the subject data to be shared, and complete the "AppState" initialization, as shown in the following code
class AppState { ThemeData themeData; AppState({this.themeData}); factory AppState.initial() => AppState(themeData: AppTheme.theme); }
The AppTheme} class defines a default theme} theme, as shown in the following code
class AppTheme { static final ThemeData _themeData = new ThemeData.light(); static get theme { return _themeData.copyWith( primaryColor: Colors.black, ); } }
At this point, the related operations of State have been completed.
Create Reducer
Create a {Reducer} appReducer method to create a} Reducer for each parameter in the} AppState class, as shown in the following code
AppState appReducer(AppState state, action) { return AppState( themeData: themeReducer(state.themeData, action), ); }
themeReducer binds ThemeData with all behaviors related to topic switching, as shown in the following code
final themeReducer = combineReducers<ThemeData>([ TypedReducer<ThemeData, RefreshThemeDataAction>(_refresh), ]); ThemeData _refresh(ThemeData themeData, action) { themeData = action.themeData; return themeData; }
Use # combineReducers # and # TypedReducer # of # fluent reduce # to connect # RefreshThemeDataAction # and # TypedReducer #_ refresh is bound with , which will be triggered every time the user issues , RefreshThemeDataAction ,_ refresh, used to update} themeData.
Create Action
Create an Action object RefreshThemeDataAction, as shown in the following code
class RefreshThemeDataAction{ final ThemeData themeData; RefreshThemeDataAction(this.themeData); }
The parameter themeData of RefreshThemeDataAction is used to receive the newly switched topic.
Code integration
All preparations for creating a Store are ready. Create a Store below, as shown in the following code
final store = new Store<AppState>( appReducer, initialState: AppState.initial(), );
Then, load the Store with the StoreProvider, and the MaterialApp maintains a connection with the StoreProvider through the StoreConnector. So far, we have completed the initialization of {fluent Redux}, as shown in the following code
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 new MaterialApp( theme: vm.themeData, routes: AppRoutes.getRoutes(), ); }, ), ); } }
StoreConnector , through , converter , in_ Convert the store in ViewModel The data of state , and finally return the control that actually needs to update the theme through , builder , so as to complete the binding of data and control. _ The code of ViewModel is shown below
class _ViewModel { final ThemeData themeData; _ViewModel({this.themeData}); static _ViewModel fromStore(Store<AppState> store) { return _ViewModel( themeData: store.state.themeData, ); } }
User behavior
Finally, you only need to add the code for switching topics, which is derived from the Style/Colors copy in the official gallery demo without too much analysis, as shown in the following code
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 get isValid => 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'), new Text(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 new ListView( 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 new PaletteTabView( 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))); }, ); } }
Operation effect
Execute the code with the following effect