Streams in Dart are a powerful way to handle asynchronous data. They allow you to work with sequences of data that are delivered asynchronously, such as user inputs, file I/O, or network requests. In this section, we will cover the basics of streams, how to create and use them, and some common patterns and practices.

Key Concepts

  1. Stream: A sequence of asynchronous events.
  2. StreamController: A controller that allows you to create and manage a stream.
  3. StreamSubscription: An object that represents a subscription to a stream.
  4. Single-subscription Stream: A stream that can only be listened to once.
  5. Broadcast Stream: A stream that can be listened to multiple times.

Creating Streams

Using StreamController

A StreamController is used to create a stream and add events to it.

import 'dart:async';

void main() {
  // Create a StreamController
  final controller = StreamController<int>();

  // Get the stream from the controller
  final stream = controller.stream;

  // Listen to the stream
  stream.listen((data) {
    print('Received: $data');
  });

  // Add data to the stream
  controller.add(1);
  controller.add(2);
  controller.add(3);

  // Close the stream
  controller.close();
}

Explanation

  • StreamController: Manages the stream and allows adding events.
  • stream.listen: Subscribes to the stream and handles incoming data.
  • controller.add: Adds data to the stream.
  • controller.close: Closes the stream when done.

Types of Streams

Single-subscription Stream

A single-subscription stream can only be listened to once.

void main() {
  final controller = StreamController<int>();

  final stream = controller.stream;

  stream.listen((data) {
    print('Received: $data');
  });

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

  controller.close();
}

Broadcast Stream

A broadcast stream can be listened to multiple times.

void main() {
  final controller = StreamController<int>.broadcast();

  final stream = controller.stream;

  stream.listen((data) {
    print('Listener 1: $data');
  });

  stream.listen((data) {
    print('Listener 2: $data');
  });

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

  controller.close();
}

Practical Example: Timer Stream

Let's create a stream that emits a value every second.

import 'dart:async';

void main() {
  final controller = StreamController<int>();
  int counter = 0;

  Timer.periodic(Duration(seconds: 1), (timer) {
    counter++;
    controller.add(counter);
    if (counter == 5) {
      controller.close();
      timer.cancel();
    }
  });

  controller.stream.listen((data) {
    print('Timer: $data');
  });
}

Explanation

  • Timer.periodic: Creates a timer that runs a callback every second.
  • controller.add: Adds the current counter value to the stream.
  • controller.close: Closes the stream after 5 seconds.

Common Patterns

Transforming Streams

You can transform streams using methods like map, where, and expand.

void main() {
  final controller = StreamController<int>();

  final stream = controller.stream;

  final transformedStream = stream.map((data) => 'Number: $data');

  transformedStream.listen((data) {
    print(data);
  });

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

  controller.close();
}

Combining Streams

You can combine multiple streams using methods like merge and zip.

import 'dart:async';

void main() {
  final controller1 = StreamController<int>();
  final controller2 = StreamController<int>();

  final stream1 = controller1.stream;
  final stream2 = controller2.stream;

  final combinedStream = StreamZip([stream1, stream2]);

  combinedStream.listen((data) {
    print('Combined: $data');
  });

  controller1.add(1);
  controller2.add(2);
  controller1.add(3);
  controller2.add(4);

  controller1.close();
  controller2.close();
}

Exercises

Exercise 1: Create a Stream

Create a stream that emits the numbers 1 to 10 and prints each number.

import 'dart:async';

void main() {
  final controller = StreamController<int>();

  final stream = controller.stream;

  stream.listen((data) {
    print('Number: $data');
  });

  for (int i = 1; i <= 10; i++) {
    controller.add(i);
  }

  controller.close();
}

Exercise 2: Transform a Stream

Create a stream that emits the numbers 1 to 5, transforms each number to its square, and prints the result.

import 'dart:async';

void main() {
  final controller = StreamController<int>();

  final stream = controller.stream;

  final transformedStream = stream.map((data) => data * data);

  transformedStream.listen((data) {
    print('Square: $data');
  });

  for (int i = 1; i <= 5; i++) {
    controller.add(i);
  }

  controller.close();
}

Summary

In this section, we covered the basics of streams in Dart, including how to create and use them, the difference between single-subscription and broadcast streams, and some common patterns like transforming and combining streams. Streams are a powerful tool for handling asynchronous data, and understanding them is crucial for effective Dart programming.

© Copyright 2024. All rights reserved