【Flutter for OpenHarmony 】第三方库 聊天应用:Provider 状态管理实战指南

Flutter for OpenHarmony 聊天应用:Provider 状态管理实战指南

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

一、前言

在 Flutter 跨平台开发中,状态管理是决定应用性能与可维护性的核心环节。尤其是在 OpenHarmony 适配场景下,多端统一的状态管理方案能大幅降低适配成本。本文将以一款 Flutter 聊天应用为例,手把手带你实现基于 Provider 的全局状态管理,覆盖用户状态、聊天列表、通讯录三大核心场景,同时梳理开发中常见的错误与解决方案,所有代码均已在鸿蒙设备上验证通过。

二、技术选型与环境准备

2.1 为什么选择 Provider?

Provider 是 Flutter 官方推荐的轻量级状态管理方案,相比 Bloc、Riverpod 等方案,它上手成本低、性能开销小,完美适配聊天应用这类状态更新频率适中的场景,同时对 OpenHarmony 平台无额外依赖,兼容性极佳。

2.2 依赖配置

首先在 pubspec.yaml 中添加核心依赖,确保版本兼容鸿蒙平台:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.2
  shared_preferences: ^2.2.2
  shared_preferences_harmonyos: ^0.1.1 # 鸿蒙本地存储兼容

执行 flutter pub get 安装依赖,鸿蒙平台需额外执行 ohpm install 同步依赖。

三、核心实现:三大状态模块开发

3.1 用户状态管理(UserProvider)

用户状态是聊天应用的基础,需实现登录状态、用户信息持久化功能,同时适配鸿蒙本地存储。

1. 定义 User 模型

使用 Equatable 实现对象相等比较,避免不必要的 UI 重建:

dart 复制代码
import 'package:equatable/equatable.dart';

class User extends Equatable {
  final String id;
  final String nickname;
  final String avatarUrl;
  final bool isLoggedIn;

  const User({
    required this.id,
    required this.nickname,
    required this.avatarUrl,
    required this.isLoggedIn,
  });

  User copyWith({
    String? id,
    String? nickname,
    String? avatarUrl,
    bool? isLoggedIn,
  }) {
    return User(
      id: id ?? this.id,
      nickname: nickname ?? this.nickname,
      avatarUrl: avatarUrl ?? this.avatarUrl,
      isLoggedIn: isLoggedIn ?? this.isLoggedIn,
    );
  }

  @override
  List<Object?> get props => [id, nickname, avatarUrl, isLoggedIn];
}
2. 实现 UserProvider
dart 复制代码
import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/user.dart';

class UserProvider extends ChangeNotifier {
  User _user = const User(id: '', nickname: '', avatarUrl: '', isLoggedIn: false);
  User get user => _user;
  bool get isLoggedIn => _user.isLoggedIn;

  // 初始化用户状态(从本地存储读取)
  Future<void> initUser() async {
    final prefs = await SharedPreferences.getInstance();
    final userId = prefs.getString('user_id') ?? '';
    final nickname = prefs.getString('user_nickname') ?? '';
    final avatarUrl = prefs.getString('user_avatar') ?? '';
    final isLoggedIn = prefs.getBool('is_logged_in') ?? false;

    _user = User(
      id: userId,
      nickname: nickname,
      avatarUrl: avatarUrl,
      isLoggedIn: isLoggedIn,
    );
    notifyListeners();
  }

  // 登录方法
  Future<void> login(String id, String nickname, String avatarUrl) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('user_id', id);
    await prefs.setString('user_nickname', nickname);
    await prefs.setString('user_avatar', avatarUrl);
    await prefs.setBool('is_logged_in', true);

    _user = User(
      id: id,
      nickname: nickname,
      avatarUrl: avatarUrl,
      isLoggedIn: true,
    );
    notifyListeners();
  }

  // 登出方法
  Future<void> logout() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.clear();
    _user = const User(id: '', nickname: '', avatarUrl: '', isLoggedIn: false);
    notifyListeners();
  }
}

