← Назад к вопросам
Как организуешь взаимодействие между блоками?
2.3 Middle🔥 241 комментариев
#State Management#Архитектура Flutter#ООП и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как организуешь взаимодействие между блоками?
Взаимодействие между BLoC'ами — это критичная часть архитектуры больших Flutter приложений. Существует несколько подходов, каждый имеет свои плюсы и минусы.
1. Event-driven архитектура (рекомендуемый подход)
Один BLoC генерирует события, которые обрабатывает другой BLoC:
// UserBloc — генерирует события при изменении пользователя
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository userRepository;
UserBloc(this.userRepository) : super(UserInitial()) {
on<FetchUserEvent>((event, emit) async {
emit(UserLoading());
try {
final user = await userRepository.getUser(event.userId);
emit(UserLoaded(user: user));
} catch (e) {
emit(UserError(message: e.toString()));
}
});
}
}
// OrderBloc — слушает изменения User и обновляет заказы
class OrderBloc extends Bloc<OrderEvent, OrderState> {
final OrderRepository orderRepository;
final UserBloc userBloc;
late StreamSubscription userSubscription;
OrderBloc(this.orderRepository, this.userBloc) : super(OrderInitial()) {
// Подписываемся на изменения UserBloc
userSubscription = userBloc.stream.listen((userState) {
if (userState is UserLoaded) {
add(RefreshOrdersEvent(userId: userState.user.id));
}
});
on<RefreshOrdersEvent>((event, emit) async {
emit(OrderLoading());
try {
final orders = await orderRepository.getUserOrders(event.userId);
emit(OrderLoaded(orders: orders));
} catch (e) {
emit(OrderError(message: e.toString()));
}
});
}
@override
Future<void> close() {
userSubscription.cancel();
return super.close();
}
}
2. Event Bus (глобальный способ)
Используем глобальный event bus для коммуникации:
// Определяем события
abstract class AppEvent {}
class UserChangedEvent extends AppEvent {
final User user;
UserChangedEvent(this.user);
}
class ProductAddedEvent extends AppEvent {
final Product product;
ProductAddedEvent(this.product);
}
// Event Bus
class AppEventBus {
static final AppEventBus _instance = AppEventBus._internal();
final StreamController<AppEvent> _eventController = StreamController.broadcast();
AppEventBus._internal();
factory AppEventBus() {
return _instance;
}
Stream<AppEvent> get eventStream => _eventController.stream;
void emit(AppEvent event) {
_eventController.add(event);
}
void dispose() {
_eventController.close();
}
}
// Использование в BLoC
class CartBloc extends Bloc<CartEvent, CartState> {
final AppEventBus eventBus;
late StreamSubscription eventSubscription;
CartBloc(this.eventBus) : super(CartInitial()) {
eventSubscription = eventBus.eventStream.listen((event) {
if (event is ProductAddedEvent) {
add(AddProductToCartEvent(product: event.product));
}
});
on<AddProductToCartEvent>((event, emit) {
// Логика добавления товара
});
}
@override
Future<void> close() {
eventSubscription.cancel();
return super.close();
}
}
3. Shared Repository (через общий источник)
Используем общий Repository для синхронизации состояния:
// Общий репозиторий
class SharedDataRepository {
final _userController = StreamController<User>();
Stream<User> get userStream => _userController.stream;
void updateUser(User user) {
_userController.add(user);
}
void dispose() {
_userController.close();
}
}
// BLoC 1
class UserProfileBloc extends Bloc<ProfileEvent, ProfileState> {
final SharedDataRepository sharedData;
UserProfileBloc(this.sharedData) : super(ProfileInitial()) {
on<UpdateUserEvent>((event, emit) async {
// Обновляем в репозитории
sharedData.updateUser(event.user);
emit(ProfileUpdated(user: event.user));
});
}
}
// BLoC 2 — слушает изменения
class UserSettingsBloc extends Bloc<SettingsEvent, SettingsState> {
final SharedDataRepository sharedData;
late StreamSubscription userSubscription;
UserSettingsBloc(this.sharedData) : super(SettingsInitial()) {
userSubscription = sharedData.userStream.listen((user) {
add(UserChangedEvent(user: user));
});
on<UserChangedEvent>((event, emit) {
// Обновляем настройки на основе пользователя
emit(SettingsUpdated(preferences: _buildPreferences(event.user)));
});
}
@override
Future<void> close() {
userSubscription.cancel();
return super.close();
}
}
4. Dependency Injection
Передаём зависимости через конструкторы:
// С помощью GetIt
final getIt = GetIt.instance;
void setupServiceLocator() {
// Регистрируем репозитории
getIt.registerSingleton<UserRepository>(UserRepository());
getIt.registerSingleton<OrderRepository>(OrderRepository());
// Регистрируем BLoC'и с зависимостями
getIt.registerSingleton<UserBloc>(UserBloc(getIt<UserRepository>()));
getIt.registerSingleton<OrderBloc>(
OrderBloc(
getIt<OrderRepository>(),
getIt<UserBloc>(), // Передаём другой BLoC
),
);
}
// Использование
final userBloc = getIt<UserBloc>();
final orderBloc = getIt<OrderBloc>();
5. Hydrated BLoC (сохранение состояния)
Для синхронизации состояния между перезагрузками:
class AppStateBloc extends HydratedBloc<AppEvent, AppState> {
AppStateBloc() : super(AppState.initial()) {
on<UserChangedEvent>((event, emit) {
emit(state.copyWith(user: event.user));
});
}
@override
AppState? fromJson(Map<String, dynamic> json) {
return AppState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(AppState state) {
return state.toJson();
}
}
Лучшие практики
1. Избегайте циклических зависимостей
// ❌ Плохо: A зависит от B, B зависит от A
class BlocA extends Bloc<AEvent, AState> {
BlocA(BlocB blocB);
}
class BlocB extends Bloc<BEvent, BState> {
BlocB(BlocA blocA);
}
// ✅ Хорошо: использовать Event Bus или Repository
2. Всегда отписывайтесь от Stream
late StreamSubscription subscription;
@override
Future<void> close() {
subscription.cancel(); // Обязательно!
return super.close();
}
3. Используйте типизированные события
// ✅ Правильно
abstract class BlocEvent {}
class FetchEvent extends BlocEvent {
final String id;
FetchEvent(this.id);
}
// ❌ Неправильно
add("fetch_data"); // Не типизировано
4. Разделяйте ответственность
// ✅ Каждый BLoC отвечает за одну область
class AuthBloc {} // Аутентификация
class UserBloc {} // Данные пользователя
class ProductsBloc {} // Продукты
class CartBloc {} // Корзина
Выбор подхода
| Подход | Когда использовать | Плюсы | Минусы |
|---|---|---|---|
| Event-driven | БолЬшие приложения | Четкая архитектура | Сложность |
| Event Bus | Глобальные события | Простота | Может привести к спагетти коду |
| Shared Repository | Синхронизация данных | Понятно | Нужна хорошая архитектура |
| Dependency Injection | Любое приложение | Тестируемость | Требует фреймворка |
Для большинства приложений рекомендую комбинированный подход: использовать Event-driven для основного потока, Shared Repository для синхронизации данных, и Dependency Injection для управления зависимостями.