Flutter 三方库 get_it + flutter_bloc 的鸿蒙化适配与实战指南

Flutter 三方库 get_it + flutter_bloc 的鸿蒙化适配与实战指南


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

Hey 大家好!,上海某高校计算机专业大一学生 🏗️!今天来聊聊 Flutter 开发中两个非常重要的基础设施------依赖注入状态管理

做小项目的时候,可能觉得不需要这些。但当你写一个完整的聊天 App,涉及到十几个服务、几十个页面的时候,就会发现:代码乱成一团、状态难以追踪、服务难以复用......这时候,你就需要 get_itflutter_bloc 了!

一、为什么需要依赖注入和状态管理?

没有管理的困境 😱

做聊天 App 的时候,你可能遇到过这些问题:

复制代码
A页面用了 ChatService
B页面也用了 ChatService
C页面也用了 ChatService
每个页面都 new 一个新实例?
→ 状态不同步!
→ 内存浪费!
→ 代码重复!

解决方案 🛠️

  1. 依赖注入 (DI):统一管理所有服务,全局只创建一个实例
  2. 状态管理:统一管理应用状态,让 UI 和数据分离

二、get_it - 依赖注入

简介

get_it 是一个轻量级的服务定位器(Service Locator)实现。它可以让你:

  • 轻松注册服务
  • 随时获取服务
  • 管理服务生命周期

依赖配置

yaml 复制代码
dependencies:
  get_it: ^8.0.2

AtomGit 适配说明:纯 Dart 库,无平台特定代码,鸿蒙零适配成本!

三、flutter_bloc - 状态管理

简介

flutter_bloc 实现了 BLoC 模式(Business Logic Component),核心思想是:

  • 事件 (Event):用户行为
  • 状态 (State):界面展示
  • BLoC:处理逻辑

依赖配置

yaml 复制代码
dependencies:
  flutter_bloc: ^8.1.6
  bloc: ^8.1.4
  equatable: ^2.0.5

AtomGit 适配说明:纯 Dart 库,无平台特定代码,鸿蒙零适配成本!

四、创建服务定位器

服务定位器初始化

dart 复制代码
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import '../services/notification_service.dart';
import '../services/socket_io_service.dart';
import '../services/image_processing_service.dart';
import '../services/audio_service.dart';
import '../services/share_service.dart';
import '../services/link_preview_service.dart';
import '../services/chat_service.dart';
import '../services/hive_service.dart';
import '../services/websocket_service.dart';

/// 依赖注入配置
/// 使用 get_it 实现服务定位器模式
final GetIt getIt = GetIt.instance;

/// 初始化所有服务【重点方法】
Future<void> setupServiceLocator() async {
  // ========== 注册服务 ==========

  // 通知服务(单例 - 全局唯一实例)
  getIt.registerSingleton<NotificationService>(NotificationService.instance);

  // Socket.IO服务(单例)
  getIt.registerSingleton<SocketIOService>(SocketIOService.instance);

  // 图片处理服务(单例)
  getIt.registerSingleton<ImageProcessingService>(ImageProcessingService.instance);

  // 音频服务(单例)
  getIt.registerSingleton<AudioService>(AudioService.instance);

  // 分享服务(单例)
  getIt.registerSingleton<ShareService>(ShareService.instance);

  // 链接预览服务(单例)
  getIt.registerSingleton<LinkPreviewService>(LinkPreviewService.instance);

  // WebSocket服务(单例)
  getIt.registerSingleton<WebSocketService>(WebSocketService.instance);

  // Chat服务(单例)
  getIt.registerSingleton<ChatService>(ChatService());

  // Hive服务(单例)
  getIt.registerSingleton<HiveService>(HiveService());

  // ========== 初始化 ==========

  // 初始化本地存储
  await HiveService.init();

  // 初始化通知服务
  await NotificationService.instance.initialize();

  debugPrint('所有服务注册完成!');
}

服务的不同注册方式

