Flutter 三方库 get_it + flutter_bloc 的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
Hey 大家好!,上海某高校计算机专业大一学生 🏗️!今天来聊聊 Flutter 开发中两个非常重要的基础设施------依赖注入 和状态管理!
做小项目的时候,可能觉得不需要这些。但当你写一个完整的聊天 App,涉及到十几个服务、几十个页面的时候,就会发现:代码乱成一团、状态难以追踪、服务难以复用......这时候,你就需要 get_it 和 flutter_bloc 了!
一、为什么需要依赖注入和状态管理?
没有管理的困境 😱
做聊天 App 的时候,你可能遇到过这些问题:
A页面用了 ChatService
B页面也用了 ChatService
C页面也用了 ChatService
每个页面都 new 一个新实例?
→ 状态不同步!
→ 内存浪费!
→ 代码重复!
解决方案 🛠️
- 依赖注入 (DI):统一管理所有服务,全局只创建一个实例
- 状态管理:统一管理应用状态,让 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 开发的黄金搭档!它们帮我解决了:
- 代码组织:所有服务统一管理,不再到处 new
- 状态可追踪:BLoC 让状态变化清晰可见
- 易于测试:服务可以轻松 mock
- 团队协作:代码结构规范,新人容易上手
给新手的话:
虽然这些概念一开始会觉得复杂,但一旦理解了就回不去!它们是 Flutter 开发的"基础设施",值得投入时间学习。建议从简单的单 Bloc 开始,逐步掌握事件和状态的编写方式。
今天的分享就到这里!有问题评论区见!