1. Basic Knowledge

StatelessWidget: a StatelessWidget that cannot be redrawn using setState to set the state of the component and whose properties should be declared final to prevent change.

Life cycle: Initialize ->build for rendering

StatefulWidget: a StatefulWidget. When a StatefulWidget component is created, it also creates a State object that can be associated with State to refresh the UI

State: In Flutter, widgets and states have different life cycles: Widgets are temporary objects used to build applications in their current State, while State objects remain constant between multiple calls to Build (), allowing them to store information (State).

State lifecycle:

Widget Tree: UI component Tree, but this is only a description, a configuration file (map), only build and rebuild and remove from the Tree, rendering engine does not know

Render Tree:

An Element is the Widget’s instantiated object at a specific location in the UI tree. Most elements have only a single renderObject, but some elements have multiple child nodes, such as classes that inherit from RenderObjectElement. Such as MultiChildRenderObjectElement. Finally, all elements’ RenderObjects form a tree, which we call a render tree, i.e

When the Render tree changes, the Rending layer calculates the changed parts, updates the UI tree, and finally draws the UI tree to the screen. This process is similar to the virtual DOM Diff algorithm in React

Resolves an important contradiction:

The performance cost of DOM manipulation is inconsistent with frequent local DOM manipulation.

BuildContext: Essentially the Element corresponding to the Widget, the Element object can be accessed directly from the context in the build methods of statelessWidgets and StatefulWidgets. Xx.of (context) in state management fetches data across components, essentially calling Element’s related methods, found by walking through the Element.

2. Why state management

When we first build the application, it may be simple, and state management may not be required

With the increase of functions, the application will have dozens or even hundreds of states. When the interaction of the APP becomes complicated, the frequency of setState will increase significantly. Every time setState will call build method again, which is bound to affect the performance, code readability and subsequent maintenance

React is the source of many of the best design of Flutter. It is particularly difficult for React components to communicate with each other at the same level. Therefore, all the states that need to be used by multiple components need to be taken out and distributed in the top container.

Flutter has similar problems. State management enables component communication, cross-component data storage, and separation of UI and business.

3. Declarative programming

If you switch to Flutter from an imperative framework (such as the Android SDK or iOS UIKit), you need to start thinking about app development in a new way. Changing the UI in an imperative framework requires explicit commands: setTextColor, but does not apply to Flutter.

Flutter applications are declarative, which means that the user interface that Flutter builds is the current state of the application.

Note: When the state of your Flutter application changes (for example, when the user clicks an on/off option in the Settings screen) and you change the state, this will trigger a redrawing of the user interface. There is no need to change the user interface itself (such as widget.settext) – you change the state and the user interface is rebuilt.

4. Ephemeral and app status

Briefly state

Transient state (sometimes referred to as user interface (UI) state or local state) is a state that you can completely include in a separate widget.

  • The current page in a PageView component
  • Current progress in a complex animation
  • A Currently selected TAB in the BottomNavigationBar

The rest of the widget tree does not need access to this state. There is no need to serialize the state, and the state does not change in complex ways. In other words, there is no need to use a state management architecture (e.g., ScopedModel, Redux) to manage this state. All you need is a StatefulWidget.

Application state

If you want to share a non-transient state between multiple parts of your application and retain this state for the duration of the user session, we call this application state (sometimes also called shared state). Some examples of application states:

  • User options
  • The login information
  • Notifications in a social application
  • Shopping cart in an e-commerce application
  • Read/unread status of an article in a news application

To manage application state, you need to explore your options. Your choice depends on the complexity and limitations of your application. Flutter does not provide native global state management and relies on third-party libraries to do so. While using inheritedWidgets on root controls is also possible, there are also issues such as deep state passing.

There are no clear rules:

There is no clear, universal rule to distinguish between a variable’s short-term state and its application state, and sometimes you have to refactor between them. For example, you might think of some states as transient at first, but as the application adds functionality, some states need to be changed to application states.

The rule of thumb is: Choose the path that reduces trouble – Dan Abramov, author of Redux

