Redian新闻
>
Flutter如何将代码显示到界面上

Flutter如何将代码显示到界面上

公众号新闻
来源 | OSCHINA 社区
作者 | 小呆呆666
原文链接:https://my.oschina.net/xdd666/blog/8704604

前言

如何优雅的将项目中的代码,亦或是你的 demo 代码展示到界面上?本文对使用简单、便于维护且通用的解决方案,进行相关的对比和探究

为了节省大家的时间,把最终解决方案的相关接入和用法写在前面

预览代码

快速开始

  • 接入:pub,github

dependencies:
code_preview: ^0.1.5
  • 用法:CodePreview,提供需要预览的 className,可自动匹配该类对应的代码文件

    • 本来想把写法简化成传入对象,但是因为一些原因无奈放弃,改成了 className

    • 具体可以参考下面 Flutter Web中的问题模块的说明

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');
}
}
  • 使用效果:flutter_smart_dialog

配置代码文件

因为原理是遍历资源文件,所以必须将需要展示的代码文件或者其文件夹路径,定义在 assets 下,这步操作,为大家提供了一个自动化的插件解决

强烈建议需要展示到界面的代码,都放在统一的文件夹里管理

  • 展示界面的代码需要在 pugspec.yaml 中的 assets 定义

如果代码预览的文件夹,分级复杂,每次都需要定义路径实在麻烦

提供一个插件:Flutter Code Helper

  • 安装:Plugins 中搜索 Flutter Code Helper

  • pugspec.yaml 中定义下需要自动生成文件夹的路径,文件夹随便套娃,会自动帮你递归在 assets 下生成

    • 不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])

code_helper:
auto_folder: [ "assets/", "lib/widgets/" ]

说明下:上面的插件是基于 RayC 的 FlutterAssetsGenerator 插件项目改的

  • 看了下 RayC 的插件代码和相关功能,和我预想的上面功能实现有一定出入,改动起来变动较大

  • 想试下插件项目的各种新配置,直接拉到最新

  • 后期如果想到需要什么功能,方便随时添加

所以没向其插件里面提 pr,就单独新开了个插件项目

高级使用

主题

提供俩种代码样式主题

  • 日间模式

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

  • 夜间模式

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

注释解析

  • 你可以使用如下的格式,在类上添加注释

    • key 的前面必须加 @,举例(@title,@xxx)

    • key 与 value 的之间,必须使用分号分割,举例(@xxx: xxx)

    • value 如果需要换行,换行的文案前必须加中划线

/// @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();
}
}
  • 然后可以从 customBuilder 的回调获取 param 参数,param 中拥有 parseParam 参数

    • 获取取得上面注释的数据:param.parseParam ['title'] 或者 param.parseParam ['***']

    • 获取的 value 的类型是 List<String>,可兼容多行 value 的类型

  • customBuilder 的用法

    • codeWidget 内置的代码预览布局,如果你想定义自己预览代码的布局,那就可以不使用 codeWidget

    • 一般来说,可以根据注释获取的数据,结合 codeWidget 嵌套来自定义符合要求的布局

    • param 中含有多个有用内容,可自行查看

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;
},
);
}
}
  • 目前内部预览的布局,会自动去掉类上的注释,如果想保留注释,可自行匹配下

 CodePreview.config = CodePreviewConfig(removeParseComment: false);

几种代码预览方案

FlutterUnit 方案

  • https://github.com/toly1994328/FlutterUnit

FlutterUnit 项目也是自带代码预览方案,这套方案是比较特殊方案

  • 大概看了下,整个 FlutterUnit 的数据都是基于 flutter.db,该文件里面就有相关 demo 的文本信息

  • 所有的 demo 也是单独存在一个叫 widgets 的项目中

  • 所以大概可以猜测出

    • 应该会有个 db 的辅助工具,会去扫描 widgets 的项目中的 demo 代码

    • 将他们的文本信息都扫描出来,然后解析上面的注释等相关信息,分类存储到数据库中,最后生成 db 文件

  • 映射表,宿主可以通过 db 中的组件类名,从这里拿到 demo 效果实例

总结

整套流程看下来,实现起来的工作量还是有点大的

  • db 辅助工具的编写

  • 文本注释相关解析规则

  • 如何便捷的维护 db 文件(辅助工具是否支持,生成后自动覆盖宿主 db 文件)

  • 不同平台 db 文件的读取和相关适配

优点

  • 因为扫描工具不依赖 Flutter 相关库,预览方案可以快速的移植到其它编程语言(compose,SwiftUI 等)

  • 具备高度自定义,因为是完全独立的第三方扫描工具,可以随性所欲的定制化

缺点

  • 最明显的缺点,应该就是稍微改下 demo 代码,就需要三方工具重新生成 db 文件(如果三方工具实现的是 cli 工具,可以将扫描生成命令和 push 等命令集成一起,应该可以比较好的避免该问题)

build_runner 方案

  • https://pub.dev/packages/build_runner

build_runner 是个强大代码自动生成工具,根据 ast 语法树 + 自定义注解信息,可以生成很多强大的附属代码信息,例如 json_serializable 等库

