开源鸿蒙Flutter持久化实战

标签:开源鸿蒙、Flutter for OpenHarmony、跨平台框架、本地持久化、数据缓存

作者:maaath

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

摘要

在 Flutter for OpenHarmony 项目里,网络数据、页面切换和组件复用往往不是最难的部分,真正影响体验的,常常是“状态是否能留住”。用户刚收藏完内容、刚切换完配置,如果应用重启后全部丢失,这种割裂感会非常明显。本文围绕 Flutter for OpenHarmony 跨平台技术 展开,结合一个实际运行的 OpenHarmony 示例工程,完成两件事:

  1. 为跨平台应用接入本地持久化能力;
  2. 实现用户配置与收藏数据缓存,并在开源鸿蒙模拟器上完成运行验证。

我在实践时最关注的并不是“能不能写进去”,而是“写进去后是否真的能在 OpenHarmony 设备侧稳定恢复”。因此,本文不仅讲 Flutter 层的封装,也会说明它在鸿蒙侧最终依赖的是 @ohos.data.preferences 能力,这一点很关键。

为什么在 Flutter for OpenHarmony 中优先做持久化

Flutter for OpenHarmony 的价值,在于它让一套 Dart UI 与业务逻辑可以在 OpenHarmony 上快速落地,减少重复开发成本。对于资讯、服务、内容分发和用户中心类应用来说,本地缓存至少承担三类职责:

  • 保存用户偏好:例如主题、语言、通知开关;
  • 保存轻量业务状态:例如最近选择项、筛选条件;
  • 保存收藏/书签数据:让用户跨会话继续使用。

如果这些数据全部依赖远端接口恢复,不仅首屏慢,而且离线体验差。基于这一点,本文选择 shared_preferences 作为 Flutter 层入口,再通过 OpenHarmony 侧插件把数据落到系统 Preferences 中。这种方案的优点是清晰、成熟,而且适合“配置类”和“轻量结构化数据”场景。

从平台能力看,OpenHarmony 官方文档明确提供了 Preferences 数据持久化接口,适合存储键值型数据;而 Flutter 官方生态中,shared_preferences 的使用方式也非常稳定。这两者组合起来,几乎就是跨平台工程里最自然的一条路径。参考资料可见:

本文示例工程的思路

本文使用的是一个 Flutter for OpenHarmony 示例工程,页面结构包含 HomeDiscoverFavoritesProfile 四个标签页。为了避免文章空泛,我把目标控制得很具体:

  • 收藏页中的内容需要被持久化;
  • 个人页中的用户状态需要具备扩展空间;
  • OpenHarmony 端必须能真正运行,而不是只停留在 Dart 层演示。

从结果看,这种拆分是合理的。收藏数据属于典型的“轻量对象列表”,可以序列化成 JSON 存入本地;用户配置则适合继续复用同一套存储入口。换句话说,先把“收藏缓存”打通,后续扩展“用户设置缓存”时成本会非常低。

Flutter 层:先把收藏服务做成可复用单例

在 Flutter for OpenHarmony 工程里,我建议把本地持久化能力封装成服务类,而不是散落在页面中。下面这段代码是本文示例的核心:一个单例 FavoritesService,负责完成初始化、添加收藏、取消收藏和本地恢复。

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/data_item.dart';

class FavoritesService extends ChangeNotifier {
  static final FavoritesService _instance = FavoritesService._internal();
  factory FavoritesService() => _instance;
  FavoritesService._internal();

  static const String _storageKey = 'favorites_data';

  final List<DataItem> _favorites = [];
  bool _initialized = false;

  List<DataItem> get favorites => List.unmodifiable(_favorites);
  bool get isInitialized => _initialized;

  bool isFavorite(int id) {
    return _favorites.any((item) => item.id == id);
  }

  Future<void> init() async {
    if (_initialized) return;
    await _loadFromPrefs();
    _initialized = true;
  }