3.2 聊天状态管理(ChatProvider)

聊天列表需实现消息列表更新、未读消息计数、会话置顶等功能,同时支持 Mock 数据模拟。

1. 定义 ChatSession 模型
dart 复制代码
import 'package:equatable/equatable.dart';
import 'package:uuid/uuid.dart';

class ChatSession extends Equatable {
  final String id;
  final String contactId;
  final String contactName;
  final String contactAvatar;
  final String lastMessage;
  final DateTime lastMessageTime;
  final int unreadCount;
  final bool isPinned;

  ChatSession({
    String? id,
    required this.contactId,
    required this.contactName,
    required this.contactAvatar,
    required this.lastMessage,
    required this.lastMessageTime,
    required this.unreadCount,
    this.isPinned = false,
  }) : id = id ?? const Uuid().v4();

  ChatSession copyWith({
    String? id,
    String? contactId,
    String? contactName,
    String? contactAvatar,
    String? lastMessage,
    DateTime? lastMessageTime,
    int? unreadCount,
    bool? isPinned,
  }) {
    return ChatSession(
      id: id ?? this.id,
      contactId: contactId ?? this.contactId,
      contactName: contactName ?? this.contactName,
      contactAvatar: contactAvatar ?? this.contactAvatar,
      lastMessage: lastMessage ?? this.lastMessage,
      lastMessageTime: lastMessageTime ?? this.lastMessageTime,
      unreadCount: unreadCount ?? this.unreadCount,
      isPinned: isPinned ?? this.isPinned,
    );
  }

  @override
  List<Object?> get props => [
        id,
        contactId,
        contactName,
        contactAvatar,
        lastMessage,
        lastMessageTime,
        unreadCount,
        isPinned,
      ];
}
2. 实现 ChatProvider
dart 复制代码
import 'package:flutter/foundation.dart';
import '../models/chat_session.dart';

class ChatProvider extends ChangeNotifier {
  List<ChatSession> _sessions = [];
  List<ChatSession> get sessions => _sessions;

  // 初始化 Mock 数据
  void initMockSessions() {
    _sessions = [
      ChatSession(
        contactId: '1',
        contactName: '鸿蒙技术交流群',
        contactAvatar: 'https://example.com/group1.png',
        lastMessage: '今天的跨平台分享会谁参加?',
        lastMessageTime: DateTime.now().subtract(const Duration(minutes: 5)),
        unreadCount: 3,
        isPinned: true,
      ),
      ChatSession(
        contactId: '2',
        contactName: '李四',
        contactAvatar: 'https://example.com/user2.png',
        lastMessage: 'Flutter 鸿蒙适配文档发我一下',
        lastMessageTime: DateTime.now().subtract(const Duration(hours: 1)),
        unreadCount: 0,
      ),
    ];
    notifyListeners();
  }

  // 更新会话最后一条消息
  void updateSessionMessage(String sessionId, String message) {
    final index = _sessions.indexWhere((s) => s.id == sessionId);
    if (index != -1) {
      _sessions[index] = _sessions[index].copyWith(
        lastMessage: message,
        lastMessageTime: DateTime.now(),
        unreadCount: _sessions[index].unreadCount + 1,
      );
      notifyListeners();
    }
  }

  // 标记会话已读
  void markSessionRead(String sessionId) {
    final index = _sessions.indexWhere((s) => s.id == sessionId);
    if (index != -1) {
      _sessions[index] = _sessions[index].copyWith(unreadCount: 0);
      notifyListeners();
    }
  }

  // 置顶/取消置顶会话
  void togglePinSession(String sessionId) {
    final index = _sessions.indexWhere((s) => s.id == sessionId);
    if (index != -1) {
      _sessions[index] = _sessions[index].copyWith(
        isPinned: !_sessions[index].isPinned,
      );
      // 置顶会话移到列表顶部
      if (_sessions[index].isPinned) {
        final session = _sessions.removeAt(index);
        _sessions.insert(0, session);
      }
      notifyListeners();
    }
  }
}