方式 说明 使用场景
registerSingleton 整个应用只有一个实例,app 结束才销毁 全局共享服务,如配置、用户信息
registerFactory 每次 get 都会创建新实例 需要独立状态的实例
registerLazySingleton 第一次使用时才创建实例 占用资源较多但不一定用到的服务
dart 复制代码
// 1. 单例模式(推荐)- 整个应用只有一个实例
getIt.registerSingleton<MyService>(MyService());

// 2. 工厂模式 - 每次 get 都会创建新实例
getIt.registerFactory<MyService>(() => MyService());

// 3. 单例懒加载 - 第一次使用时才创建实例
getIt.registerLazySingleton<MyService>(() => MyService());

如何使用服务

dart 复制代码
// 在任意地方获取服务
final notificationService = getIt<NotificationService>();

// 调用服务方法
await notificationService.showNotification(
  title: '新消息',
  body: '你有一条新消息',
);

// 在 Bloc 中使用
class ChatBloc extends Bloc<ChatEvent, ChatState> {
  // 【这种方式获取服务 - 依赖注入】
  final NotificationService _notificationService = getIt<NotificationService>();
  final SocketIOService _socketService = getIt<SocketIOService>();

  // ...
}

五、创建 BLoC

BLoC 的核心是三个部分:事件 (Event)状态 (State)BLoC 本身。让我们一步步来实现。

1. 定义事件 (Events)

事件描述用户的所有可能操作:

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

/// Chat BLoC 事件
/// 描述所有可能的用户操作
abstract class ChatEvent extends Equatable {
  const ChatEvent();

  @override
  List<Object?> get props => [];
}

/// 加载消息
class LoadMessages extends ChatEvent {
  final String conversationId;
  const LoadMessages(this.conversationId);

  @override
  List<Object?> get props => [conversationId];
}

/// 发送消息
class SendMessage extends ChatEvent {
  final String content;
  final MessageType type;
  const SendMessage({required this.content, this.type = MessageType.text});

  @override
  List<Object?> get props => [content, type];
}

/// 收到新消息
class NewMessageReceived extends ChatEvent {
  final ChatMessage message;
  const NewMessageReceived(this.message);

  @override
  List<Object?> get props => [message];
}

/// 删除消息
class DeleteMessage extends ChatEvent {
  final String messageId;
  const DeleteMessage(this.messageId);

  @override
  List<Object?> get props => [messageId];
}

/// 标记消息已读
class MarkAsRead extends ChatEvent {
  final String messageId;
  const MarkAsRead(this.messageId);

  @override
  List<Object?> get props => [messageId];
}

/// 连接状态变化
class ConnectionStatusChanged extends ChatEvent {
  final bool isConnected;
  const ConnectionStatusChanged(this.isConnected);

  @override
  List<Object?> get props => [isConnected];
}

/// 对方正在输入
class TypingStatusChanged extends ChatEvent {
  final bool isTyping;
  final String? userName;
  const TypingStatusChanged({required this.isTyping, this.userName});

  @override
  List<Object?> get props => [isTyping, userName];
}

2. 定义状态 (State)

状态描述界面可能的展示状态:

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

/// Chat BLoC 状态
/// 描述聊天界面可能的展示状态
enum ChatStatus { initial, loading, loaded, error }

class ChatState extends Equatable {
  final ChatStatus status;  // 加载状态
  final List<ChatMessage> messages;  // 消息列表
  final String? conversationId;  // 当前对话ID
  final bool isConnected;  // 连接状态
  final bool isTyping;  // 对方是否在输入
  final String? typingUserName;  // 正在输入的用户名
  final String? errorMessage;  // 错误信息
  final bool hasMoreMessages;  // 是否还有更多历史消息

  const ChatState({
    this.status = ChatStatus.initial,
    this.messages = const [],
    this.conversationId,
    this.isConnected = false,
    this.isTyping = false,
    this.typingUserName,
    this.errorMessage,
    this.hasMoreMessages = true,
  });

