How Flutter displays code on the interface

foreword

How to elegantly display the code in the project or your demo code on the interface? This article compares and explores the solutions that are easy to use, easy to maintain and general

In order to save everyone's time, write the relevant access and usage of the final solution in front

preview code

quick start

dependencies:
  code_preview: ^0.1.5
  • Usage: CodePreview, provide the className that needs to be previewed, and can automatically match the corresponding code file of the class
    • I originally wanted to simplify the writing method into the incoming object, but for some reasons I had no choice but to give up and changed it toclassName
    • For details, please refer to Flutter Web中的问题the description of the following modules
import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CodePreview(className: 'Test');
  }
}

image-20230429215042820

configuration code file

Because the principle is to traverse resource files, the code files or their folder paths that need to be displayed must be defined under assets. This step provides an automated plug-in solution for everyone.

It is strongly recommended that the code that needs to be displayed on the interface should be managed in a unified folder

  • The code to display the interface needs to be defined in assets in pugspec.yaml

image-20230422224011359

If the code preview folder is complicated in classification, it is troublesome to define the path every time

Provide a plugin: Flutter Code Helper

  • Installation: Search in PluginsFlutter Code Helper

image-20230422225244651

  • Define the path of the folder that needs to be automatically generated in pugspec.yaml, and the folder will be automatically generated under assets recursively for you.
    • It does not need to be automatically generated, you can: do not write this configuration, or configure an empty array (auto_folder: [])
code_helper:
  auto_folder: [ "assets/", "lib/widgets/" ]

Apr-09-2023 22-33-42

Explanation: The above plug-in is based on RayC 's FlutterAssetsGenerator plug-in project.

  • After looking at RayC’s plug-in code and related functions, there is a certain discrepancy with the realization of the above functions I expected, and the changes will change a lot
  • Want to try various new configurations of the plug-in project, just pull to the latest
  • If you think of any functions you need later, you can add them at any time

So I didn't mention pr to its plugin, so I opened a new plugin project separately

advanced use

theme

Provides two code style themes

  • day mode
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.light);

image-20230429215716043

  • night mode
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.dark);

image-20230429215545723

Annotation parsing

  • You can use the following format to add comments on the class
    • Must be added in front of the key @, for example ( @title , @xxx)
    • Between key and value, you must use 分号segmentation, for example ( @xxx : xxx)
    • If the value needs to be changed, it must be added before the copy of the newline中划线
/// @title:
///  - test title one
///  - test title two
/// @content: test content
/// @description: test description
class OneWidget extends StatelessWidget {
  const OneWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
  • Then you can customBuilderget the param parameter from the callback, and the param has the parseParam parameter
    • Get the data of the above comment: param.parseParam['title'] or param.parseParam['***']
    • The type of value obtained is List<String>, which is compatible with the type of multi-line value
  • customBuilderusage of
    • codeWidgetBuilt-in code preview layout, if you want to define your own preview code layout, you don't need to use itcodeWidget
    • codeWidgetGenerally speaking, you can customize the layout that meets the requirements based on the data obtained from the annotation, combined with nesting
    • paramThere are a lot of useful content in it, you can check it yourself
import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CodePreview(
      className: 'OneWidget',
      customBuilder: (Widget codeWidget, CustomParam? param) {
        debugPrint(param?.parseParam['title'].toString());
        debugPrint(param?.parseParam['content'].toString());
        debugPrint(param?.parseParam['description'].toString());
        return codeWidget;
      },
    );
  }
}
  • The current layout of the internal preview will automatically remove the annotations on the class. If you want to keep the annotations, you can match them yourself
 CodePreview.config = CodePreviewConfig(removeParseComment: false);

Several code preview schemes

FlutterUnit scheme

The FlutterUnit project also comes with a code preview solution, which is a special solution

  • After roughly looking at it, the entire FlutterUnit data is based on flutter.dbthe text information of the relevant demo in this file
  • All demos also exist separately widgetsin a project called
  • So it can probably be guessed
    • There should be a db auxiliary tool that will scan widgetsthe demo code in the project
    • Scan their text information, then parse the above comments and other relevant information, store them in the database by category, and finally generate a db file