5. Underlying logic

What are the current advantages and disadvantages of Flutter for state management? Answer: State, InheritedWidget, Notification, Stream data streams

The State:

One of the most commonly used and most frequently used state management classes must be used in conjunction with StatefulWidget, which StreamBuilder inherits from and also manages state through setState

State weakness:

  1. SetState is a function of State. Generally, we will set the subclass of State to private, so we cannot make other components call the setState function of State to refresh
  2. SetState can be difficult to maintain because it’s all over the place. As the page state increases, you may end up calling setState in more places than you can manage uniformly
  3. Processing data logic and view are mixed together, violating code design principles such as database data extraction setState to the Ui, such code, resulting in state and Ui coupling, not conducive to testing, not conducive to reuse.
  4. SetState is the entire Widget rebuilt (and the child widgets destroyed along with it), which can lead to a significant performance penalty if the page is complex enough. It is recommended to use StreamBuilder, which is also State in principle, but allows for a partial refresh of the child widgets without causing the entire page to be rebuilt.

InheritedWidget:

The inherent feature is the ability to bind InheritedWidget dependencies to descendant components that depend on it, and automatically update the dependent descendant components when the InheritedWidget data changes! With this feature, we can save the state that needs to be shared across components in an InheritedWidget, and then reference the InheritedWidget in a child component. Functional widgets such as Provider and Scoped_model that are responsible for sharing data in the Widget tree are based on it

InheritedWidget faults:

  1. All child widgets are notified with each update, and directional/directional notification is not possible, which can lead to unnecessary refreshes
  2. State across pages (route) is not supported, meaning across trees. If it is not in a tree, we cannot get it
  3. Data is immutable and must be used in conjunction with StatefulWidget, ChangeNotifier, or Steam

Summary InheritedWidget component is particularly suitable for abstractions public state in the same tree Widget, and each child Widget or grandwidget can obtain the state. We can also optimize redraw logic by means of controlling the granularity of rebuild.

Notification:

It is a mechanism for sharing data across layers in Flutter. Note that it is not a widget. It provides a dispatch method that sends Notification layer by layer along the corresponding Element node of the context.

  1. There is no support for cross-page (route) state, specifically, there is no support for NotificationListener sibling or parent Widget status notification
  2. UI refresh is not supported by itself and needs to be used in conjunction with State
  3. If State is combined, the whole UI will be redrawn, and the efficiency is not scientific

Stream:

The implementation of a pure Dart has nothing to do with Flutter, but with a StreamBuilder to build a Stream Widget. Well-known names like RxDART, BloC, Flutter_redux, and fish_redux all use the Stream API.

The Stream faults:

  1. The API is clunky and hard to understand
  2. Customization is required for more complex scenarios
  3. The downside is that it’s good enough to be flexible enough so that you can make a good design based on it to meet the current business design.

6. What are the status management schemes?

There are many kinds of Flutter state management schemes, including officially recommended ones and excellent tripartite frameworks. They are classified as follows:

  1. Flutter itself supports: State, InheritedWidget, Notification, and Stream data streams
  2. Official recommendation: Provider Redux BLoC/Rx MobX
  3. Three-way excellent framework: Scoped_model Idle Fish fish-redux

7. Analysis of status management scheme

Scoped_model:

Scoped_model is a Dart third-party library that provides functionality that allows you to easily pass your data model from the parent Widget to its descendants. In addition, it re-renders all children that use the model when the model is updated.

It comes directly from a simple extraction of the Model class in the Fuchsia core Widgets, a new system being developed by Google, and is released as a standalone Flutter plugin for standalone use.

The Scoped model uses the observer model. The data model is placed in the parent generation, and the offspring render the data by finding the parent’s model. When the data changes, the parent sends back the data, and the parent notifies all the offspring using the model to update the status. We need to place them on top of the top-level entrance, the MaterialApp, and use the InheritedWidget to manage the global state.

Use steps 1. Add dependencies 2. Create Model (inherit Model) 3. Put Model into top layer 4. Top layer wraps 5 with ScopedModel. Get Model: ScopedModelDescendant in the child page

