24  Stream

I’ll teach you about Dart streams by comparing them with Python concepts you already know. This will help bridge your existing knowledge to Dart’s approach.

24.1 What are Streams?

Streams in Dart are similar to Python generators and async iterators. They represent a sequence of asynchronous events - data that arrives over time rather than all at once.

Traditional List:    [1, 2, 3, 4, 5]  (all data available immediately)
Stream:             1 → 2 → 3 → 4 → 5  (data arrives over time)

24.2 Python vs Dart Comparison

Let me show you the equivalent concepts:

24.2.1 Python Generator (Synchronous)

def count_generator(to):
    for i in range(1, to + 1):
        yield 2 * i

# Usage
gen = count_generator(10)
total = sum(gen)
print(total)  # 110

24.2.2 Python Async Generator

import asyncio

async def count_async_generator(to):
    for i in range(1, to + 1):
        yield 2 * i

async def sum_async_gen(async_gen):
    total = 0
    async for value in async_gen:
        total += value
    return total

async def main():
    stream = count_async_generator(10)
    total = await sum_async_gen(stream)
    print(total)  # 110

asyncio.run(main())

24.2.3 Dart Stream (Your Example)

Stream<int> countStream(int to) async* {  // async* = async generator
  for (int i = 1; i <= to; i++) {
    yield 2*i;  // Same yield keyword as Python
  }
}

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (final value in stream) {  // await for = async for in Python
    sum += value;
  }
  return sum;
}

24.3 Key Dart Stream Concepts

24.3.1 1. Stream Creation (async* and yield)

// async* creates a stream generator (like Python's async def with yield)
Stream<String> dataStream() async* {
  yield 'First';   // Emit single value
  yield 'Second';  
  yield* anotherStream();  // yield* = yield from in Python
}

Python equivalent:

async def data_stream():
    yield 'First'
    yield 'Second'
    yield from another_stream()  # yield from

24.3.2 2. Stream Consumption (await for)

// await for is like Python's async for
await for (final item in stream) {
  print(item);
}

Python equivalent:

async for item in stream:
    print(item)

24.3.3 3. Stream Transformation (Like Python’s map, filter)

Stream<int> numbers = Stream.fromIterable([1, 2, 3, 4, 5]);

// Transform (like Python's map)
var doubled = numbers.map((n) => n * 2);

// Filter (like Python's filter) 
var evenOnly = numbers.where((n) => n % 2 == 0);

// Chain operations (like Python's method chaining)
var result = numbers
    .where((n) => n % 2 == 0)
    .map((n) => n * 2);

Python equivalent:

numbers = [1, 2, 3, 4, 5]

# Transform
doubled = map(lambda n: n * 2, numbers)

# Filter  
even_only = filter(lambda n: n % 2 == 0, numbers)

# Chain (more Pythonic way)
result = [n * 2 for n in numbers if n % 2 == 0]

24.4 Analyzing Your Example

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield 2*i;  // Yields: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
  }
}

This creates a stream that emits: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 Sum = 2+4+6+8+10+12+14+16+18+20 = 110 (not 55 as commented)

24.5 Common Stream Patterns

24.5.1 1. Single-Subscription vs Broadcast Streams

// Single-subscription (like Python generator - can only iterate once)
Stream<int> singleUse = countStream(5);

// Broadcast (multiple listeners, like Python list)
Stream<int> broadcast = countStream(5).asBroadcastStream();

24.5.2 2. Stream Methods (Similar to Python’s itertools)

var stream = Stream.fromIterable([1, 2, 3, 4, 5]);

// Take first n (like itertools.islice)
var firstThree = stream.take(3);

// Skip first n  
var skipTwo = stream.skip(2);

// Reduce (like Python's reduce)
var sum = await stream.reduce((a, b) => a + b);

// Convert to list (like list(generator) in Python)
var list = await stream.toList();

24.6 Real-World Example: HTTP Requests

Here’s how streams are useful for real-world async operations:

Stream<String> fetchDataStream() async* {
  for (int page = 1; page <= 3; page++) {
    // Simulate API call delay
    await Future.delayed(Duration(seconds: 1));
    yield 'Data from page $page';
  }
}

void main() async {
  print('Starting data fetch...');
  await for (final data in fetchDataStream()) {
    print('Received: $data');
  }
  print('All data fetched!');
}

This is similar to Python’s approach of yielding results as they become available, rather than waiting for all data before returning.

The key insight is that Dart streams are essentially asynchronous iterators - just like Python’s async generators, but with more built-in methods for transformation and handling.