**1, write in front **

Flutter has attracted a lot of attention since its inception. Its efficient self-rendering technology is destined to outperform its predecessors in terms of performance and experience. The only problem with Flutter is that it does not currently have the dynamic updating capabilities of Hybrid, RN, Weex, etc. The idea of dynamism was officially endorsed in the Roadmap for 2019, but was later dropped due to performance and safety concerns.

The purpose of the right-most Flutter dynamic is to provide a variety of options for technical selection, providing a better solution in some scenarios that use H5 but are highly interactive, or using native but non-core independent scenarios. Today, I would like to share some lessons from the process of making Flutter dynamic with the most right App. The following is an outline of the article and its ideas to make it easier for you to understand:

2. Android first

2.1 Implementation Roadmap

To be dynamic is to load code and resources in a specified path and ensure that the code executes properly. To achieve this, it is necessary to modify Engine. We will first Engine source Clone down, configure the Engine compiler environment, specific referenceThe official Wiki[1]. At the same time, we must first understand the startup process of Flutter itself and how the system itself works. If you are not sure, please check Out Gityuan’s blog —In-depth understanding of Flutter engine startup[2]. As you can see from section 3.3.1 of this article, the system’s resource path information is stored in the Settings structure. In fact, the executable code path is also stored in this structure, as shown in the figure below.Once we understand how the system works, we need to find the right time to change the code path and resource path saved in Settings to the specified path. Engine is then compiled to generate flutter. Jar. Engine is compiled for referenceThe official Wiki[3].

**2.2 Implementation principle **

In the implementation of the idea, we want to solve the core of the problem is to find the appropriate time. We chose the time before platform_view_android_jni.cc AttachJNI, AndroidShellHolder was created.

After recompiling the Engine, we preset the generated flutter. Jar into the main project so that the main project has the ability to dynamically load flutter code and resources.

**2.3 Workflow **

2.3.1 Compilation Phase

Android can load the normal flutter build, package the compilation products libapp.so and Flutter_assets into a resource package, and upload it to the CDN.

**2.3.2 Loading phase **

Before the loading stage, there is also a resource pack download and security verification, which will not be discussed here. When we get the complete bundle, we pass its path to the Flutter Engine layer. Android terminal is a transparent path down from Flutter. CreateView. Flutter. CreateView – > FlutterNativeView constructor – > FlutterJNI. AttachToNative – > FlutterJNI. NativeAttach – > AttachJNI, Change the default Settings of Flutter at AttachJNI of Engine layer platform_view_android_jni.cc. Set Settings’ application_LIBRary_path to libapp.so in the user-defined path and assets_path to Flutter_assets in the user-defined path. At this point, custom code and resources can be loaded onto the Android side.

**3, iOS **

3.1 Implementation Roadmap

IOS can take the same approach as Android, but because of apple’s Developer agreement, dynamic updates and executable code are not allowed; Therefore, we can adopt the same approach to Flutter resources as Android, but we need to find a new approach to Flutter code. Looking back at these cross-end schemes, we can look at the implementation of RN, except that N is no longer Native, but a Flutter. RN controls Native rendering through JS, what we want to achieve is to control Flutter rendering through JS. Do developers have to use JS to develop? Tencent TFEF-Imatrix’s open source MXFlutter[4] is a JS based dynamic framework of Flutter. It develops Flutter applications in a very similar way to Dart, by writing JavaScript code. Is it possible to be transparent to Flutter developers? On the far right, Flutter offers a powerful tool called Dart 2JS that allows Dart code to be compiled into JS. To achieve this, we also need to develop a set of frameworks that support dynamically delivered JS to control Flutter rendering, allow Flutter to transmit events, and implement all business logic of JS to achieve dynamic.

3.2 Implementation Principles

We have two things to complete, one is to modify Engine to implement custom resource loading, this part of the idea is the same as Android side, the only difference is that on iOS side only need to support custom resource loading, this part will not be repeated. The other is to implement an RN-like framework, which is relatively complex and will be described in more detail later. We determined the general workflow of this framework. The JS side carries all the business logic. By building the virtual Tree of widgets matching the business logic, the data is transferred to Flutter, and Flutter parses the UI description to build the real Widget Tree. The framework must have three parts: app.js is generated by compiling the business code with the same image class of the Flutter SDK, which we call the Client part; The Flutter side is used for UI rendering and event reception, which we call the Host part; The other part is the bridge connecting the two ends, which not only needs to assist to realize the two-way communication between JS and Flutter, but also provides some necessary mechanisms for JS side, which is called the Native part. The modified Engine is compiled into the Flutter. Framework and the Host part of the framework (app. framework), which is preset into the main project. This provides the environment to support the Flutter App in the main App. When a user starts a Flutter app, the main app will pass the path of the resource package to the Flutter Engine and start the preset app. The main App will also load app.js. After js has established communication with the Flutter, the Flutter App will run.