3.3 通讯录状态管理(ContactProvider)

通讯录需实现联系人列表加载、搜索、在线状态更新功能。

1. 实现 ContactProvider
dart 复制代码
import 'package:flutter/foundation.dart';
import '../models/contact.dart';

class ContactProvider extends ChangeNotifier {
  List<Contact> _contacts = [];
  List<Contact> get contacts => _contacts;
  List<Contact> _filteredContacts = [];
  List<Contact> get filteredContacts => _filteredContacts;

  // 初始化 Mock 联系人数据
  void initMockContacts() {
    _contacts = [
      const Contact(
        id: '1',
        name: '张三',
        avatarUrl: 'https://example.com/user1.png',
        isOnline: true,
        remark: '鸿蒙技术爱好者',
      ),
      const Contact(
        id: '2',
        name: '李四',
        avatarUrl: 'https://example.com/user2.png',
        isOnline: false,
        remark: 'Flutter 开发者',
      ),
    ];
    _filteredContacts = _contacts;
    notifyListeners();
  }

  // 搜索联系人
  void searchContacts(String keyword) {
    if (keyword.isEmpty) {
      _filteredContacts = _contacts;
    } else {
      _filteredContacts = _contacts
          .where((contact) =>
              contact.name.toLowerCase().contains(keyword.toLowerCase()) ||
              (contact.remark?.toLowerCase().contains(keyword.toLowerCase()) ?? false))
          .toList();
    }
    notifyListeners();
  }

  // 更新联系人在线状态
  void updateContactOnlineStatus(String contactId, bool isOnline) {
    final index = _contacts.indexWhere((c) => c.id == contactId);
    if (index != -1) {
      _contacts[index] = _contacts[index].copyWith(isOnline: isOnline);
      // 同步更新过滤列表
      final filteredIndex = _filteredContacts.indexWhere((c) => c.id == contactId);
      if (filteredIndex != -1) {
        _filteredContacts[filteredIndex] = _filteredContacts[filteredIndex].copyWith(isOnline: isOnline);
      }
      notifyListeners();
    }
  }
}

四、全局状态注入与使用

4.1 根节点注入 Provider

main.dart 中使用 MultiProvider 注入所有状态,确保整个应用可访问:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/user_provider.dart';
import 'providers/chat_provider.dart';
import 'providers/contact_provider.dart';
import 'router/app_router.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => UserProvider()),
        ChangeNotifierProvider(create: (_) => ChatProvider()),
        ChangeNotifierProvider(create: (_) => ContactProvider()),
      ],
      child: MaterialApp.router(
        title: 'Flutter 鸿蒙聊天应用',
        theme: ThemeData(useMaterial3: true),
        routerConfig: AppRouter.router,
      ),
    );
  }
}

4.2 页面中使用状态

聊天列表页示例:
dart 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/chat_provider.dart';

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

  @override
  State<ChatHomePage> createState() => _ChatHomePageState();
}

class _ChatHomePageState extends State<ChatHomePage> {
  @override
  void initState() {
    super.initState();
    // 初始化聊天数据
    WidgetsBinding.instance.addPostFrameCallback((_) {
      context.read<ChatProvider>().initMockSessions();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('聊天')),
      body: Consumer<ChatProvider>(
        builder: (context, chatProvider, child) {
          return ListView.builder(
            itemCount: chatProvider.sessions.length,
            itemBuilder: (context, index) {
              final session = chatProvider.sessions[index];
              return ListTile(
                leading: CircleAvatar(
                  backgroundImage: NetworkImage(session.contactAvatar),
                ),
                title: Text(session.contactName),
                subtitle: Text(session.lastMessage),
                trailing: session.unreadCount > 0
                    ? Badge(label: Text(session.unreadCount.toString()))
                    : null,
                onTap: () {
                  chatProvider.markSessionRead(session.id);
                  // 跳转到聊天详情页
                },
              );
            },
          );
        },
      ),
    );
  }
}

