Flutter for OpenHarmony 实战:built_value 强类型模型生成与不可变数据模型

在这里插入图片描述

前言

在处理鸿蒙应用复杂的后端业务逻辑时,数据的准确性一致性是开发者最大的痛点。JavaScript 风格的 Model 类虽然灵活,但容易在运行时抛出各种莫名其妙的 null 异常。

built_value 通过一套严密的“不可变(Immutable)”数据模型生成机制,配合 built_value_generator,能让你的鸿蒙 Flutter 应用在模型层拥有像 Java 甚至 Swift 一样的强类型安全保证。


一、 工程准备:安装与配置

1.1 添加依赖

pubspec.yaml 中引入 built_value 相关套件。由于它依赖代码生成,必须配置 dev_dependencies

dependencies:
  built_value: ^8.9.2

dev_dependencies:
  build_runner: ^2.4.11
  built_value_generator: ^8.12.3

二、 核心实战:构建不可变用户模型

2.1 定义抽象协议 (user_model.dart)

BuiltValue 要求使用抽象类定义字段,剩下的繁琐实现交给生成器。

import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';

part 'user_model.g.dart';

abstract class UserModel implements Built<UserModel, UserModelBuilder> {
  int get id;
  String get name;
  String? get nickname; // 允许为空

  UserModel._();
  factory UserModel([void Function(UserModelBuilder) updates]) = _$UserModel;
  static Serializer<UserModel> get serializer => _$userModelSerializer;
}

在这里插入图片描述

2.2 配置全局序列化器 (serializers.dart)

为了让 BuiltValue 支持标准的 JSON 格式(Map<String, dynamic>),我们需要配置一个全局序列化器并安装 StandardJsonPlugin

import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'user_model.dart'; // 引入所有需要序列化的模型

part 'serializers.g.dart';

([
  UserModel, // 💡 在此注册所有模型
])
final Serializers serializers = (_$serializers.toBuilder()
      ..addPlugin(StandardJsonPlugin())) // 启用标准 JSON 插件
    .build();

在这里插入图片描述

2.3 触发代码生成

在终端执行指令,自动生成所有 .g.dart 补全文件:

dart run build_runner build --delete-conflicting-outputs

在这里插入图片描述


三、 鸿蒙平台的深度实践

3.1 值相等性与 UI 性能

在鸿蒙应用的列表(ListView/Grid)中,如果使用传统的 class,即便数值没变,由于指针不同,== 判定也会失败。BuiltValue 生成的对象支持值相等性:只要属性值完全一致,两个对象就视为同一个。这种特性配合 FlutterRepaintBoundary 能极大地优化鸿蒙端 UI 的局部渲染性能。

3.2 强类型 JSON 序列化

适配鸿蒙后端接口时,最怕字段缺失导致的崩溃。BuiltValue 的序列化器在遇到类型不匹配时会立即抛出清晰的异常。

实战演示

// 将对象转换为标准 JSON 字符串
final jsonObject = serializers.serializeWith(UserModel.serializer, user);
final jsonString = jsonEncode(jsonObject);

四、 避坑指南 (FAQ)

4.1 生成速度过慢?

解析:随着项目增大,代码生成会变慢,可能导致鸿蒙端热重载等待过久。
方案:创建 build.yaml,指定只扫描 lib/models/ 目录:

