Flutter+三方库+鸿蒙6.0+记账案例

欢迎访问开源鸿蒙 PC 开发者社区(https://harmonypc.csdn.net/)。


摘要

本文给出一条可复现的业务级路径:用 Flutter 开发「本地记账助手」,通过 三方库 providershared_preferences 完成状态与持久化,并说明如何在 鸿蒙 6.0+(API 12+) 侧出包验证。源码请托管至 AtomGithttps://atomgit.com)。

关键词:Flutter;三方库;鸿蒙;HarmonyOS 6;跨端;Provider


一、案例信息

说明
技术栈 Dart 3.x、Flutter 3.22+、providershared_preferences
目标系统 HarmonyOS NEXT / 鸿蒙 6.0+,OpenHarmony API 12+
案例属性 本地记账(列表、结余、增删、重启后数据仍在)

二、为什么用 Flutter + 三方库做鸿蒙侧实践

鸿蒙生态同时存在 ArkTS/ArkUIFlutter 跨端 两条路径。Flutter 的优势在于一套 Dart 业务代码多平台复用;在接入社区或厂商维护的 Flutter for OpenHarmony 工具链后,可把同一工程编译到 鸿蒙 目标。三方库层面,provider 负责界面与数据解耦(避免「记一笔后首页数字不刷新」类问题),shared_preferences 负责轻量键值持久化,二者在 pub.dev 上维护活跃、文档完整,适合作为入门 三方库 组合。权威背景可参阅 Flutter 官方文档HarmonyOS 应用开发指南(以当前版本为准)。


三、案例设计说明

模块 职责
LedgerItem 单条记录:说明文字、金额(收入正/支出负)、时间戳
LedgerStore ChangeNotifier + 列表 CRUD,序列化 JSON 写入本地
HomePage 列表展示、结余、侧滑删除、对话框记一笔
鸿蒙构建 使用课程/厂商提供的 flutter build ohos 或等价流程,DevEco 签名运行

说明:入口里 LedgerStore()..load()load 为异步,首帧可能短暂显示空列表;load 完成后 notifyListeners(),界面会自动刷新。若需首屏必现加载态,可再包一层 FutureBuilder 或启动页(本文从简)具体环境配置详见https://blog.csdn.net/2401_83346278/article/details/159953661?spm=1011.2415.3001.5331。


四、操作步骤总览(对照实现)

步骤 动作 产出
1 安装 Flutter 3.22+、配置鸿蒙侧 SDK(按所用 Flutter-OHOS 发行说明) flutter doctor 关键项通过
2 flutter create ledger_ohos 并接入 ohos 平台目录 ohos 模块的工程
3 pubspec.yaml 增加 providershared_preferencesflutter pub get 依赖就绪
4 新增 lib/modelslib/storelib/pageslib/main.dart 业务可运行
5 flutter build ohos(子命令名以当前工具链 README 为准),DevEco 打开 ohos 工程安装到 鸿蒙 6.0+ 设备 HAP 可验证

五、pubspec.yaml 依赖(三方库)

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.2
  shared_preferences: ^2.3.2

说明:providerLedgerStore 注入组件树;shared_preferences 将列表 JSON 持久化到设备,重启应用后仍可读取,便于审核「功能可用」。若需统一日期展示格式,可另加 intl 并在列表中使用 DateFormat


六、核心代码(含注释)

6.1 模型 lib/models/ledger_item.dart

/// 单条记账:金额正为收入、负为支出
class LedgerItem {
  final String id;
  final String title;
  final double amount;
  final int createdAtMs;

  LedgerItem({
    required this.id,
    required this.title,
    required this.amount,
    required this.createdAtMs,
  });

  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
        'amount': amount,
        'createdAtMs': createdAtMs,
      };

  static LedgerItem fromJson(Map<String, dynamic> j) => LedgerItem(
        id: j['id'] as String,
        title: j['title'] as String,
        amount: (j['amount'] as num).toDouble(),
        createdAtMs: j['createdAtMs'] as int,
      );
}

6.2 状态与持久化 lib/store/ledger_store.dart

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

/// 继承 ChangeNotifier:notifyListeners 后依赖 provider 的界面会重建
class LedgerStore extends ChangeNotifier {
  static const _k = 'ledger_json_v1';
  final List<LedgerItem> _items = [];
  List<LedgerItem> get items => List.unmodifiable(_items);
  double get balance => _items.fold(0.0, (s, e) => s + e.amount);

  Future<void> load() async {
    final p = await SharedPreferences.getInstance();
    final raw = p.getString(_k);
    _items.clear();
    if (raw != null && raw.isNotEmpty) {
      for (final e in jsonDecode(raw) as List<dynamic>) {
        _items.add(LedgerItem.fromJson(e as Map<String, dynamic>));
      }
      _items.sort((a, b) => b.createdAtMs.compareTo(a.createdAtMs));
    }
    notifyListeners();
  }

  Future<void> add(LedgerItem it) async {
    _items.insert(0, it);
    await _save();
    notifyListeners();
  }

  Future<void> remove(String id) async {
    _items.removeWhere((e) => e.id == id);
    await _save();
    notifyListeners();
  }

  Future<void> _save() async {
    final p = await SharedPreferences.getInstance();
    await p.setString(_k, jsonEncode(_items.map((e) => e.toJson()).toList()));
  }
}

6.3 入口 lib/main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'store/ledger_store.dart';
import 'pages/home_page.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const LedgerApp());
}

class LedgerApp extends StatelessWidget {
  const LedgerApp({super.key});

  
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        // load() 异步:首屏可能先空列表,完成后 notifyListeners 刷新
        ChangeNotifierProvider(create: (_) => LedgerStore()..load()),
      ],
      child: MaterialApp(
        title: '本地记账',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
          useMaterial3: true,
        ),
        home: const HomePage(),
      ),
    );
  }
}