  /// 复制状态(不可变类 - 每次返回新实例)
  ChatState copyWith({
    ChatStatus? status,
    List<ChatMessage>? messages,
    String? conversationId,
    bool? isConnected,
    bool? isTyping,
    String? typingUserName,
    String? errorMessage,
    bool? hasMoreMessages,
  }) {
    return ChatState(
      status: status ?? this.status,
      messages: messages ?? this.messages,
      conversationId: conversationId ?? this.conversationId,
      isConnected: isConnected ?? this.isConnected,
      isTyping: isTyping ?? this.isTyping,
      typingUserName: typingUserName ?? this.typingUserName,
      errorMessage: errorMessage ?? this.errorMessage,
      hasMoreMessages: hasMoreMessages ?? this.hasMoreMessages,
    );
  }

  @override
  List<Object?> get props => [
    status, messages, conversationId, isConnected,
    isTyping, typingUserName, errorMessage, hasMoreMessages,
  ];
}

3. 创建 BLoC

BLoC 是连接事件和状态的桥梁:

dart 复制代码
// chat_bloc.dart
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/chat_message_model.dart';
import '../../services/chat_service.dart';
import '../../services/socket_io_service.dart';
import '../../services/notification_service.dart';
import 'package:get_it/get_it.dart';  // 引入 get_it
import 'chat_event.dart';
import 'chat_state.dart';

/// Chat BLoC
/// 管理聊天页面的所有业务逻辑
class ChatBloc extends Bloc<ChatEvent, ChatState> {
  // 通过 get_it 获取服务(依赖注入)
  final SocketIOService _socketService = getIt<SocketIOService>();
  final NotificationService _notificationService = getIt<NotificationService>();

  StreamSubscription? _messageSubscription;
  StreamSubscription? _statusSubscription;
  StreamSubscription? _typingSubscription;

  ChatBloc() : super(const ChatState()) {
    // 注册事件处理
    on<LoadMessages>(_onLoadMessages);
    on<SendMessage>(_onSendMessage);
    on<NewMessageReceived>(_onNewMessageReceived);
    on<DeleteMessage>(_onDeleteMessage);
    on<MarkAsRead>(_onMarkAsRead);
    on<ConnectionStatusChanged>(_onConnectionStatusChanged);
    on<TypingStatusChanged>(_onTypingStatusChanged);

    // 初始化 Socket 监听
    _initSocketListeners();
  }