image-20230429172832212

  • Mapping table, the host can get the demo effect instance from here through the component class name in db

image-20230429175714400

Summarize

Looking at the whole process, the workload to realize it is still a bit large

  • Writing of db auxiliary tools
  • Text annotation related parsing rules
  • How to maintain the db file conveniently (whether the auxiliary tool supports it, and automatically overwrite the host db file after generation)
  • Reading and related adaptation of db files on different platforms

advantage

  • Because the scanning tool does not depend on Flutter-related libraries, the preview solution can be quickly ported to other programming languages ​​(compose, SwiftUI, etc.)
  • Highly customizable, because it is a completely independent third-party scanning tool that can be customized as you like

shortcoming

  • The most obvious shortcoming should be that after slightly changing the demo code, a third-party tool is required to regenerate the db file (if the third-party tool implements the cli tool, you can integrate the scan generation command with push and other commands, which should be better to avoid this problem. question)

build_runner scheme

build_runner is a powerful automatic code generation tool. According to the ast syntax tree + custom annotation information, it can generate a lot of powerful auxiliary code information, such as json_serializablewaiting for the library

Therefore, you can also use this custom class annotation to obtain the code information of the corresponding entire class, convert the xx.g.dartobtained code content into a string in the corresponding attached file, and then directly display xx.g.dartthe code string information of the file Just go to the interface

advantage

  • It can generate commands, fully automatic code generation, and even automatically configure the mapping table of the entire preview demo.
  • Multiple parameters can be configured through annotations in a standardized manner

shortcoming

  • Because build_runnerthe entire ast syntax tree needs to be parsed, once the project is very large, the parsing and generation time will be very, very long!
  • Because many of these libraries are now dependent build_runner, running the automatic generation command will cause a huge number of xx.g.dartfiles to be changed, which will greatly increase the workload of cr

resource file scheme

This should be the most common solution

  • Define our code file pubspec.yamlpath inassets
flutter:
  assets:
    - lib/widgets/show/
  • Then use loadString to get the file content
final code = await rootBundle.loadString('lib/widgets/show/custome_dialog_animation.dart');

image-20230429205530817

advantage

  • Very low intrusion, will not build_runnneraffect other modules like the solution
  • Easy to maintain, if the demo preview code is changed, when packaging, the resource file will also generate the corresponding changed code file

shortcoming

  • It is troublesome to use. When using it, you need to pass in a specific file path to find the desired code resource file.
  • It is necessary to repeatedly define the file path in pubspec.yamltheassets

Resource file scheme optimization

The above three schemes have their own advantages and disadvantages.当前的诉求

  • At present, I want to write a simple, general-purpose code preview solution only in Flutter

  • Simple to use and efficient

  • Simple maintenance, no big cost when multiple people develop

FlutterUnit solution : It is costly to implement, and the maintenance of a single db file by multiple developers may be a bit problematic, for example: when updating the code, the db file is forgotten to be updated

build_runner scheme : generation time is a problem, and it is xx.g.dartalso troublesome to affect other types of files

Resource file scheme : the overall is in line with expectations, but when using it, you need to pass in the path and pubspec.yamldefine the file path repeatedly, which are two big pain points

Combined with the implementation cost and appeal, choose 资源文件方案, and optimize its pain points below

use optimization

Among the compiled products of Flutter, there is a very useful file: AssetManifest.json

In the AssetManifest.json file, there are paths of all resource files, and then it is simple, we only need to read the content of the file

final manifestContent = await rootBundle.loadString('AssetManifest.json');

After all the paths are obtained, combined with the class name passed in, read the file content of all paths, and then do a regular match with the class name passed in

slightly optimized

  • Convert the incoming class name to an underscore name and match it with all path names. If it can be matched, then perform content matching. After the match is successful, return the code content of the file
  • If the above matching fails, the full amount of matching will be carried out

before optimization

import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CodePreview(path: 'lib/widgets/show/custome_dialog_animation.dart');
  }
}

Optimized

import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CodePreview(className: 'CustomDialogAnimation');
  }
}
  • Generally speaking, I uniformly configure the preview demo and className, which is better for comparison