Pros and cons: Scoped_model simply encapsulates the InheritedWidget, so it inherits the inherent advantages and disadvantages

Advantages: automatic subscription, automatic notification, easy to use Disadvantages: No directional/directional notification, no separation of view logic and business logic, and intrusive because Model must be inherited from Model class.

FAQ:

* 1. It looks like only one model has been added, how should I add multiple models * using mixins! Class MainModel extends Model with AModel,BModel,CModel{} * * 2 Void addListener(VoidCallback Listener); removeListener(VoidCallback Listener); The observer pattern is implemented. * Whenever we call the notifyListeners() method, the observer is notified of the status update. * * 3.Scoped how data can be shared * InheritedWidgets are used to transfer data between different pages. * * Because a Model must inherit from the Model class, it is intrusive. Future state management without scoped will inevitably result in multiple code changes. This is not what we want to see.Copy the code

BLoC:

BLoC stands for Business Logic Component and was designed by Two engineers from Google, Paolo Soares and Cong Hui

BLoC is an approach to building applications using Reactive programming, a completely asynchronous world of flows. BLoC allows us to separate business logic! Don’t worry about the timing of the screen refresh.

Principle:

  • Wrap stateful parts with StreamBuilder, which listens for a stream
  • This stream is coming from BLoC
  • The data in the stateful widget comes from the stream being listened on.
  • The user interaction gesture was detected and an event was generated. Like when you press a button.
  • Call the function bloc to handle this event
  • After processing in bloc, the latest data will be added into the sink of the stream
  • The StreamBuilder listens for the new data, generates a new snapshot, and calls the Build method again
  • The Widget is rebuilt

1. Create a BLoC 2. Create a BLoC 3. Use StreamBuilder in your page

Bloc is an excellent state management method. It performs well in processing a large number of asynchronous events and separating business logic, which is convenient for later maintenance and expansion. However, it still has some defects in sharing state

The Provider:

Provider is the method used for the official documentation example. Google compares the recommended usage. In contrast to BLoC’s streaming idea, providers are an observer pattern, notifyListeners() of state changes.

Internally, the Provider implementation uses inheritedWidgets to allow valid information to be passed to the widgets under the component tree. Advantages of Provider: Dispose is automatically called after it is specified and MultiProvider is supported.

A Provider is easy to understand from its name. It is designed to provide data, and it has its own solution for managing state, whether on a single page or throughout the app.

Common concepts:

  1. ChangeNotifier: The observed object provided by the system. The data model needs to be inherited
  2. Provider: a subscriber. It is only used for data sharing management and provides the UpdateShouldNotify Function to control the refresh time
  3. ChangeNotifierProvider: A subscriber that not only provides data for descendant nodes to use, but also notifies all consumers when data changes. The ChangeNotifierProvider (subscriber) is automatically notified when the Model changes, and the InheritedWidget is rebuilt internally within ChangeNotifierProvider, and the InheritedWidget’s descendant widgets that rely on that InheritedWidget are updated.
  4. MultiProvider: Multiple subscribers: essentially wrapping itself layer by layer through the cloneWithChild method that each provider implements.
  5. Consumer: The ability to dramatically narrow your control refresh range in complex projects. A maximum of six models are supported
  6. Selector: Consumer, enhanced Consumer, filter refresh support

Use process:

  1. Add the dependent
  2. Create data Model
  3. Create top-level shared data
  4. Top-level Provider package
  5. Get the state in the child page

The Provider categories:

  1. Provider: Provides constant data and cannot tell dependent child widgets to refresh
  2. ListenableProvider: The provided object is a subclass of the Listenable abstract class and must implement its addListener/removeListener methods
  3. ChangeNotifierProvider: Provide child nodes with a class that inherits/mixin/implements the ChangeNotifier. Simply use the ChangeNotifier in the Model and call notifyListeners whenever the state needs refreshing
  4. ValueListenableProvider: Provides models that implement inheritance/mixin/ValueListenable, which is actually dedicated to a ChangeNotifier with a single changing data.
  5. StreamProvider: Specifically used to provide a Single Stream.
  6. FutureProvider: Provides a Future to its descendant nodes and notifies the dependent descendant nodes to refresh when the Future is complete

