Thoughts and practice on dynamic thermal renewal of Flutter

Thoughts and Practice on Flutter dynamic thermal renewal (II) —-Dart code conversion AST

Thoughts and Practice on Flutter dynamic thermal renewal (iii) —- analysis of AST Runtime

Thoughts and Practice on Flutter dynamic thermal renewal (IV) —- analysis of AST widgets

This article focuses on how to call another AST dynamic code

Problem 1.

If you now have two custom widgets, A and B, both of which need to be dynamically processed, and B is included in A as A child Widget in the Widget tree, how do you resolve B Widget from A Widget after the AST is converted? Similarly, customizing A class C requires dynamic processing and calling C methods in A Widget. How do you parse C and execute the methods in C?

2. Solution

First, you can modify the STRUCTURE of the AST. By designing an AST Node structure, you can define the code after transforming the AST. Then during parsing, according to the structure information, you can get the AST complete data and throw it into the Runtime mentioned above.

2.1 Designing the AST Node Structure

Define an AST Node data format as follows:

{
	"type": "AstClass"."classId": "5942760a663c195888a64e29476ac103"."name": "DemoWidget"."version": "9fc96a1bfc60567bab9f7038175919af"
}
Copy the code

“ClassId” and “version” :

  • ClassId identifies an AST dynamic code, and the algorithm is to takeMd5 ({code file path} + {class name}), basically uniquely identifies code classes in a project.
  • Version indicates the version of the codeMd5 ({contents of code class}), version changes whenever there is a change in the code class, mainly used in version rollback.

2.2 Generating AstClass Nodes

Let’s start with the previous problem as an example of how to generate an AST to associate A and B.

The problem scenario described above is that A uses B, so we create A AstClass Node for B. The idea is to use AstVisitor method visitClassDeclaration to read the class content and cache the processed data. The next step needs to use:

@override
Map visitClassDeclaration(ClassDeclaration node) {
    var name = node.name.name;
    var source = node.toSource();
    var version = hex.encode(md5.convert(Utf8Encoder().convert(source)).bytes);
    var classId = hex.encode(
        md5.convert(Utf8Encoder().convert(filePath + '/' + name)).bytes);

    return {
      "name": name,
      "path": filePath,
      'version': version,
      'classId': classId,
    };
}
Copy the code

The “filePath” field is the path of the current file that is passed in from outside.

To associate A and B, you need to do something in the template code, such as:

/// Associate the template AstClass object
Widget selectWidget<T extends Widget>(T w) => w;

///A:. B b; selectWidget<B>(b), ...Copy the code

The invocation of “selectWidget” in the AstVisitor invocation is generated. The invocation of “selectWidget” in the AstVisitor invocation is generated. Select * from AstClass Node where the “import” declaration path matches the AstClass Node information in the previous step.

///import dependent paths
List<String> _importPathes = [];

// get the path of the import in the current code
@override
Map visitImportDirective(ImportDirective node) {
  if (_isDebug) {
    stdout.writeln("visitImportDirective => ${node.toString()}");
  }
  var importPath = ' ';
  var uri = Uri.parse(node.uri.stringValue);
  if (uri.scheme == 'package') {
    var projectName = uri.pathSegments[0];
    if (projectName == _projectName) {
      for (var i = 1; i < uri.pathSegments.length; i++) {
        importPath += uri.pathSegments[i] + '/'; } _importPathes.add(importPath); }}else {
    importPath = uri.path;
    var currentPath = _filePath.substring(0, _filePath.lastIndexOf('/') + 1);
    _importPathes.add(currentPath + importPath);
  }
  return null;
}

@override
Map visitMethodInvocation(MethodInvocation node) {
  Map callee;

  if (node.methodName.name == 'selectWidget') {
    // Find the associated AST class
    if(node.typeArguments? .arguments? .isNotEmpty ==true) {
      var astClassName =
        _safelyVisitNode(node.typeArguments.arguments[0[])'name'];
      // Find ast class files through import traversal
      for (var p in _importPathes) {
        var f1 = path.relative(p);
        for (var node in _astClassNodes) {
          var f2 = path.relative(node.filePath);
          if (f1 == f2 && astClassName == node.name) {
            // Find the matching AST class records in the project and build the AstClass Node
            callee = {
              'type': 'AstClass'.'classId': annotation.classId,
              'version': annotation.version,
              'name': annotation.name, }; }}}}else {
      throw "Error: selectWidget needs to define a paradigm"; }}return {
        "type": "MethodInvocation"."callee": callee,
        "typeArguments": _safelyVisitNode(node.typeArguments),
        "argumentList":  _safelyVisitNode(node.argumentList)
       };
}
Copy the code

2.3 Storing AstClass Information

The AST data generated by each traversal of the code file needs to be stored and correlated by the classId field. The storage can be a network server, or a local file, or a local database (such as Sqlite) without going into details.

2.4 Parsing AstClass Nodes

The MethodInvocation Node is embedded in the “MethodInvocation” Node. When the “MethodInvocation” Node is resolved, check whether the “type” is “AstClass”. Retrieve the “classId” and “version” fields and extract the complete AST data from the AST storage data source based on these two fields.

3. Summary

The solution to this problem is relatively simple, the code has no technical content, the details of the code is not posted. So far, the process and technical principle of the whole Flutter dynamic are basically clear. There are no obvious technical obstacles, and the scheme is feasible. The remaining work is to perfect the details to make it applicable to the production environment.

Several months have passed since the last article. In fact, in these months, apart from dealing with the company’s affairs, we mainly focus on improving the details of this plan. So far, we have realized a relatively complete version. Including template code -> generate AST -> upload server -> App synchronization AST content dynamic update -> performance monitoring, abnormal monitoring the whole process. At the same time we also implemented a company internal use App visual editing tool, for use in products and UI designers, product or UI designers through the tool can be edited page template code automatically sent to the research and development, the development of docking after data interface can be directly generated AST and published to the server, save most write a template code work. The tool is still in the Beta stage, and I will share the implementation process and principle with you when it is mature and stable.

Welcome to exchange and learn about simplifying App development