  Future<void> toggleFavorite(DataItem item) async {
    if (isFavorite(item.id)) {
      _favorites.removeWhere((e) => e.id == item.id);
    } else {
      _favorites.add(item);
    }
    await _saveToPrefs();
    notifyListeners();
  }

  Future<void> _loadFromPrefs() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonStr = prefs.getString(_storageKey);
    if (jsonStr == null || jsonStr.isEmpty) return;

    _favorites.clear();
    final List<dynamic> data = jsonDecode(jsonStr);
    for (final item in data) {
      _favorites.add(DataItem.fromJson(item as Map<String, dynamic>));
    }
  }

  Future<void> _saveToPrefs() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonStr = jsonEncode(_favorites.map((e) => e.toJson()).toList());
    await prefs.setString(_storageKey, jsonStr);
  }
}

这段代码的设计重点有三个:

1. 用单例保证状态一致

收藏本质上是全局状态。如果每个页面都 new 一个服务实例,状态会被切碎。单例可以保证收藏列表在标签页切换时始终一致。

2. 用 JSON 保存对象列表

shared_preferences 更适合存基础类型,因此我把 List<DataItem> 转成 JSON 字符串保存。这是 Flutter 跨平台项目里非常常见、也足够稳妥的处理方式。

3. 修改状态后立即落盘

我个人比较倾向“操作后立刻持久化”,虽然这会多几次 I/O,但对于收藏这类轻量数据来说收益更大:即使用户突然退出应用,也不容易丢状态。

数据模型:尽量保持序列化友好

本地缓存如果想稳定,模型必须简单、可逆。下面这个 DataItem 模型就是一个适合本地 JSON 序列化的结构:

class DataItem {
  final int id;
  final String title;
  final String description;
  final String status;
  final String createdAt;

  DataItem({
    required this.id,
    required this.title,
    required this.description,
    required this.status,
    required this.createdAt,
  });

  factory DataItem.fromJson(Map<String, dynamic> json) {
    return DataItem(
      id: json['id'] as int? ?? 0,
      title: json['title'] as String? ?? '',
      description: json['description'] as String? ?? '',
      status: json['status'] as String? ?? 'pending',
      createdAt: json['created_at'] as String? ?? '',
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'status': status,
      'created_at': createdAt,
    };
  }
}

这里没有引入复杂反射,也没有额外的序列化生成工具,目的就是让 Flutter for OpenHarmony 工程在迁移和调试时足够直观。对于文章读者来说,这种写法也更容易上手。

页面接入:让收藏数据“看得见、删得掉、重启能恢复”

FavoritesPage 中,页面通过 ListenableBuilder 监听 FavoritesService,这样本地状态一旦变化,界面会立即刷新。这个思路非常适合 Flutter for OpenHarmony,因为 UI 更新逻辑仍然保持 Flutter 原生开发习惯,不需要为了平台适配改写页面结构。

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

  
  State<FavoritesPage> createState() => _FavoritesPageState();
}

class _FavoritesPageState extends State<FavoritesPage> {
  final FavoritesService _favoritesService = FavoritesService();

  
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: _favoritesService,
      builder: (context, child) {
        final favorites = _favoritesService.favorites;
        return Scaffold(
          appBar: AppBar(title: const Text('Favorites')),
          body: favorites.isEmpty
              ? const Center(child: Text('No favorites yet'))
              : ListView.builder(
                  itemCount: favorites.length,
                  itemBuilder: (context, index) {
                    final item = favorites[index];
                    return ListTile(
                      title: Text(item.title),
                      subtitle: Text(item.description),
                      trailing: IconButton(
                        icon: const Icon(Icons.favorite, color: Colors.red),
                        onPressed: () => _favoritesService.toggleFavorite(item),
                      ),
                    );
                  },
                ),
        );
      },
    );
  }
}

这一层实现完成后,Flutter 侧就已经具备完整收藏缓存能力了。用户收藏的对象会以 JSON 形式存储,本次会话和下次会话都能恢复。这也是我认为最值得优先落地的一步:它足够小,但对用户体验的提升很直接。