**3.3 iOS dynamic framework — JS2Flutter **

Here is the structure of the entire frame:

**3.3.1 Client **

3.3.1.1 Reflection of Flutter SDK Widget Components

Widget components mainly provide images of Flutter components. In order to facilitate the construction of the virtual tree of widgets, each Widget has the ability to transform data into Json. Taking MaterialButton as an example, During toJson, splashColor, height and other information will be stored in Json. When receiving the onPressed event, the Host side will transmit the event to the mirror on the Client side, and the mirror’s MaterialButton will inform the service onPressed event to be triggered.

class MaterialButton extends MapChildWidget { const MaterialButton({ Key key, this.onPressed, this.onHighlightChanged, this.textColor, ... this.minWidth, this.height, this.child, }) : super(key: key); final VoidCallback onPressed; final ValueChanged<bool> onHighlightChanged; final Color textColor; . final double minWidth; final double height; @override Map<String, Widget> children() { return {'child': child}; } @override bool shouldGenerateId() { return onPressed ! = null || onHighlightChanged ! = null; } @override void handleEvent(String action, dynamic data, int callbackId) { if (action == 'onPressed') { onPressed(); } else if (action == 'onHighlightChanged') { onHighlightChanged(data); } } @override Map<String, dynamic> toJson() { Map<String, dynamic> json = super.toJson(); if (onPressed ! = null) { json['onPressed'] = true; } if (onHighlightChanged ! = null) { json['onHighlightChanged'] = true; } if (textColor ! = null) { json['textColor'] = textColor.toJson(); }... if (minWidth ! = null) { json['minWidth'] = minWidth.toString(); } if (height ! = null) { json['height'] = height.toString(); } return json; }}Copy the code

**3.3.1.2 UI datalization **

The process of UI datafication refers to building a virtual tree on the JS side and then transferring the tree to Flutter via Json datafication. Why data UI? Our logic is controlled on the JS side, but the real drawing ability is on the Flutter side. In order to complete the JS control of the Flutter to render, we must tell the Flutter what we want to render, and this process needs to be described in Json. How to implement UI datafication? Each node has the ability to digitize itself and its subtrees, which can be digitized from the root node. For tree construction, we refer to the implementation of Flutter and provide some basic widgets according to different scenarios. Such as StatelessWidget, StatefulWidget, SingleChildWidget, NoChildWidget, DeferChildWidget, MultiChildWidget, and MapChildWidget.

** 3.3.1.3 Communication mechanism **

Communication mechanism is the cornerstone of the whole framework. The Client part is mainly two-way communication with Native. Asynchronous, synchronous, Vsync and other mechanisms should be established on the JS side, which of course needs the cooperation of Native part.

Most of the messages may not be concerned with the return result, such as informing the Flutter side to refresh UI data. However, some scenarios also need to return the result, which requires asynchronous and synchronous mechanisms. For example, if I need a showTimePicker, and I get the selected time, and the system returns a Future, then we need an asynchronous mechanism. In fact, synchronization is relative to the JS side, aiming at the JSWorkThread, which is a thread unrelated to UI drawing, so its blocking does not affect the rendering and event reception, so this synchronization is only the JS side synchronization, some methods must require synchronization mechanism to ensure correctness. For example, you want to measure the height of the text using the TextPainter. Vsync’s mechanics are primarily designed to support CustomPainter and mini-game capabilities in scenes that are drawn directly from the Canvas. In addition to these three major components, there are mechanisms such as WidgetBinding, MethodChannel, and EventChannel, which are implemented in the same way as Widget components, mirroring on the Client side and remaining in the Host part. The Client part of the code will eventually be compiled into the app.js file.

**3.3.2 Native part **

The Native XCJSRuntime creates a separate JSWorkThread and establishes a message loop via RunLoop, where app.js is loaded and executed. Through CADisplayLink, Vsync mechanism is provided for JS side, and the Client SchedulerBinding is based on this Vsync mechanism. SetTimeout and setInterval are also implemented. This is mainly to solve the problem that there is no setTimeout and setInterval on the JS side of Timer after dart2JS.

**3.3.3 Host section **

This part mainly parses the data passed by the Client, builds the real Widget Tree, and sends the event to the corresponding image of the Client after receiving the user event.

**3.3.3.1 Data parsing **

Data parsing includes two types. One is the data containing Widget information, which is mainly parsed into the corresponding Widget according to the type carried. For example, after the MaterialButton is identified as being passed, the real MaterialButton will be constructed in Host. And fill it with splashColor, height, etc. The first type is the command drawn directly by Canvas. The corresponding Canvas command can be parsed according to the protocol defined by the data, such as save, restore, Translate, rotate, and drawImageRect.