五、常见错误与解决方案

错误1:状态更新但 UI 不重建

现象 :调用 notifyListeners() 后,页面没有更新。
原因

  1. 未使用 ConsumerProvider.of<T>(context, listen: true) 监听状态。
  2. ChangeNotifier 实例被重复创建,导致页面监听的是旧实例。
    解决方案
  • 确保在根节点使用 ChangeNotifierProvider 注入状态,避免在页面中直接 final provider = ChatProvider() 创建实例。
  • 检查 Consumer 的泛型类型是否与注入的 Provider 类型一致。

错误2:鸿蒙平台本地存储不生效

现象 :用户登录状态在鸿蒙设备上重启应用后丢失。
原因 :未引入 shared_preferences_harmonyos 适配库,鸿蒙平台无法兼容原生 shared_preferences
解决方案

  1. pubspec.yaml 中添加 shared_preferences_harmonyos 依赖。

  2. 鸿蒙平台需在 oh-package.json5 中同步依赖配置:

    json5 复制代码
    {
      "dependencies": {
        "shared_preferences_harmonyos": "0.1.1"
      }
    }

错误3:状态更新导致全页面重建

现象 :聊天列表更新一条消息,整个页面所有组件都重建,出现卡顿。
原因Consumer 包裹范围过大,或未使用 Equatable 优化对象比较。
解决方案

  • 缩小 Consumer 的包裹范围,只监听需要更新的组件。
  • 模型类继承 Equatable,重写 props,避免不必要的重建。

错误4:Provider 实例未初始化

现象 :页面中调用 context.read<UserProvider>() 抛出 ProviderNotFoundException
原因 :根节点未注入 Provider,或注入顺序错误。
解决方案

  • 确保 MultiProvider 包裹在 MaterialApp 外层。
  • 检查注入的 Provider 类型与页面中使用的泛型类型完全匹配。

六、总结

本文基于 Flutter for OpenHarmony 实现了聊天应用的 Provider 状态管理方案,覆盖用户、聊天、通讯录三大核心场景,同时解决了鸿蒙适配中的常见问题。Provider 作为轻量级状态管理方案,在跨平台开发中表现出优秀的兼容性与易用性,非常适合中小型应用的快速开发。后续可在此基础上扩展 WebSocket 实时通信、消息推送等功能,进一步完善聊天应用能力。

相关推荐
想你依然心痛2 小时前
HarmonyOS 6金融应用实战:基于悬浮导航与沉浸光感的“光影财富“智能投顾系统
金融·harmonyos·鸿蒙·悬浮导航·沉浸光感
互联网散修2 小时前
鸿蒙星闪实战:从零实现高速可靠的跨设备文件传输 - 星闪篇
华为·harmonyos
小红星闪啊闪2 小时前
鸿蒙开发速通(一)
harmonyos
特立独行的猫a2 小时前
HarmonyOS鸿蒙PC开源QT软件移植:移植开源文本编辑器 NotePad--(Ndd)到鸿蒙 PC实践总结
qt·开源·notepad++·harmonyos·notepad--·鸿蒙pc
IntMainJhy2 小时前
【futter for open harmony】Flutter 聊天应用实战:Material Design 3 全局 UI 规范落地指南✨
flutter·华为·harmonyos
IntMainJhy2 小时前
【flutter for open harmony】Flutter 聊天应用实战:go_router 路由管理完全实现指南
flutter·华为·harmonyos
liulian09162 小时前
【Flutter For OpenHarmony第三方库】Flutter 页面导航的鸿蒙化适配实战
flutter·华为·学习方法·harmonyos
南村群童欺我老无力.3 小时前
鸿蒙PC开发的borderWidth_API签名的类型陷阱
华为·harmonyos
前端不太难3 小时前
鸿蒙游戏 + AI:自动测试与自动发布
人工智能·游戏·harmonyos