JSONFormat4Flutter 是一个通过输入 json 字符串,生成可以在 Flutter 项目中使用(其实不止)的 dart 语言实体类代码的 GUI 工具。初版编写于2018年1月,由于当时还没有好用的同类工具,所以按照个人的偏好和理解以相对简单粗暴的方式编写了该工具,代码质量惨不忍睹,万万没想到会持续更新到今天。。。

迁移到空安全

参考 dart 2.9 打开空安全后全错了,有计划兼容嘛? #30,在 Flutter 升级到2.0之后,默认开启空安全语法检查(参考:迁移Flutter项目到空安全的血泪史),这个工具生成的 json 代码全部都报错了,主要问题如下:

  1. 实例的变量需要在声明时初始化,或者在类的构造方法中进行初始化;
  2. 类的工厂方法不允许返回 null;
  3. 复杂逻辑下的可达性分析和空安全类型提升并不完美(例如对数组字段生成的赋值代码,循环中的 list 对象逻辑上不会为 null,但是语法检查器还是会将其判定为不安全的)

例如:
error

针对第一个问题,修改后生成的代码所有字段的类型均设置为可空。为什么不是保持字段类型为非空呢,参考:Map 的索引操作符是可空的,由于从 json 对象中用索引操作符方式取值时,其返回值就是可空的,而且事实上由于我们无法保证 json 字符串输入的可靠性,字段为可空是更合理的设计,在使用时总应考虑字段为空时应该如何处理,这样才能避免由于后台接口传来的数据问题导致程序出错。

第二个问题让我头疼了很久。在最初的设计中,为了方便使用,我为生成的模板类添加了工厂构造函数,这样就可以直接将 json 字符串或 json 对象传递给其构造函数来快速创建实体对象,如:

1
2
var response = await HTTP.get(url);
var resp = BeanResp(response.body);

通过在构造函数中对输入值的判断,如果输入为空(如null、’’、’null’)时,返回的 response 即为 null。但是新的空安全语法中,构造函数不允许返回 null,这就使得设计变得不可行(参考:我应该如何迁移可能返回 null 的工厂方法?)。最终我是新增了 parse(jsonStr) 静态方法,用于替代之前的工厂构造函数,推荐在项目中优先使用该方法进行解析,也就是说上面的代码可以写成:

1
2
var response = await HTTP.get(url);
var resp = BeanResp.parse(response.body);

而且这样以来也有一个好处,就是在对网络请求框架进行封装的时候,参考我在在Flutter开发过程中快速生成json解析模板类的工具 | 掘金技术征文中给出的示例,解析处的代码需要写成:

1
2
3
4
var response = await HTTP.get(url);
Person data = BaseResp<Person>(response.body, (res) => Person.fromJson(res)).data;
///或者
Phone data = BaseResp<Phone>(response.body, (res) => Phone.fromJson(res)).data;

现在在基类代码基本不变的情况下,代码可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:convert' show json;

class BaseResp<T> {
int? code;
String? msg;
T? data;

factory BaseResp(jsonStr, Function buildFun) =>
jsonStr is String ? BaseResp.fromJson(json.decode(jsonStr), buildFun) : BaseResp.fromJson(jsonStr, buildFun);

BaseResp.fromJson(jsonRes, Function buildFun) {
code = jsonRes['code'];
msg = jsonRes['msg'];
/// 这里可以做code和msg的处理逻辑
data = buildFun(jsonRes['data']);
}
}

解析处的代码可以写成:

1
2
3
4
var response = await HTTP.get(url);
Person data = BaseResp<Person>(response.body, Person.parse).data;
///或者
Phone data = BaseResp<Phone>(response.body, Phone.parse).data;

代码相对精简了一点

使用新版本生成的上面报错类的代码如下:
ok

使用 nuitka3 打包 Linux 下的二进制程序为 AppImage

参看:0.8无linux版本下载 #29,一开始选择使用 PyQt 写这个工具,就是看中它的跨平台能力,可以方便地使用 Pyinstaller 进行三个桌面平台打包发布(当然如果是现在,可能 Electron 或者 Flutter for Desktop 会是更好的选择)。但是后面发现,使用 Pyinstaller 打包的方案在 Windows 和 MacOS 上表现还算良好,而在 Linux 平台下,不光打出的包要大很多,而且兼容性很有问题,在比较新的系统上进行打包,放到相对老一点的系统大概率打不开。

于是现在在 Linux 平台下改用 nuitka3 打包为 AppImage 格式。在我的 Deepin Linux 下安装配置和打包的操作如下:

1
2
3
4
5
6
7
8
# 安装 nuitka3
pip3 install nuitka3

# 安装编译所需的依赖
sudo apt install clang python3-dev chrpath

# 打包为 AppImage
nuitka3 --clang --standalone --windows-disable-console --linux-onefile-icon=logo.png --output-dir=output --show-progress --plugin-enable=qt-plugins --onefile formatter.py

参考:Nuitka Release 0.6.10

Added experimental support for Onefile mode with –onefile that uses AppImage on Linux and our own bootstrap binary on Windows. Other platforms are not supported at this time. With this, the standalone folder is packed into a single binary. The Windows variant currently doesn’t yet do any compression yet, but the Linux one does.

这个版本开始可以通过加入 --onefile 参数,在 Linux 平台下使用 AppImage 打包~

全平台都可以直接双击运行工具了👏

得益于多数 Linux 桌面对 AppImage 的支持,再来本人前段时间不得已订阅了 Apple 开发者账号,现在三个桌面平台运行程序都不用再输入命令了:
linux:
直接双击运行 formatter_linux.AppImage

mac:
双击解压 formatter_mac.zip 后即可双击 formatter.app

windows:
直接双击运行 formatter_win.exe