所以,也能利用这点自定义类注解,获取到对应的整个类的代码信息,在对应附属的 xx.g.dart 文件中,将获取的代码内容转换成字符串,然后直接将 xx.g.dart 文件的代码字符串信息,展示到界面就行了

优点

  • 可以通过生成命令,全自动的生成代码,甚至将整个预览 demo 的映射表都可以自动配置完成

  • 可以规范的通过注解配置多个参数

缺点

  • 因为 build_runner 需要解析整个 ast 语法树,一旦项目很大之后,解析生成的时间会非常非常的长!

  • 因为现在很多的这类库都是依赖 build_runner,所以跑自动生成命令,会导致巨多 xx.g.dart 文件被改动,极大的增加 cr 工作量

资源文件方案

这应该最常用的一种方案

  • 在 pubspec.yaml 中的 assets 中定义下我们代码文件路径

flutter:
assets:
- lib/widgets/show/
  • 然后用 loadString 获取文件内容

final code = await rootBundle.loadString('lib/widgets/show/custome_dialog_animation.dart');

优点

  • 侵入性非常低,不会像 build_runnner 方案那样影响到其它模块

  • 便于维护,如果 demo 预览代码被改变了,打包的时候,资源文件也会生成对应改变后的代码文件

缺点

  • 使用麻烦,使用的时候需要传入具体的文件路径,才能找到想要的代码资源文件

  • 需要反复的在 pubspec.yaml 中的 assets 里面定义文件路径

资源文件方案优化

上面的三种方案各有优缺点,明确当前的诉求

  • 目前是想写个简单的,通用的,仅在 Flutter 中实现代码预览方案

  • 要求使用简单,高效

  • 维护简单,多人开发的时候不会有很大成本

FlutterUnit 方案:实现起来成本较大,且多人开发对单个 db 文件的维护很可能会有点问题,例如:更新代码的时候,db 文件忘记更新

build_runner 方案:生成时间是个问题,还有很对其他类型 xx.g.dart 文件产生影响也比较麻烦

资源文件方案:整体是符合预期的,但是使用时候,需要传入路径和 pubspec.yaml 中反复定义文件路径,这是俩个很大痛点

结合实现成本和诉求,选择资源文件方案,下面对其痛点进行优化

使用优化

Flutter 的编译产物中,有个相当有用的文件:AssetManifest.json

AssetManifest.json 文件里面,有所有的资源文件的路径,然后就简单了,我们只需要读取该文件内容

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

获取到所有的路径之后,再结合传入的类名,读取所有路径的文件内容,然后和传入的类名做正则匹配就行了

稍微优化

  • 将传入的类名,转换为下划线名称和所有路径名称做匹配,如果能匹配上,再进行内容匹配,匹配成功后就返回该文件的代码内容

  • 如果上述匹配失败,就进行兜底的全量匹配

优化前

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');
}
}

优化后

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');
}
}
  • 一般来说,我是统一配置预览 demo 和 className,这样比较好对照

路径定义优化

本来是想在 pubspec.yaml 的 assets 里面直接写通配符定义全路径,然后悲剧了,它不支持这种写法

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

GG,只能想其他办法了,想了很多方法都不行,只能从外部入手,用 idea 插件的形式,实现自动化扫描生成路径

  • 安装:Plugins 中搜索 Flutter Code Helper

  • pugspec.yaml 中定义下需要自动生成文件夹的目录,文件夹随便套娃,会自动帮你递归在 assets 下生成

    • 不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])

code_helper:
auto_folder: [ "assets/", "lib/widgets/" ]

Flutter Web 中的问题

魔幻的 runtimeType

flutter web 的 release 模式中

  • dart2js 会压缩 JS,这样会使得类型名被改变

  • 例如:dart 中的 TestWidgetFunction 类的 runtimeType,可能会变成 minified:Ah,而不是 TestWidgetFunction

为啥需要压缩呢?压缩名称可以使得编译器将 JavaScript 体积缩小 3 倍 +;精确等效语义和性能 / 代码大小之间的权衡,Dart 明显是选择了后者

这种情况只会在 Flutter Web 的 release 模式下发生,其他平台和 Flutter web 的 Debug | Profile 模式都不会有这种问题;所以说 Xxx.runtimeType.toString,并不一定会得到预期内的数据。。。

具体讨论可参考

  • https://github.com/dart-lang/sdk/issues/35052

  • https://github.com/flutter/flutter/issues/78041

解决思路

  • 将压缩类型 minified:Ah 恢复成 Test

  • 将获取的 Test 字符串使用相同算法压缩成 minified:Ah

如有知道如何实现的,务必告诉鄙人

下面从压缩级别调整的角度,探究是否可解决该问题

dart2js 压缩说明

