Что такое Subject в Dart (rxdart)?

Ответ

Subject — это специальный контроллер потока из библиотеки rxdart, который одновременно является и Stream (на него можно подписаться), и Sink (в него можно добавлять события). Это «горячий» поток, который начинает эмитировать данные сразу и может иметь несколько подписчиков.

Основные типы Subject в rxdart:

  1. PublishSubject: Передает только события, произошедшие после подписки. Не хранит предыдущие значения.
  2. BehaviorSubject: Кеширует и передает новым подписчикам последнее отправленное значение.
  3. ReplaySubject: Кеширует и передает новым подписчикам заданное количество последних значений.

Пример использования BehaviorSubject для управления состоянием:

import 'package:rxdart/rxdart.dart';

class AuthService {
  // Создаем Subject, который хранит последнее значение (начальное — false)
  final _isLoggedIn = BehaviorSubject<bool>.seeded(false);
  // Предоставляем поток для подписки в UI
  Stream<bool> get isLoggedInStream => _isLoggedIn.stream;

  void login() {
    // ... логика входа
    _isLoggedIn.add(true); // Уведомляем всех подписчиков
  }

  void logout() {
    // ... логика выхода
    _isLoggedIn.add(false);
  }

  void dispose() {
    _isLoggedIn.close(); // Важно закрыть при завершении
  }
}
// В UI:
StreamBuilder<bool>(
  stream: authService.isLoggedInStream,
  builder: (context, snapshot) {
    return snapshot.data == true ? HomePage() : LoginPage();
  },
)

Зачем использовать? Subject идеально подходит для ручного управления событиями в реактивном стиле, например, для стримов событий из UI (клики, текстовый ввод) или как основа для простых State Management решений.

Ответ 18+ 🔞

Давай разберём эту штуку, а то смотрю — народ путается, как слепой кот в подвале. Subject из rxdart — это такая универсальная палка-копалка, которая умеет и слушать, и говорить. Представь рацию: ты можешь в неё орать (это Sink), а другие могут эту речь ловить (это Stream). И самое главное — она «горячая», то есть вещает сразу, даже если никто не слушает, как сосед за стенкой в три ночи.

Какие бывают эти самые Subject, чтобы не путать их с чем попало:

  1. PublishSubject — как сплетня в очереди: передаёт только то, что услышал после того, как подошёл. Что было до — не в курсе, не хранит.
  2. BehaviorSubject — уже хитрая жопа: помнит последнюю переданную фигню и сразу плюёт её новому подписчику в ухо. Типа «на, держи, чувак, актуальное».
  3. ReplaySubject — вообще маньяк-архивариус: может запомнить овердохуища последних значений и вывалить их всем новеньким. Настраивается, конечно, но суть — помнит всё, как слон.

Вот тебе живой пример на BehaviorSubject, чтобы вообще ни хуя не осталось непонятного:

import 'package:rxdart/rxdart.dart';

class AuthService {
  // Создаём Subject, который изначально знает, что мы не залогинены (seeded(false))
  final _isLoggedIn = BehaviorSubject<bool>.seeded(false);
  // Даём наружу только поток для чтения, чтобы какой-нибудь распиздяй не накрутил туда левых данных
  Stream<bool> get isLoggedInStream => _isLoggedIn.stream;

  void login() {
    // ... там твоя логика входа, запросы на сервер и прочая муть
    _isLoggedIn.add(true); // БАМ! Кидаем всем подписчикам true. Все узнают, что зашли.
  }

  void logout() {
    // ... выходим, чистим куки, грустим
    _isLoggedIn.add(false); // И опять уведомляем мир. Реактивно, блядь!
  }

  // А это святое — не забыть закрыть кран, а то потечёт
  void dispose() {
    _isLoggedIn.close();
  }
}

// А в UI прикручиваем это дело через StreamBuilder, например:
StreamBuilder<bool>(
  stream: authService.isLoggedInStream,
  builder: (context, snapshot) {
    // snapshot.data — это наш булевский флаг
    return snapshot.data == true ? HomePage() : LoginPage();
  },
)

Зачем это всё, спросишь? Да затем, ёпта! Subject — это же ебать какая удобная штука, когда нужно вручную управлять потоком событий. Кликнули кнопку — subject.add(событие). Ввели текст — ещё add. Сделали из этого простенький state management для мелких задач — вообще красота. Главное — не забывай dispose(), а то будет тебе хиросима с утечками памяти.