  /// 初始化 Socket 监听
  void _initSocketListeners() {
    // 监听新消息
    _messageSubscription = _socketService.messageStream.listen((data) {
      final message = ChatMessage(
        id: data['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
        content: data['content'] ?? '',
        senderId: data['senderId'] ?? '',
        senderName: data['senderName'] ?? '',
        timestamp: data['timestamp'] != null
            ? DateTime.parse(data['timestamp'])
            : DateTime.now(),
        type: MessageType.text,
        isMe: data['senderId'] == 'me',
        status: MessageStatus.delivered,
      );
      add(NewMessageReceived(message));
    });

    // 监听连接状态
    _statusSubscription = _socketService.statusStream.listen((status) {
      add(ConnectionStatusChanged(status == 'connected'));
    });

    // 监听输入状态
    _typingSubscription = _socketService.typingStream.listen((data) {
      add(TypingStatusChanged(
        isTyping: data['isTyping'] ?? false,
        userName: data['userName'],
      ));
    });
  }

  /// 加载消息
  Future<void> _onLoadMessages(
    LoadMessages event,
    Emitter<ChatState> emit,
  ) async {
    emit(state.copyWith(status: ChatStatus.loading));

    try {
      final conversation = ChatService.getOrCreateConversation(
        event.conversationId,
        event.conversationId[0],
      );

      emit(state.copyWith(
        status: ChatStatus.loaded,
        messages: conversation.messages,
        conversationId: event.conversationId,
      ));
    } catch (e) {
      emit(state.copyWith(
        status: ChatStatus.error,
        errorMessage: e.toString(),
      ));
    }
  }

  /// 发送消息
  Future<void> _onSendMessage(
    SendMessage event,
    Emitter<ChatState> emit,
  ) async {
    if (state.conversationId == null) return;

    // 1. 创建消息
    final message = ChatMessage(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      content: event.content,
      senderId: 'me',
      senderName: '我',
      timestamp: DateTime.now(),
      type: event.type,
      isMe: true,
      status: MessageStatus.sending,
    );

    // 2. 更新状态(乐观更新 - 先显示再发送)
    final updatedMessages = List<ChatMessage>.from(state.messages)..add(message);
    emit(state.copyWith(messages: updatedMessages));

    // 3. 发送消息
    _socketService.sendMessage(
      conversationId: state.conversationId!,
      content: event.content,
      type: event.type.name,
    );
  }

  /// 收到新消息
  void _onNewMessageReceived(
    NewMessageReceived event,
    Emitter<ChatState> emit,
  ) {
    final updatedMessages = List<ChatMessage>.from(state.messages)
      ..add(event.message);
    emit(state.copyWith(messages: updatedMessages));

    // 如果是收到的消息,发送通知
    if (!event.message.isMe) {
      _notificationService.showChatNotification(
        senderName: event.message.senderName,
        message: event.message.content,
        conversationId: state.conversationId ?? 'unknown',
      );
    }
  }

  /// 删除消息
  void _onDeleteMessage(
    DeleteMessage event,
    Emitter<ChatState> emit,
  ) {
    final updatedMessages = state.messages
        .where((m) => m.id != event.messageId)
        .toList();
    emit(state.copyWith(messages: updatedMessages));
  }

  /// 标记已读
  void _onMarkAsRead(
    MarkAsRead event,
    Emitter<ChatState> emit,
  ) {
    final updatedMessages = state.messages.map((m) {
      if (m.id == event.messageId) {
        return ChatMessage(
          id: m.id,
          content: m.content,
          senderId: m.senderId,
          senderName: m.senderName,
          timestamp: m.timestamp,
          type: m.type,
          isMe: m.isMe,
          status: MessageStatus.read,
        );
      }
      return m;
    }).toList();
    emit(state.copyWith(messages: updatedMessages));
  }

  /// 连接状态变化
  void _onConnectionStatusChanged(
    ConnectionStatusChanged event,
    Emitter<ChatState> emit,
  ) {
    emit(state.copyWith(isConnected: event.isConnected));
  }

  /// 输入状态变化
  void _onTypingStatusChanged(
    TypingStatusChanged event,
    Emitter<ChatState> emit,
  ) {
    emit(state.copyWith(
      isTyping: event.isTyping,
      typingUserName: event.userName,
    ));
  }

  @override
  Future<void> close() {
    // 清理资源 - 重要!防止内存泄漏
    _messageSubscription?.cancel();
    _statusSubscription?.cancel();
    _typingSubscription?.cancel();
    return super.close();
  }
}

六、在页面中使用 BLoC

完整页面示例

dart 复制代码
/// 聊天详情页面
class ChatDetailPage extends StatelessWidget {
  const ChatDetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 通过 BlocProvider 提供 BLoC
    return BlocProvider(
      create: (_) => ChatBloc()..add(const LoadMessages('conversation_1')),
      child: const _ChatDetailView(),
    );
  }
}

class _ChatDetailView extends StatelessWidget {
  const _ChatDetailView();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // 监听连接状态
        title: BlocBuilder<ChatBloc, ChatState>(
          buildWhen: (prev, curr) => prev.isConnected != curr.isConnected,
          builder: (context, state) {
            return Row(
              children: [
                Container(
                  width: 10,
                  height: 10,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: state.isConnected ? Colors.green : Colors.red,
                  ),
                ),
                const SizedBox(width: 8),
                const Text('聊天'),
              ],
            );
          },
        ),
      ),
      body: Column(
        children: [
          // 消息列表
          Expanded(
            child: BlocBuilder<ChatBloc, ChatState>(
              buildWhen: (prev, curr) => prev.messages != curr.messages,
              builder: (context, state) {
                if (state.status == ChatStatus.loading) {
                  return const Center(child: CircularProgressIndicator());
                }
                if (state.status == ChatStatus.error) {
                  return Center(
                    child: Text('Error: ${state.errorMessage}'),
                  );
                }
                return ListView.builder(
                  reverse: true,
                  itemCount: state.messages.length,
                  itemBuilder: (context, index) {
                    return ChatBubble(message: state.messages[index]);
                  },
                );
              },
            ),
          ),
          // 输入状态提示
          BlocBuilder<ChatBloc, ChatState>(
            buildWhen: (prev, curr) => prev.isTyping != curr.isTyping,
            builder: (context, state) {
              if (state.isTyping) {
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text(
                    '${state.typingUserName ?? '对方'} 正在输入...',
                    style: TextStyle(color: Colors.grey[600], fontSize: 12),
                  ),
                );
              }
              return const SizedBox.shrink();
            },
          ),
          // 输入区域
          const ChatInputArea(),
        ],
      ),
    );
  }
}