注:flutter build web 默认的是 O4 优化级别

  • O0: 禁用许多优化。

  • O1: 启用默认优化 (仅是 dart2js 该命令的默认级别)

  • O2: 在 O1 优化基础上,尊重语言语义且对所有程序安全的其他优化(例如缩小)

    • 备注:使用 - O2, 使用开发 JavaScript 编译器编译时,类型的字符串表示不再与 Dart VM 中的字符串表示相同

  • O3: 在 O2 优化基础上,并省略隐式类型检查。

    • 注意:省略类型检查可能会导致应用程序因类型错误而崩溃

  • O4: 在 O3 优化基础上,启用更积极的优化

    • 注意:O4 优化容易受到输入数据变化的影响,在依赖 O4 之前,需测试用户输入中的边缘情况

下面是 flutter 新建项目,未做任何改动,不同压缩级别的 js 产物体积

# 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

总结

  • 预期用法

    • 为什么想使用对象?因为当对象名称改变时,对应使用的地方,可以便捷观察到需要改变

    • 可以使用传入的对象实例,在内部使用 runtimeType 获取类型名,再进行相关匹配

CodePreview(code: Test());

但是

综上可知,使用 flutter build web --dart2js-optimization O1 编译的 flutter web release 产物,能够使得 runtimeType 的语义和 Dart VM 中字符串保持一致

但是该压缩级别下的,js 体积过于夸张,务必会对加载速度产生极大影响,可想而知,在复杂项目中的体积增涨肯定更加离谱

对于想要用法更加简单,使用低级别压缩命令打包的想法需要舍弃

  • 用法不得已做妥协

CodePreview(className: "Test");

这是个让我非常纠结的思路历程

最后

到这里也结束了,自我感觉,对大家应该能有一些帮助

一般来说,大部分团队,都会有个自己的内部组件库,因为 Flutter 强大的跨平台特性,所以就能很轻松的发布到 web 平台,可以方便的体验各种组件的效果,结合文章中的代码预览方案,就可以更加快速的上手各种组件用法了~


END



ChatGPT火了,一大批开源平替也来了


🌟 活动推荐


2023 年 5 月 27-28 日,GOTC 2023 全球开源技术峰会将在上海张江科学会堂隆重举行。

为期 2 天的开源行业盛会,将以行业展览、主题发言、特别论坛、分论坛、快闪演讲的形式来诠释此次大会主题 ——“Open Source, Into the Future”。与会者将一起探讨元宇宙、3D 与游戏、eBPF、Web3.0、区块链等热门技术主题,以及 OSPO、汽车软件、AIGC、开源教育培训、云原生、信创等热门话题,探讨开源未来,助力开源发展。

长按识别下方二维码立即查看 GOTC 2023 详情/报名。

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Amid Twitter Chaos, Japanese Illustrators Flock to WeiboFlutter异步编程指南市值百亿、年收入近13亿元,最强虚拟偶像公司Anycolor如何发财的[干货] 一张图让你明白 cut, cut off, cut up 的区别~​比亚迪智能手表预计4月上市;李开复正在筹组AI2.0公司;Twitter将于3月31日开源所有用于推文推荐的代码……浪漫的故事,使一首歌曲《月亮河》成为经典马斯克:Twitter将于3月31日开源所有推荐机制代码Logstash、Fluentd、Fluent Bit 和 Vector,谁才是开源日志收集最强王者?这两种饮食可能会降低患老年痴呆症的风险 -满德(MIND) 和地中海饮食与大脑健康有关揭秘 ChatGPT 背后的技术栈:OpenAI 如何将 Kubernetes 扩展到了 7500 个节点硬核观察 #958 Twitter 公开推荐算法源代码,马斯克获特别优待一坛老酒醉清风 (ZT)【本周折扣汇总】Ocado超市75折!NIKE/Hollister/Lululemon最低4折!资生堂半价!Twitter部分源代码泄漏、疑遭离职员工报复:马斯克要求GitHub交出所有上传、下载人员名单iOS 17系统界面再曝,Final Cut Pro将上线iPad ProHow a Forgotten Park Birthed a RevolutionHotelFT 更新:新增 Hyatt Privé, Marriott STARS & Luminous 查询地图和更多详情硬核观察 #957 Twitter 要求 GitHub 披露其源代码上传者的身份首开!Deloitte (US) 开放2026 Winter Internship‘Teenager Mode’ in Video Apps Has Flaws, Authorities FindPAB纸盒将代替塑料杯!看看到底哪种更好?山东“历史事件”探究之五周末厨房丨【印式奶油鸡块】 Butter Chicken (Murgh Makhni)开源设计系统 PatternFly 的 5 个最佳实践 | Linux 中国奋斗半辈子,结果老了Flutter热更新技术探索现代移动开发哪家强:原生还是跨平台?JetBrains 专家:我选 Flutter[败家] 反季入坑单板,Flow NX2 carbon 及 Flux GX 固定器有内鬼? Twitter部分源代码在网上泄露他将代表国民党参选2024四大卷王 | Deloitte 率先开启2026 Winter InternshipiPhone 8 Plus white 256g battery health 100%资本家再出手,内部文件显示,马斯克将 Twitter 的育儿假从 20 周缩短至 2 周maxsun GeForce GTX 1660 Super Terminator Computer Video Graphics任正非发文重申「华为不造车」;意大利宣布禁止使用 ChatGPT;Twitter 对外披露部分源代码 | 极客早知道
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。