OpenHarmony 侧:shared_preferences 最终落到 Preferences

如果只讲 Flutter 代码,文章会显得“跨平台味道不够”。为了把链路讲完整,有必要说明一下 OpenHarmony 侧插件是如何落地的。

在本文工程中,shared_preferences_ohos 插件最终通过 @ohos.data.preferences 完成真实持久化。也就是说,Flutter 调用 SharedPreferences.getInstance() 后,并不是凭空生效,而是由 OpenHarmony 插件桥接到系统 Preferences

import data_preferences from '@ohos.data.preferences'

const PREFERENCES_NAME = 'FlutterSharedPreferences';

onAttachedToEngine(binding: FlutterPluginBinding): void {
  let promise = data_preferences.getPreferences(
    binding.getApplicationContext(),
    PREFERENCES_NAME
  );
  promise.then((object) => {
    this.preferences = object;
  })
}

put(key: string, value: ESObject): Promise<void> {
  this.preferences?.put(key, value);
  return this.preferences!.flush();
}

这部分让我更愿意把 Flutter for OpenHarmony 视为“真正可落地的跨平台方案”,而不是简单的界面移植。因为它既保留了 Flutter 的编程体验,又能在 OpenHarmony 设备上调用原生数据能力,形成一条完整的存储链路。

模拟器运行验证:这一步不能省

本文没有停留在代码展示层面,而是完成了 OpenHarmony 模拟器侧的安装与启动验证。验证链路包括:

  • 使用 hvigor 构建 HAP;
  • 通过 hdc 安装到模拟器;
  • 成功启动应用主 Ability;
  • 在界面中看到服务台页面与缓存相关内容。

从工程实践角度看,我认为“能编过”与“能跑起来”是两个概念。前者说明语法没问题,后者才说明平台能力、签名、打包与运行路径全部打通。本文示例已经完成了这一层验证,因此更适合作为读者上手 Flutter for OpenHarmony 持久化改造的起点。

运行截图(OpenHarmony 模拟器)

下图为代码在 OpenHarmony 模拟器上的运行截图,可作为本文方案成功落地的验证证据:

在这里插入图片描述

读者实践建议

如果你也要在 Flutter for OpenHarmony 项目中加入本地持久化,我建议按下面顺序推进:

  1. 先定数据边界:优先缓存轻量数据,不要一开始就把复杂数据库场景塞进来;
  2. 先封装服务层:把收藏、配置等逻辑放进 Service,而不是散在页面里;
  3. 先验证恢复链路:测试“收藏后退出应用,再进入是否恢复”;
  4. 再扩展配置项:例如主题、语言、通知、最近浏览记录;
  5. 最后再考虑同步策略:本地缓存与远端账户同步是下一阶段问题,不必一步到位。

如果需要托管自己的跨平台代码仓库,建议统一使用 AtomGithttps://atomgit.com 。这样在团队协作、版本管理和成果展示时会更规范,也符合当前国产开源协作生态。

结语

回到题目本身:为开源鸿蒙跨平台工程集成本地持久化能力,实现用户配置与收藏数据缓存,并完成开源鸿蒙模拟器运行验证,这件事在 Flutter for OpenHarmony 中完全可行,而且实现路径并不复杂。

在我看来,这类改造最有价值的地方,不是多写了几百行代码,而是把“跨平台应用在 OpenHarmony 上是否具备真实可用性”往前推进了一步。Flutter 层负责组织业务状态,OpenHarmony 层负责承接系统存储能力,两者结合后,用户才能真正感知到“这个应用是连续的、可信赖的”。

如果你正在做内容类、服务类或者用户中心较重的 Flutter for OpenHarmony 项目,我会很建议先把本地持久化打牢。因为一旦这层能力稳定下来,后续你做离线优化、弱网恢复、个性化推荐,都会轻松很多。

感谢各位阅读!

Logo

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

更多推荐