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

В чем разница между Subject и BehaviorSubject?

1.0 Junior🔥 171 комментариев
#Навигация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между Subject и BehaviorSubject

Subject и BehaviorSubject — это классы из библиотеки RxDart (реактивное программирование в Dart/Flutter). Они используются для управления асинхронными потоками данных и состоянием приложения.

Что такое Subject?

Subject — это специальный тип Stream, который может одновременно быть производителем (добавлять данные) и потребителем (слушать данные).

final subject = Subject<int>();

// Добавляем данные
subject.add(1);
subject.add(2);
subject.add(3);

// Слушаем данные
subject.listen((value) {
  print('Получено: $value');  // Получено: 1, 2, 3
});

Что такое BehaviorSubject?

BehaviorSubject — это улучшенная версия Subject, которая запоминает последнее значение и отправляет его новым подписчикам.

final behaviorSubject = BehaviorSubject<int>(seededValue: 0);

behaviorSubject.add(1);
behaviorSubject.add(2);
behaviorSubject.add(3);

// Новый подписчик получит последнее значение (3)
behaviorSubject.listen((value) {
  print('Получено: $value');  // Получено: 3
});

Главные отличия

ПараметрSubjectBehaviorSubject
Запоминает значениеНетДа (последнее значение)
Новому подписчикуНичего не отправляетОтправляет последнее значение
Начальное значениеНе требуетсяТребуется
ИспользованиеПростые событияСостояние приложения

Визуальное сравнение

Subject:
├── Добавляем значение 1
│   └── Подписчик 1 получает 1
├── Добавляем значение 2
│   ├── Подписчик 1 получает 2
│   └── Подписчик 2 получает 2
├── Добавляем значение 3
│   ├── Подписчик 1 получает 3
│   ├── Подписчик 2 получает 3
│   └── Подписчик 3 получает 3
└── Новый подписчик (подписчик 4) - не получает ничего!

BehaviorSubject:
├── Добавляем значение 1 (текущее: 1)
│   └── Подписчик 1 получает 1
├── Добавляем значение 2 (текущее: 2)
│   ├── Подписчик 1 получает 2
│   └── Подписчик 2 получает 2 (тут же текущее значение)
├── Добавляем значение 3 (текущее: 3)
│   ├── Подписчик 1 получает 3
│   └── Подписчик 2 получает 3
└── Новый подписчик (подписчик 3) - ПОЛУЧАЕТ 3 (последнее значение)!

Пример 1: Subject (не подходит для состояния)

class NotificationService {
  final notificationSubject = Subject<String>();

  void showNotification(String message) {
    notificationSubject.add(message);
  }
}

// В приложении
final notificationService = NotificationService();

// Подписчик 1 создан и слушает
notificationService.notificationSubject.listen(
  (message) => print('Пользователь 1: $message'),
);

// Отправляем уведомление
notificationService.showNotification('Привет!');
// Вывод: Пользователь 1: Привет!

// Подписчик 2 создан ПОЗЖЕ сообщения
notificationService.notificationSubject.listen(
  (message) => print('Пользователь 2: $message'),
);

// Подписчик 2 НЕ ПОЛУЧИТ предыдущее сообщение!
// Он получит только новые сообщения

notificationService.showNotification('Как дела?');
// Вывод:
// Пользователь 1: Как дела?
// Пользователь 2: Как дела?

Пример 2: BehaviorSubject (для состояния приложения)

class UserRepository {
  final userSubject = BehaviorSubject<User?>(seededValue: null);

  Stream<User?> get userStream => userSubject.stream;
  User? get currentUser => userSubject.value;  // доступ к текущему значению

  Future<void> fetchUser(String id) async {
    try {
      final user = await api.getUser(id);
      userSubject.add(user);
    } catch (e) {
      userSubject.addError(e);
    }
  }
}

// В приложении
final userRepo = UserRepository();

// Подписка в UI
class UserProfile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: userRepo.userStream,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          // Новый подписчик получит ТЕКУЩЕЕ значение
          // Не придётся ждать нового события
          return Text('User: ${snapshot.data?.name}');
        }
        return Text('Загрузка...');
      },
    );
  }
}

Когда использовать Subject?

Subject подходит для событий, которые не требуют запоминания:

// Клики кнопок — не нужно запоминать последний клик
final clickSubject = Subject<void>();

ElevatedButton(
  onPressed: () => clickSubject.add(null),
  child: Text('Нажми'),
)

// Подписка
clickSubject.listen((_) {
  print('Кнопка нажата');
});

Когда использовать BehaviorSubject?

BehaviorSubject подходит для состояния:

// Текущее состояние приложения
final appState = BehaviorSubject<AppState>(
  seededValue: AppState.initial(),
);

// Текущий пользователь
final currentUser = BehaviorSubject<User?>(
  seededValue: null,
);

// Сумма в корзине
final cartTotal = BehaviorSubject<double>(
  seededValue: 0.0,
);

// Любой новый UI может получить текущее значение
print('Текущий баланс: ${appState.value.balance}');

ReplaySubject

Третий вариант — ReplaySubject — запоминает последние N значений:

// Запомнит последние 5 значений
final replaySubject = ReplaySubject<int>(maxSize: 5);

replaySubject.add(1);
replaySubject.add(2);
replaySubject.add(3);

// Новый подписчик получит все 3 значения
replaySubject.listen((value) {
  print('Получено: $value');  // 1, 2, 3
});

Практический пример: управление состоянием

class AuthBloc {
  // BehaviorSubject запомнит текущего пользователя
  final _userController = BehaviorSubject<User?>(seededValue: null);
  final _loadingController = BehaviorSubject<bool>(seededValue: false);
  
  Stream<User?> get userStream => _userController.stream;
  Stream<bool> get loadingStream => _loadingController.stream;
  
  User? get currentUser => _userController.value;  // быстрый доступ
  bool get isLoading => _loadingController.value;

  Future<void> login(String email, String password) async {
    _loadingController.add(true);
    
    try {
      final user = await api.login(email, password);
      _userController.add(user);
    } catch (e) {
      _userController.addError(e);
    } finally {
      _loadingController.add(false);
    }
  }

  void logout() {
    _userController.add(null);
  }

  void dispose() {
    _userController.close();
    _loadingController.close();
  }
}

В современных решениях

В Riverpod и Provider BehaviorSubject встроен по умолчанию:

// Riverpod автоматически хранит состояние
final userProvider = StateNotifierProvider<UserNotifier, User?>(
  (ref) => UserNotifier(),
);

// Любой новый слушатель получит текущее значение

Вывод: Subject используй для событий (кликов, ввода), BehaviorSubject — для состояния. BehaviorSubject гарантирует, что новые подписчики получат текущее значение без необходимости ждать нового события. Это критично для управления состоянием приложения.

В чем разница между Subject и BehaviorSubject? | PrepBro