Skip to content

Redux Config & Setup

Valdio Veliu edited this page Apr 1, 2020 · 7 revisions

Redux folder structure

Redux was initially created as a JavaScript state management library and you can read more about it in redux documentation.

The core concepts of redux are:

  • actions: An object that describes what happens when they are dispatched.
  • reducers: Functions that implement what happens when an action is dispatched.

This explains two of four folders of the following structure. But that is not enough in this case. We need some way to manage our data inside the redux store, a way to maintain and structure it. This is where the /models come to play. They are a set o normal MODEL classes that are utilized to group information inside the state. Again they are commonly structured feature-based but it is not uncommon that several features share the same data model inside redux and vice versa. They follow the same rules as normal MODEL classes with some extra twists to it. More on it further in this wiki.

/store
    /actions
    /models     # redux related models, used inside the redux store
    /reducers
    /view_models
  config.dart   # contains the redux store setup

Having this kind of setup for all the data management ensures that all the business logic and data management is encapsulated inside this module. Due to this encapsulation, if for some reason we want to change the logic or replace redux, all that's needed is to re-write only this module. The VIEW layer will be unaffected.

The config.dart

It contains the global configuration of the store and this is where any extra middlewares are applied to the store to extend its capabilities. One example is the persistence traits that are applied to the store so the app can save its state between different sessions.

After all the setup is in place the store is initialized in the main method of the app. That's all the setup needed for the app to have redux. Now everything is handled through the ViewModels and the store is not exposed to the VIEW layer.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final store = await ReduxConfig().getStore();
  runApp(StoreProvider<AppState>(store: store, child: App()));
}

AppState and State models

To contain all the data in the redux state and to structure it in some way we need to create the redux structure tree ourselves. This differs from the JavaScript implementation where there is no necessity to create the structure by hand, we just update the state via reducers. Since now we are in OOP and Strongly typed languages we need to create the structure of the state and we need classes/models so we can shape our state tree.

For this reason, inside the /models folder in redux, there exists the AppState.dart class which is the main class containing every entry of the retux state tree.

As mentioned above, the state models are usually designed featured based. Each State model is created using the singleton pattern using factory constructors. This approach will avoid creating a new instance of the redux extry for every redux update.

Most developers usually avoid the singleton pattern, but in this case, I found it really helpful and it looks like the perfect solution for state management in this setup.

Here is an example of a state model class:

class SettingsData {
  bool darkModeEnabled;
  int randomNumber;

  factory SettingsData() {
    return _singleton;
  }

  SettingsData._internal();

  static final SettingsData _singleton = SettingsData._internal();

  factory SettingsData.fromJson(Map<String, dynamic> json) =>
      _$SettingsDataFromJson(json);

  Map<String, dynamic> toJson() => _$SettingsDataToJson(this);
}

Actions

Actions are pretty simple structures that describe each single unit operation of the app. To follow a modular structure for all the actions in the app, I've created the index.dart file that combines all of them into one single module. Apart from that, every action is pretty simple. Here is an example:

class GalleryImagesListAction {
  final List<ImageItem> items;
  GalleryImagesListAction(this.items);
}

The class of the action also specifies the TYPE of action. Therefore there is no need to create uniques string types like they are usually implemented in JavaScript projects. In dart is simple the action type is GalleryImagesListAction (class name) and action data are whatever is passed in the class constructor.

Reducers

The reducers represent a way to map each action into an operation that updates the redux state.

To setup reducers, we need a way to register all of the reducers into the redux store once it is created. That is the purpose of the following code snippet, /reducers/index.dart. This is similar to combineReducers method used in JavaScript setups of redux. This method is called in the config.dart file, when we setup redux.

AppState appReducer(AppState state, action) {
  return AppState(
    settingsData: settingsReducer(state.settingsData, action),
    /// ... OTHER REDUCERS
  );
}

It is clear that this method combines all of the separate reducers to the redux store. The same logic is applied to each reducer file. Each reducer file contains several reducers based on what actions need to be handled by it and every file contains a "combine" method to combine all of them. This is then registered in this appReducer. In the shown case we have settingsData which is the representative combination of all of the reducers inside the settings.dart file.

This method returns an instance of AppState which is the main class of the redux store, from where every part of the state can be accessed.

ViewModels

ViewModels make use of this reference to the global state to implement their functionalities as described in the ViewModel wiki.