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
EasyLet'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 aMessageHubclass 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
messagesthat 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
- Use a broadcast
main.dart: Import your message hub and set up a scenario with multiple subscribers listening to the same stream:- Create a
MessageHubinstance - 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, andGoodbye - Shut down the hub
- Create a
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: GoodbyeCheat 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
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor12Async OOP
Futures & async/awaitStreams BasicsStream ControllersAsync ConstructorsAsync in Class MethodsRecap - Data Fetcher