With the booming development of mobile smart devices, a mobile multi-terminal development framework has become a general trend. Download the Flutter Analysis and Practice: Evolution and Innovation of Xianyu Technologies eBook for step-by-step analyses based on real-life problems, as well as clear explanations of the important concepts of Flutter.
The open-source Fish Redux of Xianyu on GitHub is an assembled Flutter app framework based on Redux data management. It is especially suitable for building large- and medium-sized complex apps. It features the functional programming model, predictable status management, pluggable component system, and best performance. This article describes the features and usage of Fish Redux.
At the beginning of Flutter connection, Xianyu's businesses were complex, mainly in two aspects:
When trying Redux and BLOC frameworks, Xianyu found that no framework could solve both the centralized status management and UI componentization issues due to the contradiction between these two issues. To use one framework to solve these issues, Xianyu developed Fish Redux.
Fish Redux has gone through three major iterations and has also been intensively discussed and considered by the team.
The first version was modified based on Flutter_Redux
in the community. The core of this version was componentization of UI code. However, for complex details and service publishing, a lot of business logic was used, making it impossible to componentize the logic code.
The second version was significantly modified. Although it solved the problem of separate governance on UI code and logic code, it broke the principle of Redux. This was unacceptable for the Xianyu team, which strives for excellence.
The third version was refactored, during which Xianyu established the overall architectural principles and layered requirements. On the one hand, Redux is implemented on the Flutter side according to ReduxJS code, which completely preserves the principle of Redux. On the other hand, components on Redux are encapsulated. In addition, the capability of separately governing business code is innovatively provided through architectural design at this layer.
In this way, Xianyu has completed the basic design of Fish Redux. In subsequent apps, Xianyu detected the code performance problem after business assembly. Xianyu provided a solution to solve this problem in long-list scenarios. Currently, Fish Redux is running stably online.
As shown in Figure 3-1, Fish Redux is divided into two layers from the bottom up.
1) What can Redux do?
Redux is a centralized and flexible data management framework that is easy to debug and can be used for making predictions. Redux handles all operations, such as adding, deleting, modifying, and querying data.
2) How is Redux designed and implemented?
Traditionally, object-oriented programming (OOP) manages data by using beans, each bean exposing several public APIs to manipulate internal data.
In the functional approach, data is defined as structs (anemic model) and the method of manipulating data is unified into a reducer with the same function signature.
(T, Action) => T:
FP: Struct + Reducer = OOP:bean (hyperemia model)
Redux includes the middleware, (aspect-oriented programming (AOP) mode and "subscribe" mechanism commonly used in FP. This gives the framework strong flexibility and scalability.
Figure 3-1
3) Disadvantages of Redux
Redux is only concerned with data management but not with its scenarios, which is both an advantage and disadvantage of Redux.
Xianyu encounters two specific problems when using Redux:
4) Fish Redux Improvement
Fish Redux performs centralized and observable data management through Redux. In addition, it offers better and higher abstraction than Redux in terms of usage in development scenarios for the Flutter pages on the client side.
Each component consists of a data point (struct) and a reducer. There is a parent-child dependent relationship between different components. Fish Redux uses this dependent relationship to resolve the contradiction between centralization and division of components. Fish Redux also enables the reducer to be automatically assembled by the framework. This greatly simplifies Redux usage. In this way, Xianyu implements centralization and division of code.
5) Alignment with Community Standards
The concepts of state, action, reducer, store, and middleware mentioned previously are consistent with the community's ReduxJS. Xianyu retains all of Redux's advantages.
Components involve local presentation and function encapsulation. Based on Redux's principles, functions are subdivided into data modification functions (reducers) and functions other than data modification (effects.)
View, effect, and reducer are the three elements of a component. They are responsible for component presentation, non-modification behavior, and modification behavior, respectively.
This subdivision is oriented to the present and the future. The current Redux regards this subdivision as "data management" and "other," while future-oriented UI-automation regards this subdivision as "UI expression" and "other."
UI expression is about to enter a black box era and R&D engineers will focus more on modification and non-modification behavior.
Components are the division of views and the division of data. Through layer-by-layer division, complex pages and data are divided into small modules that are independent of each other, facilitating collaborative development within teams.
1) View
The view is simply a function signature: (T,Dispatch,ViewService) => Widget
. It contains information with three characteristics.
ViewService
, for example, in a typical view signature-compliant function.Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) {
final ListAdapter adapter = viewService.buildAdapter();
return Scaffold(
appBar: AppBar(
backgroundColor: state.themeColor,
title: const Text('ToDoList'),
),
body: Container(
child: Column(
children: <Widget>[
viewService.buildComponent('report'),
Expanded(
child: ListView.builder(
itemBuilder: adapter.itemBuilder,
itemCount: adapter.itemCount))
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => dispatch(PageActionCreator.onAddAction()),
tooltip: 'Add',
child: const Icon(Icons.add),
),
);
}
2) Effect
As a standard definition for non-modification behavior, the effect is a function signature: (Context, Action) => Object
. It contains information with four characteristics.
The following shows an example of good coroutine support.
void _onRemove(Action action, Context<ToDoState> ctx) async {
final String select = await showDialog<String>(
context: ctx.context,
builder: (BuildContext buildContext) {
return AlertDialog(
title: Text('Are you sure to delete "${ctx.state.title}"?'),
actions: <Widget>[
GestureDetector(
child: const Text(
'Cancel',
style: TextStyle(fontSize: 16.0),
),
onTap: () => Navigator.of(buildContext).pop(),
),
GestureDetector(
child: const Text('Yes', style: TextStyle(fontSize: 16.0)),
onTap: () => Navigator.of(buildContext).pop('Yes'),
)
],
);
});
if (select == 'Yes') {
ctx.dispatch(ToDoActionCreator.removeAction(ctx.state.uniqueId));
}
}
3) Reducer
A reducer is a Redux-compliant function signature: (T, Action) => T
. The following shows a signature-compliant reducer.
PageState _initToDosReducer(PageState state, Action action) {
final List<ToDoState> toDos = action.payload ?? <ToDoState>[];
final PageState newState = state.clone();
newState.toDos = toDos;
return newState;
}
The widgets and adapters that large components depend on are registered through explicit configuration. This dependency configuration is called dependencies.
Therefore, Component = View + Effect (optional) + Reducer (optional) + Dependencies (optional)
.
The following shows a typical assembly example.
class ToDoListPage extends Page<PageState, Map<String, dynamic>> {
ToDoListPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
dependencies: Dependencies<PageState>(
adapter: NoneConn<PageState>() + ToDoListAdapter(),
slots: <String, Dependent<PageState>>{
'report': ReportConnector() + ReportComponent()
}),
);
}
Through abstraction of a component, complete division, multi-dimension reuse, and better decoupling are achieved.
The adapter also involves local presentation and function encapsulation. It is created for the high-performance scenarios of ListView
and is a change in the component implementation.
The adapter aims to solve three problems the component model faces in Flutter-ListView
scenarios.
1) Putting a "Big-Cell" in the component makes it impossible to enjoy the ListView
code's performance optimization.
2) The component cannot distinguish between appear/disappear
and init/dispose
.
3) The coupling between the effect's lifecycle and the view does not meet the intuitive expectations in ListView
scenarios.
In short, what is needed is an abstraction of local presentation and function encapsulation that is logically a ScrollView
but functionally a ListView
. Xianyu compares the performance when no framework is used for the page and when the framework and adapter are used.
Reducer is long-lived, Effect is medium-lived, View is short-lived.
Testing with an Android device obtains the following results:
The recommended directory structure in Fish Redux:
sample_page
-- action.dart
-- page.dart
-- view.dart
-- effect.dart
-- reducer.dart
-- state.dart
components
sample_component
-- action.dart
-- component.dart
-- view.dart
-- effect.dart
-- reducer.dart
-- state.dart
The upper layer is responsible for assembly, while the lower layer is responsible for implementation. A plug-in is provided for quick filling. Figure 3-2 shows an example of the assembly in a details page scenario.
Figure 3-2
class DetailPage extends Page<RentalDetailState, DetailParams> {
DetailPage()
: super(
initState: initRentalDetailState,
view: buildMainView,
reducer: asReducer(RentalDetailReducerBuilder.buildMap()),
dependencies: Dependencies<RentalDetailState>(
slots: <String, Dependent<RentalDetailState>>{
'appBar': CommonBuyAppBarConnector() + AppBarComponent(),
'body': CommonBuyItemBodyConnector() +
RentalBodyComponent(slots: <Dependent<RentalBodyState>>[
galleryConnector() + RentalGalleryComponent(),
priceConnector() + RentalPriceComponent(),
titleConnector() + RentalTitleComponent(),
bannerConnector() + RentalBannerComponent(),
detailTitleConnector() + RentalDetailTitleComponent(),
equipmentConnector() + RentalEquipmentComponent(),
descConnector() + RentalDescComponent(),
tagConnector() + RentalTagComponent(),
itemImageConnector() + RentalImageComponent(),
marketConnector() + RentalMarketComponent(),
channelConnector() + RentalChannelComponent(),
productConnector() + ProductParamComponent(),
recommendConnector() + RentalRecommendAdapter(),
paddingConnector() + PaddingComponent(),
]),
'bottomBar':
CommonBuyBottomBarConnector() + RentalBottomBarComponent(),
},
),
);
}
There is complete independence between components and between components and containers.
Figure 3-3 shows intra-component communication and inter-component communication.
A prioritized broadcast clip (self-first-broadcast) is used for dispatch communication APIs. An issued action will first be self-processed, or it will be broadcast to other components and Redux for processing. Finally, all communication requirements inside components and between components (such as parent-child, child-parent, and sibling-sibling components) are implemented through a simple and intuitive dispatch.
Figure 3-3
1) Data Refreshing
In local data modification, a shallow copy of the upper layer data is automatically triggered layer by layer, as shown in Figure 3-4. This is transparent to the upper layer business code. Layer-by-layer data copying strictly complies with Redux's data modification and data-driven presentation.
Figure 3-4
2) View Refreshing
Notifications are sent to all components, and components determine whether they need to refresh the view through shouldUpdate
, as shown in Figure 3-5.
Figure 3-5
1) Centralized Data Management
Fish Redux performs centralized and observable data management through Redux, in which all the advantages of Redux are retained. In addition, the reducer is assembled automatically by the framework, greatly simplifying the use of Redux.
2) Division Management of Components
Components are the division of views and the division of data. Through layer-by-layer division, complex pages and data are divided into small modules that are independent of each other, facilitating collaborative development within teams.
3) Isolation Between the View, Effect, and Reducer
A component is split into three stateless and independent functions. These functions are easy to write, debug, test, and maintain due to their statelessness. This also brings greater possibilities for combination, reuse, and innovation.
4) Declarative Configuration Assemblies
Components and adapters are assembled through free and declarative configuration, including components' view, reducer, effect, and dependent child-relationships.
5) Strong Scalability
The core framework only deals with its core focuses while maintaining flexible scalability for the upper layer.
6) Small, simple, and complete
Flutter Analysis and Practice: Design of Lightweight Dynamic Rendering Engine
56 posts | 4 followers
FollowXianYu Tech - September 8, 2020
Alibaba Clouder - September 21, 2020
XianYu Tech - September 10, 2020
XianYu Tech - September 11, 2020
XianYu Tech - September 9, 2020
XianYu Tech - September 10, 2020
56 posts | 4 followers
FollowHelp enterprises build high-quality, stable mobile apps
Learn MoreAn enterprise-level continuous delivery tool.
Learn MoreProvides comprehensive quality assurance for the release of your apps.
Learn MoreAlibaba Cloud (in partnership with Whale Cloud) helps telcos build an all-in-one telecommunication and digital lifestyle platform based on DingTalk.
Learn MoreMore Posts by XianYu Tech