flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
/// ============================================================
|
||||
/// 【AppDatabase】本地业务数据库(存储分层第三层)—— 平台分发入口
|
||||
///
|
||||
/// 用法不变:main.dart 里 `await AppDatabase.instance.init();`
|
||||
///
|
||||
/// 一、为什么拆成条件导出(三个文件):
|
||||
/// isar 3.x 生成的 Schema 代码(*.g.dart)里含 64 位哈希常量
|
||||
/// (CollectionSchema.id,如 -6819722535046815095),远超 JS 可精确
|
||||
/// 表示的最大整数 2^53,dart2js 编译 Web 端时会直接报
|
||||
/// "The integer literal ... can't be represented exactly in JavaScript"。
|
||||
/// 即使 kIsWeb 在【运行时】跳过数据库,【编译期】依然失败——
|
||||
/// 因为 app_database.dart 静态 import 了模型 → 静态 import 了 .g.dart。
|
||||
///
|
||||
/// 二、条件导出的分发规则(编译期决定,运行期零开销):
|
||||
/// - native(Android/iOS/桌面,dart.library.io 为真):
|
||||
/// → app_database_io.dart,真 Isar 实现
|
||||
/// - web(dart2js / dart2wasm,dart.library.io 为假):
|
||||
/// → app_database_web.dart,空实现(web 仅调试 UI/网络)
|
||||
///
|
||||
/// 三、高频坑:
|
||||
/// 1. 三件套必须都定义 `class AppDatabase`,且成员签名保持一致,
|
||||
/// 否则共享代码(如 main.dart)在某个平台会编译不过;
|
||||
/// 2. web 端别用 Isar 集合类型(`IsarCollection<T>`),
|
||||
/// 那只存在于 io 实现里——web 端没有这些成员;
|
||||
/// 3. 改模型字段后仍要重跑:
|
||||
/// dart run build_runner build --delete-conflicting-outputs
|
||||
/// ============================================================
|
||||
library;
|
||||
|
||||
export 'app_database_web.dart'
|
||||
if (dart.library.io) 'app_database_io.dart';
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:isar_community/isar.dart';
|
||||
import '../models/play_record.dart';
|
||||
import '../models/question.dart';
|
||||
import '../models/wrong_question.dart';
|
||||
import '../models/answer_record.dart';
|
||||
import 'package:new_learn/core/version/app_version_entity.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【AppDatabase】Isar 本地业务数据库 —— native(Android/iOS/桌面)真实现
|
||||
///
|
||||
/// 仅当 dart.library.io 为真时编译(见 app_database.dart 条件导出)。
|
||||
/// web 端不会编译本文件,因此可以放心 import isar 与 *.g.dart。
|
||||
///
|
||||
/// 一、为什么需要第三层(对照前两层):
|
||||
/// 1. flutter_secure_storage → Token 等敏感凭据(最高安全)
|
||||
/// 2. shared_preferences → 用户信息/开关等小配置(明文快读)
|
||||
/// 3. AppDatabase(Isar) → 题库/错题/播放记录等大批量结构化数据
|
||||
/// 几十上百条 JSON 塞进 shared_preferences 会越写越卡,
|
||||
/// Isar 是本地 NoSQL 数据库:读写毫秒级、强类型、ACID 事务、支持索引。
|
||||
///
|
||||
/// 二、为什么用 isar_community 而不是 hive / isar:
|
||||
/// - hive:存 Map 无类型保障,官方已停更;
|
||||
/// - isar 3.1.8:只发在已宕机的 pub.isar-community.dev,装不上;
|
||||
/// - isar_community:社区在 pub.dev 的维护版,API 与 isar 完全一致。
|
||||
///
|
||||
/// 三、高频坑(Isar 专属):
|
||||
/// 1. init() 必须在 runApp 前 await(见 main.dart),
|
||||
/// 否则页面访问集合会抛 "Missing XxxSchema in Isar.open";
|
||||
/// 2. 模型改字段后必须重跑代码生成:
|
||||
/// dart run build_runner build --delete-conflicting-outputs
|
||||
/// (生成 *.g.dart,XxxSchema 就在里面);
|
||||
/// 3. 所有写操作包在 isar.writeTxn() 里;查询用 where()/filter();
|
||||
/// 4. 集合访问用 `isar.collection<T>()`(与生成的 isar.xxx 等价);
|
||||
/// 5. Web 端调试用空实现(app_database_web.dart),不要在这加 kIsWeb:
|
||||
/// 本文件只会在 native 编译,kIsWeb 恒为 false。
|
||||
/// ============================================================
|
||||
class AppDatabase {
|
||||
AppDatabase._();
|
||||
|
||||
static final AppDatabase instance = AppDatabase._();
|
||||
|
||||
late Isar isar;
|
||||
|
||||
/// 初始化:打开 Isar 实例并注册全部集合 Schema(main() 里 await)
|
||||
Future<void> init() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
isar = await Isar.open(
|
||||
[
|
||||
QuestionSchema,
|
||||
WrongQuestionSchema,
|
||||
PlayRecordSchema,
|
||||
RemoteVersionEntitySchema,
|
||||
AnswerRecordSchema,
|
||||
],
|
||||
directory: dir.path,
|
||||
name: 'app_db',
|
||||
inspector: true, // ✅加上这一行,debug模式启用Isar Inspector调试页面
|
||||
);
|
||||
debugPrintIsarInit();
|
||||
}
|
||||
|
||||
/// 集合访问守卫:未初始化时给出明确报错(而不是空指针)
|
||||
void _ensureReady() {
|
||||
if (!isar.isOpen) {
|
||||
throw StateError('本地数据库(Isar)未初始化:请在 main() 里 await init()');
|
||||
}
|
||||
}
|
||||
|
||||
/// 题库集合:新增/查询/删除题目都走它
|
||||
IsarCollection<Question> get questions {
|
||||
_ensureReady();
|
||||
return isar.collection<Question>();
|
||||
}
|
||||
|
||||
/// 错题本集合
|
||||
IsarCollection<WrongQuestion> get wrongQuestions {
|
||||
_ensureReady();
|
||||
return isar.collection<WrongQuestion>();
|
||||
}
|
||||
|
||||
/// 答题进度集合(续答功能:进卷读回 / 作答写入 / 交卷清理)
|
||||
IsarCollection<AnswerRecord> get answerRecords {
|
||||
_ensureReady();
|
||||
return isar.collection<AnswerRecord>();
|
||||
}
|
||||
|
||||
/// 播放记录集合
|
||||
IsarCollection<PlayRecord> get playRecords {
|
||||
_ensureReady();
|
||||
return isar.collection<PlayRecord>();
|
||||
}
|
||||
|
||||
/// 远端版本策略缓存集合(版本更新机制用)
|
||||
IsarCollection<RemoteVersionEntity> get remoteVersions {
|
||||
_ensureReady();
|
||||
return isar.collection<RemoteVersionEntity>();
|
||||
}
|
||||
|
||||
/// 清空全部业务数据(换账号/恢复出厂设置时调用)
|
||||
Future<void> clearAll() async {
|
||||
_ensureReady();
|
||||
await isar.writeTxn(() async {
|
||||
await questions.clear();
|
||||
await wrongQuestions.clear();
|
||||
await playRecords.clear();
|
||||
await remoteVersions.clear();
|
||||
await answerRecords.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 打印初始化完成日志(独立函数便于 web 空实现复用相同日志风格)
|
||||
void debugPrintIsarInit() {
|
||||
// ignore: avoid_print
|
||||
print('▶ 【Isar】业务数据库初始化完成(题库/错题/播放记录)');
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【AppDatabase】本地业务数据库 —— web 空实现(Chrome/Edge 调试用)
|
||||
///
|
||||
/// 仅当 dart.library.io 为假时编译(见 app_database.dart 条件导出)。
|
||||
/// isar 3.x 全系不支持 web(官方版解析不了 Dart 3;社区版 web 实现是
|
||||
/// 占位 stub,调用即抛 "Please use Isar 2.5.0 if you need web support"),
|
||||
/// 且生成代码里的 64 位哈希常量 dart2js 编译不过。
|
||||
/// 所以 web 端用空实现:init() 直接跳过,UI/登录/网络调试完全不受影响。
|
||||
/// 生产走 Android/iOS,app_database_io.dart 真 Isar 照常。
|
||||
///
|
||||
/// ⚠ 注意:本文件不要 import isar 或任何 *.g.dart ——
|
||||
/// web 端没有 IsarCollection 等类型,集合读写成员也不存在(编译报错)。
|
||||
/// 这正是"web 只调试、不上数据库"的强制约束。
|
||||
/// ============================================================
|
||||
class AppDatabase {
|
||||
AppDatabase._();
|
||||
|
||||
static final AppDatabase instance = AppDatabase._();
|
||||
|
||||
/// web 调试模式:跳过本地数据库(UI/网络正常调试)
|
||||
Future<void> init() async {
|
||||
debugPrint('▶ 【Isar】Web 调试模式:跳过本地数据库(UI/网络正常调试)');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user