Streams Basics
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 77 of 110.
While a Future delivers a single value, a Stream delivers a sequence of values over time. Think of it like a pipe that can emit multiple pieces of data - perfect for events, real-time updates, or processing data piece by piece.
You can create a simple stream using Stream.fromIterable() or an async generator function with async* and yield:
Stream<int> countToThree() async* {
yield 1;
yield 2;
yield 3;
}
Future<void> main() async {
await for (var number in countToThree()) {
print(number);
}
}
// Output: 1, 2, 3The async* keyword marks a function that returns a Stream, and yield emits each value. To consume the stream, use await for which processes each value as it arrives.
You can also listen to a stream using the listen() method, which takes a callback function:
Stream<String> greetings() async* {
yield 'Hello';
yield 'Hi';
yield 'Hey';
}
void main() {
greetings().listen((message) {
print(message);
});
}The key difference from Futures: a Future completes once with a single value, while a Stream can emit many values before it's done. This makes Streams ideal for handling ongoing data like user interactions, sensor readings, or data arriving in chunks.
Challenge
EasyLet's build a countdown timer system that emits values over time using streams! You'll create a class that generates a sequence of countdown numbers, demonstrating how streams deliver multiple values rather than just one.
You'll organize your code into two files:
countdown.dart: Create aCountdownclass that generates countdown streams. Your class should have:- A
startfield (int) that stores the starting number for the countdown - A constructor that takes the starting number
- A method
countDown()that returns aStream<int>. Useasync*andyieldto emit numbers fromstartdown to0(inclusive) - A method
countDownWithMessage()that returns aStream<String>. This should emit strings in the format'T-minus [number]'for each number fromstartdown to1, then emit'Liftoff!'as the final value
- A
main.dart: Import your countdown file and demonstrate both stream methods. Your main function should be async and:- Create a
Countdowninstance starting from3 - Print
Number countdown: - Use
await forto iterate throughcountDown()and print each number - Print an empty line
- Print
Launch sequence: - Use
await forto iterate throughcountDownWithMessage()and print each message
- Create a
Notice how each yield statement emits a value through the stream, and await for processes each value as it arrives. Unlike a Future that delivers one result, your streams will emit multiple values in sequence!
Expected output:
Number countdown:
3
2
1
0
Launch sequence:
T-minus 3
T-minus 2
T-minus 1
Liftoff!Cheat sheet
A Stream delivers a sequence of values over time, unlike a Future which delivers a single value. Streams are ideal for events, real-time updates, or processing data piece by piece.
Create a stream using an async generator function with async* and yield:
Stream<int> countToThree() async* {
yield 1;
yield 2;
yield 3;
}Consume a stream using await for:
Future<void> main() async {
await for (var number in countToThree()) {
print(number);
}
}Alternatively, use the listen() method with a callback:
void main() {
greetings().listen((message) {
print(message);
});
}You can also create a stream from an iterable using Stream.fromIterable().
Try it yourself
import 'countdown.dart';
Future<void> main() async {
// TODO: Create a Countdown instance starting from 3
// TODO: Print 'Number countdown:'
// TODO: Use await for to iterate through countDown() and print each number
// TODO: Print an empty line
// TODO: Print 'Launch sequence:'
// TODO: Use await for to iterate through countDownWithMessage() and print each message
}
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