Flutter 百题斩#17 | SDK 组件数据入库 - sqlite


最近在着手开发我的 《匠心星问》 ,它定位是一款 题库 应用,将集题目浏览、发布、解答、做题为一体。打算第一步先以 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 练习

  1. 查询指定父类名称下,所有的子组件信息

替换 ? 为目标父类名,如 'StatelessWidget'

sql 复制代码
SELECT w.*
FROM widgets w
JOIN widget_inheritance wi ON w.id = wi.widget_id
WHERE wi.parent_name = ?

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

  1. 支持过滤抽象类、私有类(带 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

  1. 支持根据组件名模糊搜索

比如问号改为 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 站 。

相关推荐
JarvanMo1 分钟前
终极指南:在 Flutter 中通过 sign_in_with_apple 实现 Apple 登录
前端
Learner7 分钟前
Python异常处理
java·前端·python
tao35566710 分钟前
VS Code登录codex,报错(os error 10013)
java·服务器·前端
深海呐11 分钟前
Android WebView吊起软键盘遮挡输入框的问题解决
android·webview·android 键盘遮挡·webview键盘遮挡
小雨下雨的雨11 分钟前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
摘星编程13 分钟前
RAG的下一站:检索增强生成如何重塑企业知识中枢?
android·人工智能
军军君0114 分钟前
Three.js基础功能学习七:加载器与管理器
开发语言·前端·javascript·学习·3d·threejs·三维
cn_mengbei14 分钟前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
JarvanMo16 分钟前
情迷服务器驱动 UI:我在 Flutter 开发中的爱与哀愁
前端
tzy23319 分钟前
分享一个 HTTP(S) 代理&抓包工具,拦截和Mock Web客户端请求和服务端响应
前端·网络协议·http