Flutter for OpenHarmony 实战:Sembast — 丝滑体验的纯 Dart NoSQL 数据库

在这里插入图片描述

前言

Flutter for OpenHarmony 开发中,我们经常需要处理结构化但又具有高度灵活性的数据。虽然 SQLite 是老牌选择,但其繁琐的 SQL 语句和固定的表结构对于快速迭代的互联网项目来说显得有些笨重。

Sembast 是目前跨平台生态中最受欢迎的纯 Dart 实现的 NoSQL 数据库。它不需要任何复杂的 C 语言级二进制依赖(FFI),直接基于 JSON 格式进行存储。这意味着它在鸿蒙全版本系统上都有着极其稳定的表现。本文将带你探索这一“随处可用”的数据存储利器。


二、为什么 Sembast 是中小型鸿蒙应用的首选?

1.1 零原生依赖,极致稳定 🛡️

Sembast 不依赖底层系统的数据库驱动。它将数据持久化为简单的文本流。在鸿蒙设备开启了“极速模式”或系统内核大幅更新时,Sembast 不会因为 FFI 兼容问题而导致应用启动异常。

1.2 强大的反应式 (Reactive) 支持

它原生支持 Stream 监听。一旦数据库某个 Record 发生变动,UI 层的 StreamBuilder 会自动捕捉并刷新,无需手动刷新页面。


三、配置环境 📦

引入核心包及其配套的文件系统适配器:

dependencies:
  sembast: ^3.7.4
  path_provider: ^2.1.2
  path: ^1.9.0

# ⚠️ 关键:必须覆盖原有的 path_provider 以支持鸿蒙沙箱路径获取
dependency_overrides:
  path_provider_ohos:
    git:
      url: https://gitee.com/openharmony-sig/flutter_packages.git
      path: packages/path_provider/path_provider_ohos

3.1 初始化鸿蒙侧数据库工厂

由于 Sembast 是纯 Dart 实现,它不需要在鸿蒙 Native 层配置任何 .so(不像 ObjectBox)。你只需要获取到一个合法的鸿蒙沙箱路径即可:

import 'package:sembast/sembast_io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';

Future<Database> openOhosDb() async {
  // 1. 获取鸿蒙应用的私有文档目录
  // 💡 技巧:path_provider_ohos 会自动映射到鸿蒙 el2 级加密存储
  final dir = await getApplicationDocumentsDirectory();
  
  // 2. 确保目录存在
  await dir.create(recursive: true);
  
  // 3. 拼接数据库物理路径
  final dbPath = join(dir.path, 'harmony_vault.db');
  
  // 4. 打开并返回数据库实例
  return await databaseFactoryIo.openDatabase(dbPath);
}

在这里插入图片描述


四、核心功能:3 个高阶数据操作场景

3.1 基于 Map 的灵活存取 (Dynamic Schema)

无需建表,直接存储任何结构的 JSON 数据。

void saveConfig(Database db) async {
  final store = intMapStoreFactory.store('ohos_settings');
  
  // 💡 技巧:直接插入 Map,系统会自动生成自增 ID
  await store.add(db, {
    'theme': 'deep_blue',
    'notifications_enabled': true,
    'last_sync': DateTime.now().toIso8601String(),
  });
}

在这里插入图片描述

3.2 响应式实时监听 (Stream Query)

让选中的记录集始终与 UI 保持同步。

Stream<List<RecordSnapshot<int, Map<String, dynamic>>>> watchTasks(Database db) {
  final store = intMapStoreFactory.store('tasks');
  final finder = Finder(sortOrders: [SortOrder('priority', false)]);
  
  // 💡 技巧:这是构建即时聊天或动态列表的利器
  return store.query(finder: finder).onSnapshots(db);
}

在这里插入图片描述

3.3 事务处理与并发安全 (Transactions)

确保鸿蒙端在多任务同时操作数据库时的原子性。

await db.transaction((txn) async {
  await store.record(1).put(txn, {'status': 'processed'});
  await auditStore.add(txn, {'log': 'ID 1 updated'});
});

在这里插入图片描述


5.1 极致的跨平台兼容性 🚀

Sembast 是目前在鸿蒙开发中最推荐的复杂数据库方案。

  • 原因:它没有任何 Native 绑定代码。即便鸿蒙系统从 4.0 升级到 5.0,或者从 ARM 架构切换到其他架构,Sembast 都能因为“纯 Dart”的特性实现无缝运行,完全避开了 ObjectBox 等库面临的 .so 库丢失或二进制冲突问题。

5.2 处理鸿蒙后台挂起时的断电风险

  • 💡 技巧:Sembast 的 sembast_io 实现使用了“顺序追加”日志模式。即使在写入中途发生断电或应用强杀,它也能在下次启动时自动修复损坏的末端,极大地保护了鸿蒙用户的数据完整性。建议对关键操作显式使用 await db.flush() 以确保数据落地。

六、完整实战示例:构建鸿蒙应用“全平台”离线文章收藏中心

我们将构建一个具备高性能的收藏管理模块:它能够离线缓存文章详情,并提供秒级的多条件模糊查询能力。

import 'package:flutter/material.dart';
import 'package:sembast/sembast.dart';
import 'sembast_helper.dart';

class OhosContentVaultPage extends StatefulWidget {
  const OhosContentVaultPage({super.key});

  
  State<OhosContentVaultPage> createState() => _OhosContentVaultPageState();
}