七、鸿蒙化实现

虽然 Flutter 版本的 get_it + flutter_bloc 可以在鸿蒙上运行,但 OpenHarmony 原生开发可以使用以下方案:

依赖注入 - 原生实现

typescript 复制代码
// service_locator.ets - 简单的服务定位器
export class ServiceLocator {
  private static instance: ServiceLocator
  private services: Map<string, Object> = new Map()

  static getInstance(): ServiceLocator {
    if (!ServiceLocator.instance) {
      ServiceLocator.instance = new ServiceLocator()
    }
    return ServiceLocator.instance
  }

  registerSingleton<T>(serviceName: string, service: T): void {
    this.services.set(serviceName, service)
  }

  get<T>(serviceName: string): T {
    return this.services.get(serviceName) as T
  }
}

// 使用
const locator = ServiceLocator.getInstance()
locator.registerSingleton('ChatService', new ChatService())
const chatService = locator.get<ChatService>('ChatService')

状态管理 - Observable + Observer

typescript 复制代码
// 简单的响应式状态管理
class Observable<T> {
  private value: T
  private listeners: Set<(value: T) => void> = new Set()

  constructor(initialValue: T) {
    this.value = initialValue
  }

  get(): T {
    return this.value
  }

  set(newValue: T): void {
    this.value = newValue
    this.listeners.forEach(listener => listener(newValue))
  }

  subscribe(listener: (value: T) => void): () => void {
    this.listeners.add(listener)
    return () => this.listeners.delete(listener)
  }
}

// 使用示例
class ChatStore {
  messages = new Observable<ChatMessage[]>([])
  isConnected = new Observable<boolean>(false)

  addMessage(message: ChatMessage) {
    this.messages.set([...this.messages.get(), message])
  }

  loadMessages(conversationId: string) {
    // 加载消息
    this.messages.set(mockMessages)
  }
}

八、踩坑纪实

踩坑1:BlocProvider 忘记包裹 💀

一开始我在子页面创建 Bloc,结果状态不对------因为父组件不监听变化。解决方案:

dart 复制代码
// 错误 ❌
Widget build(BuildContext context) {
  return BlocProvider(
    create: (_) => ChatBloc(),
    child: BlocBuilder<ChatBloc, ChatState>(
      // 这里的状态不会更新,因为不在同一棵 Bloc 树下
      builder: ...
    ),
  );
}

// 正确 ✅
Widget build(BuildContext context) {
  return BlocProvider(
    create: (_) => ChatBloc()..add(LoadMessages('xxx')),  // 创建时加载数据
    child: BlocBuilder<ChatBloc, ChatState>(
      builder: (context, state) {
        // 正确!状态会更新
        return ...
      },
    ),
  );
}

踩坑2:get_it 类型不匹配 🔧

服务注册和获取时类型不一致:

dart 复制代码
// 错误 ❌
getIt.registerSingleton<ChatService>(ChatService.instance);
// 获取时却写 getIt<ChatService>()  // 类型不匹配!

// 正确 ✅
getIt.registerSingleton<ChatService>(ChatService());
// 获取时 getIt<ChatService>() // 类型一致

