Flutter Analysis and Practice: Design of Lightweight Dynamic Rendering Engine

3.2.1 Background

Companies on the Android platform have comprehensive dynamic solutions to implement dynamic native capabilities. Google also provides Android App Bundles to allow developers to better support the dynamic transformation. Apple does not support this, due to concern about the risks of dynamic transformation. Therefore, we consider how to integrate the dynamic capabilities with the web, including the initial WebView-based hybrid solution and the existing React Native and Weex solutions.

Meanwhile, as Xianyu’s Flutter technology is more widely used, more than 10 Flutter pages have been implemented and the demands for dynamic Flutter are also increasing. However, none of the preceding methods are suitable for Flutter scenarios. How can this problem be solved?

3.2.2 Dynamic Solution

3.2.2.1 CodePush

3.2.2.2 Dynamic Template

Therefore, Xianyu proposes its own Flutter dynamic solution.

3.2.3 Template Compilation

3.2.3.1 Template Specifications

Figure 3–6

The built-in sub-controls are MenuTitleWidget, MenuItemWidget, and HintItemWidget. The templates are:

@override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new MenuTitleWidget(data), // 头部
new Column( // 菜单栏
children: <Widget>[
new Row(
children: <Widget>[
new MenuItemWidget(data.menus[0]),
new MenuItemWidget(data.menus[1]),
new MenuItemWidget(data.menus[2]),
],
)
],
),
new Container( // 提示栏
child: new HintItemWidget(data.hints[0])),
],
),
);
}

The style description is omitted here. The method of writing a template file is the same as writing a common widget. However, each widget needs to be modified with new or const, data access must start with “data,” data arrays are accessed in the form of “[],” and dictionaries are accessed in the form of “.”

After writing the template, we must consider how to render it on the client. In an earlier version, files were parsed on the client side. However, to ensure performance and stability, we had to compile these files before sending them to the client side.

3.2.3.2 Compilation Process

Figure 3–7

The child node of the build function parsed from the preceding template is ReturnStatementImpl. It contains the InstanceCreationExpressionImpl child node, corresponding to new Container(...) in the template. The ConstructorNameImpl and ArgumentListImpl child nodes are the most important. ConstructorNameImpl specifies the name of the creation node. ArgumentListImpl specifies the creation parameters, including the parameter list and variables.

The following struct is defined to store the information:

class ConstructorNode {
// 创建节点的名称
String constructorName;
// 参数列表
List<dynamic> argumentsList = <dynamic>[];
// 变量参数
Map<String, dynamic> arguments = <String, dynamic>{};
}

You can retrieve a ConstructorNode tree by recursively traversing the entire tree. The following code provides an example of how to parse a single node:

ArgumentList argumentList = astNode;for (Expression exp in argumentList.arguments) {
if (exp is NamedExpression) {
NamedExpression namedExp = exp;
final String name = ASTUtils.getNodeString(namedExp.name);
if (name == 'children') {
continue;
}
/// 是函数
if (namedExp.expression is FunctionExpression) {
currentNode.arguments[name] =
FunctionExpressionParser.parse(namedExp.expression);
} else {
/// 不是函数
currentNode.arguments[name] =
ASTUtils.getNodeString(namedExp.expression);
}
} else if (exp is PropertyAccess) {
PropertyAccess propertyAccess = exp;
final String name = ASTUtils.getNodeString(propertyAccess);
currentNode.argumentsList.add(name);
} else if (exp is StringInterpolation) {
StringInterpolation stringInterpolation = exp;
final String name = ASTUtils.getNodeString(stringInterpolation);
currentNode.argumentsList.add(name);
} else if (exp is IntegerLiteral) {
final IntegerLiteral integerLiteral = exp;
currentNode.argumentsList.add(integerLiteral.value);
} else {
final String name = ASTUtils.getNodeString(exp);
currentNode.argumentsList.add(name);
}
}

After the ConstructorNode node tree is obtained, a widget tree is generated based on the widget name and parameters.

3.2.4 Rendering Engine

Figure 3–8
  • Developers compile and upload Dart files to Alibaba Cloud Content Delivery Network (CDN.)
  • The client side obtains the template list and stores it.
  • The business side delivers the template ID and template data.
  • The Flutter side obtains the template and creates a widget tree.
  • The native side manages the template and outputs the template to the Flutter side.

3.2.4.1 Template Obtaining

Figure 3–9

3.2.4.2 Widget Creation

Figure 3–10

The process of creating each widget is to parse the argumentsList and arguments in the node and bind data. For example, when you create HintItemWidget, new HintItemWidget(data.hints[0]) needs to be imported. When argumentsList is parsed, a specific value is obtained from the raw data in key-path format, as shown in Figure 3-11.

Figure 3–11

All the obtained values are stored in WidgetCreateParam. When each creation node is recursively traversed, each widget can parse the required parameters from the WidgetCreateParam.

/// 构建Widget用的参数
class WidgetCreateParam {
String constructorName; /// 构建的名称
dynamic context; /// 构建的上下文
Map<String, dynamic> arguments = <String, dynamic>{}; /// 字典参数
List<dynamic> argumentsList = <dynamic>[]; /// 列表参数
dynamic data; /// 原始数据
}

Based on the preceding logic, the ConstructorNode tree can be converted into a widget tree before rendered by the Flutter Framework.

Now, the template can be parsed and rendered on UIs. Then, how should interaction events be handled?

3.2.4.3 Event Processing

Use InkWell as an example. Define the onTap function of InkWell as openURL(data.hints[0]. href, data.hints[0].params). The parsing logic parses it into an event with OpenURL as the ID. The Flutter side provides an event processing mapping table. When you tap InkWell, the corresponding processing function is located, and the corresponding parameter list is obtained and transmitted. The code is:

...
final List<dynamic> tList = <dynamic>[];
// 解析出参数列表
exp.argumentsList.forEach((dynamic arg) {
if (arg is String) {
final dynamic value = valueFromPath(arg, param.data);
if (value != null) {
tList.add(value);
} else {
tList.add(arg);
}
} else {
tList.add(arg);
}
});
// 找到对应的处理函数
final dynamic handler =
TeslaEventManager.sharedInstance().eventHandler(exp.actionName);
if (handler != null) {
handler(tList);
}
...

3.2.5 Final Effect

3.2.5.1 Frame Rate

Figure 3–12

As shown in Figure 3–12, the frame rate remains at 55 to 60. More dynamic cards can be added to check the effect.

Note: “My Page” has some local business judgment. When you return to “My Page,” it is refreshed, which reduces the frame rate.

In terms of implementation, each card needs to be created by traversing the ConstructorNode tree, and the parameters must be parsed for each creation, which can be optimized. For example, if the same widgets are cached, only the data needs to be mapped and bound.

3.2.5.2 Failure Rate

Based on the Flutter dynamic template, changes can be made to Flutter dynamically, instead of being incorporated in the releases. The preceding logic is based on the Flutter native system, with low learning and maintenance costs, and dynamic code can be quickly integrated to the client side.

In addition, Xianyu is working on UI2CODE. If a component needs to be displayed dynamically, the user experience designer has already made a visual draft, converted it into a Dart file through UI2CODE, and then converted the Dart file into a dynamic template in this system. Then, the template is delivered to and rendered directly on the client side.

Based on Flutter widgets, more personalized components can be extended. For example, a built-in animation component can be used to deliver an animation dynamically.

Original Source:

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store