class _OhosContentVaultPageState extends State<OhosContentVaultPage> {
  final _store = intMapStoreFactory.store('collected_articles');
  String _searchKeyword = '';

  // 💡 实战:构建动态过滤的 Stream
  Stream<List<RecordSnapshot<int, Map<String, dynamic>>>> _getFilteredStream(
      Database db) {
    Filter? filter;
    if (_searchKeyword.isNotEmpty) {
      // 同时匹配标题或标签
      filter = Filter.or([
        Filter.matches('title', _searchKeyword),
        Filter.matches('tags', _searchKeyword),
      ]);
    }

    final finder = Finder(
      filter: filter,
      sortOrders: [SortOrder('createdAt', false)],
    );

    return _store.query(finder: finder).onSnapshots(db);
  }

  Future<void> _addSampleArticle() async {
    final db = await SembastHelper.database;
    final samples = [
      {'title': '鸿蒙内核深度解析', 'url': 'ohos.dev/kernel', 'tags': '底层'},
      {'title': 'Dart 3.0 新特性', 'url': 'dart.dev/v3', 'tags': '语法'},
      {
        'title': 'Flutter 鸿蒙适配指南',
        'url': 'flutter_ohos.org/guide',
        'tags': '工程'
      },
    ];

    // 随机选一个存入
    final article = samples[DateTime.now().second % samples.length];
    await _store.add(db, {
      ...article,
      'createdAt': DateTime.now().millisecondsSinceEpoch,
    });
  }

  void _onSearchSubmit(String kw) {
    setState(() {
      _searchKeyword = kw;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text('内容同步中心 (真实 DB)'),
        elevation: 0,
        backgroundColor: Colors.white,
        foregroundColor: Colors.black,
      ),
      body: FutureBuilder<Database>(
        future: SembastHelper.database,
        builder: (context, dbSnapshot) {
          if (!dbSnapshot.hasData)
            return const Center(child: CircularProgressIndicator());

          return Column(
            children: [
              _buildSearchBar(),
              Expanded(
                child: StreamBuilder<
                    List<RecordSnapshot<int, Map<String, dynamic>>>>(
                  stream: _getFilteredStream(dbSnapshot.data!),
                  builder: (context, streamSnapshot) {
                    final articles = streamSnapshot.data ?? [];

                    if (articles.isEmpty) {
                      return Center(
                        child: Text(
                            _searchKeyword.isEmpty ? '你的收藏夹空空如也' : '未找到相关文章'),
                      );
                    }

                    return ListView.separated(
                      padding: const EdgeInsets.symmetric(horizontal: 16),
                      itemCount: articles.length,
                      separatorBuilder: (_, __) => const Divider(height: 1),
                      itemBuilder: (context, index) {
                        final data = articles[index].value;
                        return ListTile(
                          contentPadding:
                              const EdgeInsets.symmetric(vertical: 8),
                          title: Text(data['title'] as String,
                              style:
                                  const TextStyle(fontWeight: FontWeight.bold)),
                          subtitle: Text(data['url'] as String,
                              style: TextStyle(color: Colors.grey[600])),
                          trailing: Chip(
                            label: Text(data['tags'] as String,
                                style: const TextStyle(fontSize: 10)),
                            backgroundColor: Colors.blue[50],
                          ),
                          onLongPress: () async {
                            await _store
                                .record(articles[index].key)
                                .delete(dbSnapshot.data!);
                          },
                        );
                      },
                    );
                  },
                ),
              ),
              _buildNote(),
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addSampleArticle,
        child: const Icon(Icons.bookmark_add_outlined),
      ),
    );
  }

  Widget _buildSearchBar() {
    return Container(
      padding: const EdgeInsets.all(16),
      child: TextField(
        onSubmitted: _onSearchSubmit,
        onChanged: (v) {
          if (v.isEmpty) _onSearchSubmit('');
        },
        decoration: InputDecoration(
          hintText: '搜索本地离线文章 (按回车提交)...',
          prefixIcon: const Icon(Icons.search),
          filled: true,
          fillColor: Colors.grey[100],
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(30),
            borderSide: BorderSide.none,
          ),
        ),
      ),
    );
  }

  Widget _buildNote() {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: Colors.blue[50],
        border: Border(top: BorderSide(color: Colors.blue[100]!)),
      ),
      child: const Row(
        children: [
          Icon(Icons.info_outline, color: Colors.blue, size: 20),
          SizedBox(width: 12),
          Expanded(
            child: Text(
              '已开启鸿蒙级 FFI 零依赖持久化:搜索是在真实的本地 .db 文件中利用 Filter.matches 完成的。',
              style: TextStyle(color: Colors.blue, fontSize: 12),
            ),
          ),
        ],
      ),
    );
  }
}

在这里插入图片描述


七、总结

SembastFlutter for OpenHarmony 开发者提供了一种极其优雅的数据交互方式。它不需要你成为 SQL 专家,只需关注你的 Dart 模型。在鸿蒙系统这个快速进化的舞台上,Sembast 正是那个能让你“轻装上阵”的数据库伙伴。

如果你追求开发速度与运行稳定性的平衡,请务必给 Sembast 一个机会。


🌐 欢迎加入开源鸿蒙��平台社区开源鸿蒙跨平台开发者社区

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