踩坑3:Bloc 内存泄漏 💥

忘记取消 Stream 订阅,导致内存泄漏:

dart 复制代码
@override
Future<void> close() {
  // 一定要在这里取消订阅!否则会造成内存泄漏
  _messageSubscription?.cancel();
  _statusSubscription?.cancel();
  _typingSubscription?.cancel();
  return super.close();
}

踩坑4:copyWith 忘记包含所有字段 🐛

状态更新时,copyWith 要包含所有可能变化的字段:

dart 复制代码
// 错误 ❌ - 只更新了 messages,丢失了 isConnected
emit(state.copyWith(messages: updatedMessages));

// 正确 ✅
emit(state.copyWith(
  messages: updatedMessages,
  isConnected: state.isConnected,  // 显式传递
));

九、效果展示

模拟数据示例

dart 复制代码
// 模拟消息数据
final mockMessages = [
  ChatMessage(
    id: '1',
    content: '嗨,你好!',
    senderId: 'user_001',
    senderName: '小美',
    timestamp: DateTime.now().subtract(const Duration(hours: 2)),
    type: MessageType.text,
    isMe: false,
    status: MessageStatus.read,
  ),
  ChatMessage(
    id: '2',
    content: '你好呀!最近怎么样?',
    senderId: 'me',
    senderName: '我',
    timestamp: DateTime.now().subtract(const Duration(hours: 1, minutes: 55)),
    type: MessageType.text,
    isMe: true,
    status: MessageStatus.read,
  ),
  ChatMessage(
    id: '3',
    content: '挺好的!正在学习 Flutter',
    senderId: 'user_001',
    senderName: '小美',
    timestamp: DateTime.now().subtract(const Duration(hours: 1, minutes: 50)),
    type: MessageType.text,
    isMe: false,
    status: MessageStatus.delivered,
  ),
];

运行效果


功能验证结果:

  • ✅ 依赖注入正常工作
  • ✅ 服务全局唯一实例
  • ✅ BLoC 状态管理正常
  • ✅ 事件触发状态更新
  • ✅ UI 正确响应状态变化
  • ✅ 内存泄漏已修复

十、总结心得

get_it + flutter_bloc 简直是 Flutter 开发的黄金搭档!它们帮我解决了:

  1. 代码组织:所有服务统一管理,不再到处 new
  2. 状态可追踪:BLoC 让状态变化清晰可见
  3. 易于测试:服务可以轻松 mock
  4. 团队协作:代码结构规范,新人容易上手

给新手的话:

虽然这些概念一开始会觉得复杂,但一旦理解了就回不去!它们是 Flutter 开发的"基础设施",值得投入时间学习。建议从简单的单 Bloc 开始,逐步掌握事件和状态的编写方式。


今天的分享就到这里!有问题评论区见!

相关推荐
maaath1 小时前
【maaath】Flutter for OpenHarmony 定位服务能力集成指南
flutter·华为·harmonyos
maaath2 小时前
【maaath】Flutter for OpenHarmony分类筛选与标签匹配深度剖析
flutter·华为·harmonyos
isyangli_blog3 小时前
华为企业级虚拟化解决方案
华为
说再见再也见不到3 小时前
华为交换机QoS配置一条龙:从基础模型到MQC实战
华为·交换机·qos·端口限速
耳東陈4 小时前
Flutter开箱即用一站式解决方案5.0-ComDraggable悬浮拖拽
flutter
Lanren的编程日记4 小时前
Flutter 鸿蒙应用快捷操作功能实战:快捷菜单+快捷手势+快捷键支持,打造高效操作体验
flutter·华为·harmonyos
memoryjs4 小时前
鸿蒙系统进一步学习(二):ArkUI底层原理揭秘
学习·华为·harmonyos
木斯佳4 小时前
HarmonyOS 本地存储实战:用一个记账本案例吃透 RDB 与 KVStore
harmonyos·存储
苗俊祥4 小时前
纯AI打造沐界输入法--简洁、流畅、实用的 HarmonyOS 中文输入法
华为·harmonyos