
最近在着手开发我的 《匠心星问》 ,它定位是一款 题库 应用,将集题目浏览、发布、解答、做题为一体。打算第一步先以 Flutter 为核心,准备题库资源。于是诞生《每日一题》 系列,准备精心设计一些 Flutter 的问题与解答,作为题库的养料。
接下来的几题,将从一个需求逐步演进,让答题者给出方案设计。上一题已经解析完了 Flutter SDK 中的 所有 Widget
派生类数据信息,并导出了 json 文件。数据资源已经放在 github:StarQA_Issues 中了。 本题将更进一步,焦点是探讨:
将收集的所有 Widget 组件基本信息,录入 sqlite 数据库
通过关系型数据库,入库之后,便于我们更好地进行统计分析。比如本题解答完后,就可以基于数据库查询,得到下面的统计学习,结合 Flutter 的视图表现,可以更好的展示和传播。

1. 题目描述
对于已有的 widget.json
, 其中内容是下面单体构成的列表。请你设计数据库表,将 json 数据录入数据库。并支持完成如下几个功能,给出对应的 sql 语句:
- 查询指定父类名称下,所有的子组件信息。
- 统计 Flutter SDK 中所有父类型组件有哪些。
- 支持过滤抽象类、私有类。
- 支持根据组件名模糊搜索。
json
{
"name": "ProgressIndicator",
"path": "/src/material/progress_indicator.dart",
"abstract": true,
"parents": [
"StatefulWidget",
"Widget"
],
"desc": " A base class for Material Design progress indicators."
},
2. 数据库设计
将 json 数据中的信息拆分成两张表:
widgets
表: 记录组件的基本信息。
sql
CREATE TABLE widgets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
path TEXT NOT NULL,
is_abstract INTEGER NOT NULL DEFAULT 0,
is_private INTEGER NOT NULL DEFAULT 0,
description TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
)
widget_inheritance
表: 记录组件的继承关系。
sql
CREATE TABLE widget_inheritance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
widget_id INTEGER NOT NULL,
parent_name TEXT NOT NULL,
inheritance_order INTEGER NOT NULL,
)
3. Flutter 中解析 json
现在新建一个项目,把上一题中生成的 json 数据放在 assets
中。在 Flutter 代码中通过 rootBundle
可以读取资源数据,然后解析为 WidgetPo
列表即可:

dart
/// 从 assets 加载 Widget 数据
Future<List<WidgetPo>> loadWidgets() async {
try {
final jsonString = await rootBundle.loadString('assets/data/Widget.json');
final List<dynamic> jsonList = json.decode(jsonString);
return jsonList.asMap().entries.map((entry) {
final index = entry.key + 1; // 从 1 开始
final json = entry.value;
final widget = WidgetPo.fromJson(json);
// 设置顺序 ID
return WidgetPo(
id: index,
name: widget.name,
path: widget.path,
parents: widget.parents,
isAbstract: widget.isAbstract,
isPrivate: widget.isPrivate,
desc: widget.desc,
);
}).toList();
} catch (e) {
throw Exception('加载 Widget.json 失败: $e');
}
4. 数据入库
这里使用 sqlflite 插件访问数据库。
下面的 DatabaseHelper 类通过单例模式,来封装常用数据库操作。只需要调用它,就可以方便地打开、初始化或关闭数据库。运行项目之后,调用 initDatabase方法,就可以创建出数据库:

dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
/// 数据库帮助类
class DatabaseHelper {
static const String _databaseName = 'widget_parser.db';
static const int _databaseVersion = 1;
static Database? _database;
/// 获取数据库实例
static Future<Database> get database async {
_database ??= await initDatabase();
return _database!;
}
/// 初始化数据库
static Future<Database> initDatabase() async {
final String dbPath = await getDatabasesPath();
final String path = join(dbPath, _databaseName);
print("Database path:$path");
return await openDatabase(
path,
version: _databaseVersion,
onCreate: _onCreate,
);
}
/// 创建表结构
static Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE widgets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
path TEXT NOT NULL,
is_abstract INTEGER NOT NULL DEFAULT 0,
is_private INTEGER NOT NULL DEFAULT 0,
description TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''');
await db.execute('''
CREATE TABLE widget_inheritance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
widget_id INTEGER NOT NULL,
parent_name TEXT NOT NULL,
inheritance_order INTEGER NOT NULL
)
''');
}
/// 关闭数据库
static Future<void> close() async {
if (_database != null) {
await _database!.close();
_database = null;
}
}
}
目前已经有了 List<WidgetPo>
列表,小面接力棒交给数据的入库操作。sqlflite 中可以通过 Batch 来批量操作,只需要一次提交操作。相比于每次都通过 Database 插入一条效率要高一些。操作完后,我们就有了下锅的粮食,接下来就可以通过 sql 来分析了。

dart
/// 批量插入
static Future<void> insertBatch(List<WidgetPo> widgets) async {
final db = await DatabaseHelper.database;
await db.transaction((Transaction txn) async {
final Batch batch = txn.batch();
for (WidgetPo widget in widgets) {
final int widgetId = widget.id ?? 0;
batch.insert(
'widgets',
widget.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
for (int i = 0; i < widget.parents.length; i++) {
batch.insert('widget_inheritance', {
'widget_id': widgetId,
'parent_name': widget.parents[i],
'inheritance_order': i,
});
}
}
await batch.commit(noResult: true);
});
}
5. sql 练习
- 查询指定父类名称下,所有的子组件信息
替换 ?
为目标父类名,如 'StatelessWidget'
sql
SELECT w.*
FROM widgets w
JOIN widget_inheritance wi ON w.id = wi.widget_id
WHERE wi.parent_name = ?

- 统计 Flutter SDK 中所有父类型组件有哪些(去重)
sql
SELECT DISTINCT parent_name
FROM widget_inheritance
ORDER BY parent_name COLLATE NOCASE

- 支持过滤抽象类、私有类(带 where 条件)
这样就可以分析出 Flutter SDK 中公开可用的 StatelessWidget 派生类一共有 131 个:
sql
SELECT w.*
FROM widgets w
JOIN widget_inheritance wi ON w.id = wi.widget_id
WHERE wi.parent_name = ?
AND w.is_abstract = 0
AND w.is_private = 0

- 支持根据组件名模糊搜索
比如问号改为 Button
,就可以查看出 Flutter SDK 中可用的名字里有 Button
的组件一共有哪些:
sql
SELECT *
FROM widgets
WHERE
is_private=0
AND
name LIKE '%?%'

6.小结
这一题的核心是:把我们前面收集到的所有 Widget 组件信息,从 JSON 文件转存到本地数据库里。通过建两张表来分别保存组件的基本信息和继承关系,再用 Flutter 的 sqflite
插件批量写入数据库。这样做的好处是,后续查询和分析就非常方便,比如可以查某个父类有哪些子类、有哪些是抽象类、哪些是私有的,甚至还可以按组件名模糊搜索。
数据库一建好,我们就算是把 Flutter 的组件体系"数字化"存档了。后续还可以继续解析和入库更复杂的结构,比如每个组件类中有哪些字段,字段类型的详细信息等等。甚至你可以分析出 Flutter 组件中有哪些组件有 padding 属性。
后续的《每日一题》也将持续围绕实战问题展开,欢迎持续关注和参与,一起构建一个 开放、精致、有深度 的 Flutter 题库生态!
你是否也有想出的题目,欢迎投稿加入《匠心星问》共同打造!
如果你有其他的看法,或者有什么想要的题目、或者想提供题目和答案,都欢迎在评论区留言。更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。