targets:
  $default:
    builders:
      built_value_generator:
        generate_for:
          - lib/models/*.dart

4.2 为什么不能直接修改属性?

设计理念:不可变性是为了防止副作用(Side Effects)。如果你想修改用户姓名,必须使用 rebuild 产生一个新实例:

var newUser = oldUser.rebuild((b) => b.name = '新名字');

五、完整示例

import 'dart:convert';
import 'package:flutter/material.dart';
import '../../models/built_value/user_model.dart';
import '../../models/built_value/serializers.dart';

// 💡 注意:在没有生成 .g.dart 文件前,这里使用 Mock 来演示 built_value 的核心理念:不可变性与 rebuild
class BuiltValueLabPage extends StatefulWidget {
  const BuiltValueLabPage({super.key});

  
  State<BuiltValueLabPage> createState() => _BuiltValueLabPageState();
}

class _BuiltValueLabPageState extends State<BuiltValueLabPage> {
  // 💡 使用真实的 BuiltValue 模型
  UserModel _user = UserModel((b) => b
    ..id = 1
    ..name = '鸿蒙专家'
    ..balance = 1024.0);

  String _jsonPreview = '';

  
  void initState() {
    super.initState();
    _updateJsonPreview();
  }

  void _updateJsonPreview() {
    // 💡 演示序列化:将强类型对象转换为标准 JSON 格式
    try {
      final jsonObject = serializers.serializeWith(UserModel.serializer, _user);
      _jsonPreview = const JsonEncoder.withIndent('  ').convert(jsonObject);
    } catch (e) {
      _jsonPreview = '序列化尚未就绪 (请确保已生成 serializers.g.dart)';
    }
  }

  void _rebuildProfile() {
    // 💡 演示真实的 rebuild:这就是不可变数据的魅力
    setState(() {
      _user = _user.rebuild((b) => b
        ..name = '开发者: ${_user.name.contains('专家') ? '极客' : '专家'}'
        ..balance = _user.balance + 100.0);

      _updateJsonPreview();
    });

    _showToast();
  }

  void _showToast() {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('模型已触发不可变更新,UI 响应重绘'),
        duration: Duration(seconds: 1),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('强类型模型实验室'),
        backgroundColor: Colors.teal,
        foregroundColor: Colors.white,
      ),
      body: Container(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            const Card(
              color: Color(0xFFE0F2F1),
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: Text(
                  '演示场景:通过 built_value 强制落实数据的“不可变性”。在鸿蒙高性能 App 中,这种模式能显著降低状态混乱引发的 Bug。',
                  style: TextStyle(color: Colors.teal),
                ),
              ),
            ),
            const SizedBox(height: 50),
            _buildProfileCard(),
            const Spacer(),
            SizedBox(
              width: double.infinity,
              height: 55,
              child: ElevatedButton.icon(
                onPressed: _rebuildProfile,
                icon: const Icon(Icons.auto_fix_high),
                label: const Text('触发模拟 Rebuild 更新'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.teal,
                  foregroundColor: Colors.white,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12)),
                ),
              ),
            ),
            const SizedBox(height: 24),
            const Text('实时序列化预览 (JSON):',
                style:
                    TextStyle(fontWeight: FontWeight.bold, color: Colors.grey)),
            const SizedBox(height: 8),
            Container(
              width: double.infinity,
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: const Color(0xFF263238),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(
                _jsonPreview.isEmpty ? '等待序列化...' : _jsonPreview,
                style: const TextStyle(
                    fontFamily: 'monospace',
                    color: Color(0xFF80CBC4),
                    fontSize: 12),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildProfileCard() {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 20,
            offset: const Offset(0, 10),
          )
        ],
      ),
      child: Column(
        children: [
          const CircleAvatar(
            radius: 40,
            backgroundColor: Colors.teal,
            child: Icon(Icons.person, size: 40, color: Colors.white),
          ),
          const SizedBox(height: 16),
          // 💡 渲染模型真实字段
          Text(_user.name,
              style:
                  const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Text('鸿蒙开发者余额: ¥${_user.balance.toStringAsFixed(2)}',
              style: const TextStyle(color: Colors.grey)),
          const SizedBox(height: 24),
          const Divider(),
          const SizedBox(height: 16),
          const Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _StatItem(label: '不可变', icon: Icons.lock_outline),
              _StatItem(label: '强类型', icon: Icons.verified_user_outlined),
              _StatItem(label: '高性能', icon: Icons.speed),
            ],
          )
        ],
      ),
    );
  }
}

class _StatItem extends StatelessWidget {
  final String label;
  final IconData icon;
  const _StatItem({required this.label, required this.icon});

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Icon(icon, color: Colors.teal, size: 20),
        const SizedBox(height: 4),
        Text(label, style: const TextStyle(fontSize: 12, color: Colors.teal)),
      ],
    );
  }
}

在这里插入图片描述

五、 总结

built_value 让鸿蒙应用的“地基(数据层)”变得稳如泰山。它不仅提供了强类型保护,还通过不可变特性强制开发者编写更纯净、可预测的代码。虽然增加了初次配置的复杂度,但从长远来看,它缩短了联调定位 Bug 的时间。


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

Logo

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

更多推荐