В чем разница между Stream и Iterable в Dart?

«В чем разница между Stream и Iterable в Dart?» — вопрос из категории Dart Core, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В разработке на Dart и Flutter я работаю с обоими типами коллекций, но они предназначены для разных сценариев.

Iterable представляет синхронную, лениво вычисляемую последовательность. Я часто использую его для обработки данных в памяти:

// Пример из реального проекта: обработка списка пользователей
List<User> users = [/* ... */];

// Цепочка преобразований с Iterable
final activeAdults = users
    .where((user) => user.isActive)
    .where((user) => user.age >= 18)
    .map((user) => user.toJson())
    .toList();

// Ленивое вычисление - данные обрабатываются только при итерации
final expensiveCalculation = users.map((user) {
  print('Вычисление для ${user.name}');
  return calculateSomethingExpensive(user);
});
// Ничего не выведется пока не начнем итерацию

Stream представляет асинхронную последовательность событий. В Flutter-приложениях я использую Stream для работы с событиями UI и сетевыми запросами:

// Пример: обработка потока событий от Firebase Firestore
Stream<List<Message>> getMessages(String chatId) {
  return FirebaseFirestore.instance
      .collection('chats')
      .doc(chatId)
      .collection('messages')
      .orderBy('timestamp', descending: true)
      .limit(50)
      .snapshots()
      .map((snapshot) => snapshot.docs
          .map((doc) => Message.fromFirestore(doc))
          .toList());
}

// Использование в виджете
StreamBuilder<List<Message>>(
  stream: getMessages(chatId),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return ListView.builder(
        itemCount: snapshot.data!.length,
        itemBuilder: (context, index) {
          return MessageWidget(snapshot.data![index]);
        },
      );
    }
    return CircularProgressIndicator();
  },
)

Основные различия в моей практике:

  1. Синхронность vs Асинхронность:

    • Iterable: данные доступны сразу, блокирует выполнение
    • Stream: данные поступают со временем, не блокирует UI
  2. Многократность использования:

    • Iterable можно итерировать многократно
    • Single-subscription Stream можно слушать только один раз
  3. Использование в Flutter:

    • Iterable: для статических данных, фильтрации, преобразований
    • Stream: для real-time обновлений, событий пользователя, WebSockets
  4. Ошибки и завершение:

    • Iterable либо возвращает все значения, либо бросает исключение
    • Stream может эмитировать данные, ошибки и сигнал о завершении

В сложных приложениях я часто комбинирую оба подхода, например, преобразуя Stream данных в Iterable для batch-обработки.