← Назад к вопросам

Как организуешь взаимодействие между блоками?

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 для управления зависимостями.

Как организуешь взаимодействие между блоками? | PrepBro