2. Essentially: Prvioder implements local refresh by inheritedElement, updates UI by controlling the Element layer it implements, and calls Dispose via unmount function provided by Element to implement selective release. Its core class: InheritedProvider

Not only does a Provider provide data, but it has a complete solution that covers most situations you will encounter. It also solved the thorny dispose problem that BLoC had not solved, and the intrusion of the ScopedModel. It allows you to develop simple, high-performance, hierarchical applications.

Disadvantages: The Build pattern of Flutter Widgets can easily be compontized at the UI level, but there is still a dependency between models and Views using only providers. Dependencies can only be removed by manually converting a Model to a ViewModel.

Story:

Redux is a one-way data flow architecture that makes it easy to develop, maintain, and test applications, as well as a State management approach recommended by Google.

Principle:

  • All states are stored in the Store. The Store will be placed in the root Widget.
  • The state data that the View receives from the Store is mapped to the View rendering.
  • Redux does not directly let the View manipulate the data, but sends an action to notify the Reducer of the state change
  • Reducer receives this action and generates a new state based on the action state and replaces the old state in the Store.
  • When a Store stores a new state, it notifies all views that use that state of updates (similar to setState). So we can synchronize the states in different views.

Note: When the Store updates the state, it does not change the original state object, but directly replaces the old state object generated by the reducer with the new state object. Therefore, our state should be immutable.

Redux related concepts:

  • State: Data model
  • Store Repository: The top layer of the entire APP that stores and manages state
  • Action: Tell the Reducer that the update status has been updated by issuing an Action
  • Reducer Restore: Create a new state based on the Action
  • StoreProvider: An InheritedWidget that stores a Store internally. (Data center) The top layer must start with StoreProvider
  • StoreConnector: Connector: We need two generics: one is the State (ReduxState) we created, and one is the ViewModel, which determines the return value type on the converter side and provides a StoreStreamListener, It’s essentially a StreamBuilder
  • StoreConverter: Similar to the Selector in Selector, convert the data that the Widget wants
  • StoreStreamListener: Rebuild a view by listening on its own Stream.
  • StoreBuilder: Provides the same functions as StoreConnector, which can convert data before assigning values to components. StoreBuilder directly displays data on components
  • Middleware: Similar to an interceptor, scoped before a reducer state update, is essentially a function.
  • For example, if I want to add user actions (asynchronous actions, action filtering, logging, exception reporting, etc.) before adding a user, I can implement the MiddlewareClass class using middleware.
  • There is a key method next() in the call method of middleware, which needs to be called in most cases, otherwise the chain of middleware is broken, and the following middleware and Reducer will not be executed.
  • Dispatcher: How do you notify status updates? Through the store. Dispatch

Redux page update process

Redux usage process:

  1. Add the dependent
  2. Create the State
  3. Create an action
  4. Create the reducer
  5. Create the store
  6. Put Store on top
  7. Get the state in Store in the child page
  8. Send out the action

Advantages:

  • Automatically subscribe
  • Automatic notification
  • You can direct notifications
  • View and business logic are separated

Disadvantages of Redux:

  • The Redux core is all about data management, not about what scenarios to use it in, which is both its strength and its weakness.
  • There are two specific problems in our actual use of Redux.
  • The contradiction between Redux centralization and Component divide-and-conquer.
  • Reducer of Redux requires layers of manual assembly, resulting in complexity and error-prone.

The Fish – story:

Fish Redux is mainly inspired by excellent frameworks such as Redux, React, Elm and Dva. Standing on the shoulders of giants, Fish Redux takes centralization, divide-and-conquer, reuse and isolation to the next step.

Hierarchical architecture diagram