MaterialButton materialButtonCreator(Map<dynamic, dynamic> data) {
  int widgetId = data['widgetId'];
  VoidCallback onPressed;
  if (data['onPressed'] ?? false) {
    onPressed = () {
      Flutter2JSChannel.instance.sendWidgetEvent('onPressed', widgetId);
    };
  }

  ValueChanged<bool> onHighlightChanged;
  if (data['onHighlightChanged'] ?? false) {
    onHighlightChanged = (value) {
      Flutter2JSChannel.instance
          .sendWidgetEvent('onHighlightChanged', widgetId, value);
    };
  }

  return MaterialButton(
    key: newInstance(data['key']),
    child: newInstance(data['child']),
    onPressed: onPressed,
    onHighlightChanged: onHighlightChanged,
    textColor: newInstance(data['textColor']),
    ...
    minWidth: numToDouble(data['minWidth']),
    height: numToDouble(data['height']),
  );
}
Copy the code

**3.3.3.2 Communication mechanism **

The communication mechanism of Host side is mainly two-way communication with Native through MethodChannel, so as to realize the two-way communication from JS to Flutter and from Flutter to JS.

**3.4 Workflow **

**3.4.1 Compilation Phase **

With the help of dart2JS, compile the business code and the framework’s Flutter SDK image code into app.js, package it with Flutter_assets into a resource bundle, and upload it to the CDN.

3.4.2 Loading Phase

The loading is divided into two parts: one is the loading of Flutter_assets and the other is the loading of app.js. We added a setAssetsPath method to the FlutterDartProject, which specifies the assets_path of the Settings, Just specify the FlutterDartProject in the initWithName constructor of the FlutterEngine. After starting FlutterEngine, the upper layer starts loading app.js via XCJSRuntime.

**3.4.3 Operation Phase **

XCJSRuntime loads app.js in JSWorkThread, sends Widget Tree data to Native side after loading, and then passes it to Host part of framework through MethodChannel. The framework first parses the data. This completes the display of the page. When the Flutter receives an event, it sends it back to the JS side, which processes the response to the event.

**4, climb over the pit **

I have encountered many pits in the process of implementing the JS2Flutter framework. I would like to share some of my most impressive pits with you.

**4.1 Widget Tree Status Synchronization **

This is mainly for StatefulWidget. StatefulWidget is widely used and often appears in some complex scenarios. Since the most original UI description data comes from THE JS side, when the State corresponding to the StatefulWidget triggers the refresh, the JS side will rebuild the subtree. Pass to Flutter, parse the new data on the Host side, and re-render. The data structure of the whole tree is a large Map, and nodes are created by traversing from the root node. The data of StatefulWidget actually depends on the data passed to it by the parent node. This is where the problem arises. The child StatefulWidget triggers its own State refresh first, and its data has changed on the JS side. If the outer StatefulWidget triggers a build (not triggered by JS, for example, when a new page is accessed), The state of the child StatefulWidget will be refreshed to its original state because the data refresh of the child StatefulWidget itself does not synchronize the data to the entire tree structure, which is a big logical error made early in the framework.

**4.2 Delay-constructed Widget nesting StatefulWidget **

In the framework Client, we call them deferchild widgets. Most of these widgets are implemented with a StatefulWidget on the Host side. At the initState time of the occupied StatefulWidget’s State, the JS side requests the data of the subtree. After the JS side builds the data of the subtree, the JS side sends it back to the occupied StatefulWidget to refresh its State. At this time, the construction of the real subtree is triggered. In the early implementation of StatefulWidget, although there is a corresponding StatefulWidgetHost (inherited from the real StatefulWidget) on the Host side to implement a custom StatefulWidget, However, the Host side does not synchronize the actual timing to the JS side. The initState and Dispose that are called back to StatefulWidget State on the Client side of the framework are called back when they are built and destroyed from the virtual tree on the JS side. So when a DeferChildWidget is nested with a StatefulWidget, there is a timing issue if there is an asynchronous fetch in initState (e.g. Retrieve a state from a SharedPreference, update the state after the data is retrieved, and the problem is exposed when the StatefulWidget that preempted the Host triggers the build of its real subtree. In fact, there are a lot of challenging problems to implement such a framework, such as: how to draw small games efficiently? How to improve communication efficiency and so on? I won’t go into the discussion here.

**5, Conclusion **

The popularity of Flutter has been overwhelming. I believe that many developers have been trying to make Flutter dynamic. This article shares some lessons learned from the process of making Flutter dynamic, hoping to help you. The technology discussed in this article is based on this version. Gityuan’s article — In-depth Understanding of Flutter engine Startup [2] is based on the source code of Flutter1.5, with slightly different source details. The startup process is consistent.

**6. References **

[1]:Engine build Wiki github.com/flutter/flu…

[2]: Insight into Flutter engine startup gityuan.com/2019/06/22/…

[3]:Engine compiler Wiki github.com/flutter/flu…

[4] : MXFlutter github.com/TGIF-iMatri…