image-20230429170007279

Path definition optimization

Originally, I wanted pubspec.yamlto assetswrite wildcards to define the full path directly, but unfortunately, it does not support this way of writing

flutter:
  assets:
    - lib/widgets/**/*.dart

GG, I can only think of other ways. I can’t think of many ways. I can only start from the outside and use the idea plug-in to realize the automatic scan generation path.

  • Installation: Search in PluginsFlutter Code Helper

image-20230422225244651

  • In pugspec.yaml, define the directory that needs to automatically generate the folder. The folder can be nested at will, and it will be automatically recursively generated under assets for you.
    • It does not need to be automatically generated, you can: do not write this configuration, or configure an empty array (auto_folder: [])
code_helper:
  auto_folder: [ "assets/", "lib/widgets/" ]

Apr-09-2023 22-33-42

Issues in Flutter Web

Magic runtimeType

In the release mode of flutter web

  • dart2js will compress JS, which will cause the type name to be changed
  • For example: the runtimeType of a class in dart TestWidgetFunctionmay become minified:Ah, instead of TestWidgetFunction!

Why do you need compression? Compressing the name allows the compiler to reduce the size of JavaScript by 3 times +; the trade-off between precise equivalent semantics and performance/code size, Dart obviously chooses the latter

This situation will only happen in the release mode of Flutter Web. Other platforms and the Debug | Profile mode of Flutter web will not have this problem; therefore, the Xxx.runtimeType.toStringexpected data may not be obtained. . .

For specific discussion, please refer to

Solutions

  • minified:AhRevert the compression type toTest
  • TestUse the same algorithm to compress the obtained string intominified:Ah

If you know how to achieve it, be sure to tell me

Let's explore whether this problem can be solved from the perspective of compression level adjustment

dart2js compression instructions

Note: flutter build web defaults to O4 optimization level

  • O0: Disable many optimizations.
  • O1: Enable default optimization (only the default level of dart2js command)
  • O2: Based on the O1 optimization, other optimizations that respect language semantics and are safe for all programs (such as minification)
    • Remark: With -O2, when compiling with the development JavaScript compiler, the string representation of the type is no longer the same as in the Dart VM
  • O3: On the basis of O2 optimization, and omit implicit type checking.
    • Note: Omitting type checking may cause the application to crash with type errors
  • O4: On the basis of O3 optimization, enable more aggressive optimization
    • Note: O4 optimization is susceptible to changes in input data, test edge cases in user input before relying on O4

The following is a new project of flutter, without any changes, the js product volume of different compression levels

# main.dart.js: 7.379MB
flutter build web --dart2js-optimization O0 
# main.dart.js: 5.073MB
flutter build web --dart2js-optimization O1
# main.dart.js: 1.776MB
flutter build web --dart2js-optimization O2
# main.dart.js: 1.716MB
flutter build web --dart2js-optimization O3
# main.dart.js: 1.687MB
flutter build web --dart2js-optimization O4

Summarize

  • intended usage
    • Why would you want to use objects? Because when the name of the object changes, it can be easily observed that it needs to be changed corresponding to the place where it is used
    • You can use the incoming object instance, internally use runtimeType to get the type name, and then perform related matching
CodePreview(code: Test());

but

In summary, using flutter build web --dart2js-optimization O1the compiled flutter web release product can make the semantics of runtimeType consistent with the strings in Dart VM

However, under this compression level, the js volume is too exaggerated, which will definitely have a great impact on the loading speed. It is conceivable that the volume increase in complex projects must be even more outrageous

For simpler usage, the idea of ​​using low-level compression commands to package needs to be discarded

  • usage has to compromise
CodePreview(className: "Test");

This is a process of thinking that confuses me very much.

at last

It's over here, I feel that it should be of some help to everyone

Generally speaking, most teams will have their own internal component library. Because of Flutter’s powerful cross-platform features, it can be easily published to the web platform, and you can easily experience the effects of various components. With the code preview solution, you can get started with the usage of various components more quickly~

Well, see you next time, pretty boys!

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/xdd666/blog/8704604