Fish Redux improvements:

  • New concepts: Adapter, Component.
  • Redux itself provides a global state management solution, not business specific. Fish Redux does centralized observable data management through Redux.
  • Fish_redux is another usage level modification of Redux for the business side: each Component needs to define a data Struct and a Reducer. At the same time, the dependency between components solves the contradiction between centralization and divide-and-conquer.
  • At the same time, the manual Combine of Reducer is automatically completed by the framework, which simplifies the difficulty of using Redux.

Common concepts:

  • Store: the state of the Store.
  • State: page status and data
  • Action: Indicates the Action
  • Effect: Action to receive processing, including callback to life cycle. It is read-only. If you want to modify the data, send an Action to the Reducer. Mainly handle side effects, such as display pop-ups, network requests, database queries, etc.
  • Reducer Action Returns a new state named {verb}, a context-free pure function. In a nutshell, Reducer is responsible for state updates, and Effect is responsible for anything other than state updates.
  • AsReducer: Combine all Reducer of the same component into a large Reducer and provide the components
  • View: Responsible for presentation
  • Component: Local display and function encapsulation, three elements: View, Effect, Reducer.
  • The functions are divided into Reducer and Reduce-data functions (negative effects).
  • The basic element of the Fish Redux is page, which is also based on Component. Do not have its own initState method. 2. Do not use it directly.
  • Adapter: Adapter that occurs when Fish_redux optimizes ListView performance due to the high frequency use of Flutter.
  • Connector: Connects the state of the home page to the Component that is associated with the page.
  • ViewService: a ViewService containing a Context, which is used to assemble adapter, Component, dialog, etc.
  • Page: Assembles a description of the above, with aop enhancements based on Component, and its own state. Page has an initState() method that Component does not.
  • AOP: Middleware, viewMiddleware, effectMiddleware, adapterMiddleware

The Fish – story summary:

Advantages:

  1. Centralized data management, the framework automatically complete reducer merge.
  2. Component divide-and-conquer management, isolating components from each other and containers.
  3. View, Reducer, and Effect isolation. Easy to write reuse.
  4. Declarative configuration assembly.
  5. Good scalability.

The biggest characteristic is to configure the type assembly: on the one hand, will be a big page, to view and the data layer and dismantling for independent Component | Adapter, the upper is responsible for the assembly, the lower is responsible for the implementation; On the other hand, Component | Adapter into the View, Reducer, Effect and other independent context-free function. So it will be very clean, easy to maintain, easy to collaborate with.

Disadvantages:

  1. The framework design is heavy, suitable for complex business scenarios, coupled with complex directory structure and related concepts, is not suitable for ordinary data not too complex business.
  2. The cost of learning is relatively high. Although the scheme is relatively good and has various supports, the development efficiency will be affected if it is not well used in actual use.

8. Summary of state management & Thinking

How to choose a framework?

There is no one-size-fits-all framework, and there is no one-size-fits-all framework. Depending on what the business analysis fits, the code needs to evolve as the business changes. Getting involved with a framework like Fish_redux from the get-go is expensive, difficult, and not a good choice just to implement some simple secondary and tertiary pages.

Selection principle

  • invasive
  • scalability
  • A high performance
  • security
  • ride
  • Ease of use
  • The scope of sex

All frameworks are intrusive, don’t you agree? The ScopedModel, which is currently more intrusive, can be abandoned if you choose a framework that only uses a few of the entries it provides.

High performance: it is also important to understand how it works and see how it does management.

Security: it is also very important to see if his data management channel is secure and stable.

Control: you say you don’t understand you dare to use, out of the problem to find who? If you can’t handle it, don’t use it.

Ease of use: Everyone should understand that if using this framework requires N more configuration, N more implementation, forget it, not suitable. Simplicity is the key.

Scope: This feature is obvious in FLUTTER. When selecting a frame, you must consider the scope of application of the frame. Whether it is suitable for local or global management should be considered.

Can multiple state management frameworks be used simultaneously?

Sure, if you use redux, setState () is not allowed, right? Obviously not. How can using different frameworks at the same time meet your needs for better performance, ease of use, and readability

Q&A

flutterDemo