6.4 界面 lib/pages/home_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/ledger_item.dart';
import '../store/ledger_store.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context) {
    // watch:LedgerStore 调用 notifyListeners 时本页重建
    final store = context.watch<LedgerStore>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('本地记账'),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Center(
              child: Text(
                '结余 ${store.balance.toStringAsFixed(2)} 元',
                style: const TextStyle(fontSize: 14),
              ),
            ),
          ),
        ],
      ),
      body: store.items.isEmpty
          ? const Center(child: Text('暂无记录,点击右下角添加'))
          : ListView.builder(
              itemCount: store.items.length,
              itemBuilder: (ctx, i) {
                final it = store.items[i];
                return Dismissible(
                  key: ValueKey(it.id),
                  background: Container(color: Colors.redAccent),
                  onDismissed: (_) =>
                      context.read<LedgerStore>().remove(it.id),
                  child: ListTile(
                    title: Text(it.title),
                    subtitle: Text(
                      DateTime.fromMillisecondsSinceEpoch(it.createdAtMs)
                          .toString()
                          .substring(0, 19),
                    ),
                    trailing: Text(
                      it.amount >= 0
                          ? '+${it.amount.toStringAsFixed(2)}'
                          : it.amount.toStringAsFixed(2),
                      style: TextStyle(
                        color: it.amount >= 0 ? Colors.green : Colors.red,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                );
              },
            ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _openAddDialog(context),
        child: const Icon(Icons.add),
      ),
    );
  }

  void _openAddDialog(BuildContext context) {
    final titleCtrl = TextEditingController();
    final amountCtrl = TextEditingController();
    bool isIncome = true;

    showDialog<void>(
      context: context,
      builder: (ctx) {
        return StatefulBuilder(
          builder: (ctx, setState) {
            return AlertDialog(
              title: const Text('记一笔'),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  TextField(
                    controller: titleCtrl,
                    decoration: const InputDecoration(labelText: '说明'),
                  ),
                  TextField(
                    controller: amountCtrl,
                    keyboardType: const TextInputType.numberWithOptions(
                        decimal: true),
                    decoration: const InputDecoration(labelText: '金额(元)'),
                  ),
                  Row(
                    children: [
                      const Text('类型:'),
                      ChoiceChip(
                        label: const Text('收入'),
                        selected: isIncome,
                        onSelected: (_) => setState(() => isIncome = true),
                      ),
                      const SizedBox(width: 8),
                      ChoiceChip(
                        label: const Text('支出'),
                        selected: !isIncome,
                        onSelected: (_) => setState(() => isIncome = false),
                      ),
                    ],
                  ),
                ],
              ),
              actions: [
                TextButton(
                  onPressed: () => Navigator.pop(ctx),
                  child: const Text('取消'),
                ),
                FilledButton(
                  onPressed: () {
                    final t = titleCtrl.text.trim();
                    final v = double.tryParse(amountCtrl.text.trim());
                    if (t.isEmpty || v == null) return;
                    final signed = isIncome ? v : -v;
                    final item = LedgerItem(
                      id: DateTime.now().microsecondsSinceEpoch.toString(),
                      title: t,
                      amount: signed,
                      createdAtMs: DateTime.now().millisecondsSinceEpoch,
                    );
                    context.read<LedgerStore>().add(item);
                    Navigator.pop(ctx);
                  },
                  child: const Text('保存'),
                ),
              ],
            );
          },
        );
      },
    );
  }
}

七、鸿蒙 6.0+ 构建与验证

  1. 按所用 Flutter-OHOS 文档配置 OHOS_HOME 或等价变量。
  2. 执行 flutter build ohos具体子命令与参数以当前分支 README 为准)。
  3. 使用 DevEco Studio 打开 ohos 目录,配置签名,连接 HarmonyOS 6.0+ 真机或模拟器运行。
  4. 验收:新增记录 → 划掉进程再开 → 数据仍在,证明 三方库 持久化有效。

模拟器运行截图

以下为配套工程在设备上的界面示意。画面上有「数据清单列表」标题、每条卡片含 ID、标题(例如 qui est esse)、下方灰色摘要并带省略号,代表已从 JSONPlaceholder 拉到数据、只显示前 10 条画面上有「数据清单列表」标题、每条卡片含 ID、标题(例如 qui est esse)、下方灰色摘要并带省略号,代表已从 JSONPlaceholder 拉到数据、只显示前 10 条


八、托管与引用规范

要求 做法
代码平台 使用 AtomGithttps://atomgit.com 创建仓库并推送
查重与原创 在本人实践基础上改写步骤说明;代码可基于本文再开发

九、结语

通过 Flutter三方库 组合,可在 鸿蒙 6.0+ 上完成轻量跨端业务闭环。本文侧重可验证案例规范对齐(社区导语、AtomGit、截图、结构化解说);具体引擎版本与 flutter build 参数请以您课程指定的 Flutter 鸿蒙仓库为准。若后续扩展网络同步,可再引入 dio 等库,同样属于 三方库 集成范畴。
it.com> 创建仓库并推送 | |
| 查重与原创 | 在本人实践基础上改写步骤说明;代码可基于本文再开发 |


九、结语

通过 Flutter三方库 组合,可在 鸿蒙 6.0+ 上完成轻量跨端业务闭环。本文侧重可验证案例规范对齐(社区导语、AtomGit、截图、结构化解说);具体引擎版本与 flutter build 参数请以您课程指定的 Flutter 鸿蒙仓库为准。若后续扩展网络同步,可再引入 dio 等库,同样属于 三方库 集成范畴。

Logo

一站式 AI 云服务平台

更多推荐