Menu
Coddy logo textTech

Stream Controllers

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 78 of 110.

While async* generators are great for creating streams with predefined data, sometimes you need more control - like adding data to a stream from different parts of your code. That's where StreamController comes in.

A StreamController acts as a manager for a stream. It gives you a "sink" to add data and a "stream" that listeners can subscribe to:

import 'dart:async';

void main() {
  var controller = StreamController<String>();
  
  // Listen to the stream
  controller.stream.listen((data) {
    print('Received: $data');
  });
  
  // Add data through the sink
  controller.add('Hello');
  controller.add('World');
  
  controller.close();  // Always close when done
}

The add() method pushes new values into the stream, and any listeners receive them immediately. You should always call close() when you're finished to release resources.

By default, a stream can only have one listener. For multiple listeners, use a broadcast controller:

import 'dart:async';

void main() {
  var controller = StreamController<int>.broadcast();
  
  controller.stream.listen((n) => print('Listener 1: $n'));
  controller.stream.listen((n) => print('Listener 2: $n'));
  
  controller.add(42);
  controller.close();
}

StreamControllers are essential in OOP for building event-driven classes - like notifying multiple parts of your app when data changes, handling user actions, or managing real-time updates within your objects.

challenge icon

Challenge

Easy

Let's build a message hub system that allows multiple subscribers to receive messages in real-time! You'll use a StreamController to manage the flow of messages and demonstrate how broadcast streams enable multiple listeners.

You'll organize your code into two files:

  • message_hub.dart: Create a MessageHub class that manages message distribution using a StreamController. Your hub should:
    • Use a broadcast StreamController<String> internally so multiple listeners can subscribe
    • Provide a getter called messages that returns the stream for listeners to subscribe to
    • Have a broadcast(String message) method that adds a message to the stream
    • Have a shutdown() method that closes the controller
  • main.dart: Import your message hub and set up a scenario with multiple subscribers listening to the same stream:
    • Create a MessageHub instance
    • Add a first listener that prints messages in the format: Subscriber A: [message]
    • Add a second listener that prints messages in the format: Subscriber B: [message]
    • Broadcast three messages: Hello everyone, Welcome to the hub, and Goodbye
    • Shut down the hub

Remember to import dart:async in your message hub file to access StreamController. Notice how both subscribers receive every message that gets broadcast - that's the power of broadcast streams!

Expected output:

Subscriber A: Hello everyone
Subscriber B: Hello everyone
Subscriber A: Welcome to the hub
Subscriber B: Welcome to the hub
Subscriber A: Goodbye
Subscriber B: Goodbye

Cheat sheet

A StreamController manages a stream, providing control over when and how data is added. It gives you a "sink" to add data and a "stream" that listeners can subscribe to.

Basic usage:

import 'dart:async';

var controller = StreamController<String>();

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

// Add data through the sink
controller.add('Hello');
controller.add('World');

controller.close();  // Always close when done

The add() method pushes new values into the stream. Listeners receive them immediately. Always call close() when finished to release resources.

By default, a stream can only have one listener. For multiple listeners, use a broadcast controller:

var controller = StreamController<int>.broadcast();

controller.stream.listen((n) => print('Listener 1: $n'));
controller.stream.listen((n) => print('Listener 2: $n'));

controller.add(42);
controller.close();

StreamControllers are essential for building event-driven classes, notifying multiple parts of your app when data changes, handling user actions, or managing real-time updates.

Try it yourself

import 'message_hub.dart';

void main() {
  // TODO: Create a MessageHub instance
  
  // TODO: Add first listener that prints: Subscriber A: [message]
  
  // TODO: Add second listener that prints: Subscriber B: [message]
  
  // TODO: Broadcast three messages:
  // - "Hello everyone"
  // - "Welcome to the hub"
  // - "Goodbye"
  
  // TODO: Shut down the hub
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming