StreamNotifier
StreamNotifier<T> is Riverpod’s base class for creating and controlling streams with embedded business logic, exposed through a StreamNotifierProvider.
What is it?
StreamNotifier is used to manage stream-based state with full control over emission logic.
It allows you to:
- Create streams from scratch
- Transform or control emitted values
- Expose methods that influence the stream
- Access dependencies via
ref - Handle lifecycle-aware streaming
Unlike StreamProvider, which only consumes an existing stream, StreamNotifier owns the stream and its behavior.
Why does it exist?
In real applications, streams are rarely just passive data sources.
You often need to:
- Start/stop streams manually
- Apply filters or transformations
- Reconnect on failure
- Trigger events (send messages, refresh data)
- Combine multiple async sources
With StreamProvider, logic becomes external and scattered.
StreamNotifier solves this by centralizing:
- Stream creation
- Business logic
- Event control
- Dependency usage
It turns streaming into a managed state system, not just a data subscription.
Syntax
Creating a StreamNotifier
class CounterNotifier extends StreamNotifier<int> {
@override
Stream<int> build() async* {
var i = 0;
while (true) {
await Future.delayed(const Duration(seconds: 1));
yield i++;
}
}
}
Explanation:
build()defines the stream source.yieldemits values over time.- The stream is automatically managed by Riverpod.
Connecting with StreamNotifierProvider
final counterProvider =
StreamNotifierProvider<CounterNotifier, int>(
CounterNotifier.new,
);
Explanation:
- Exposes the notifier.
- State is consumed as
AsyncValue<int>.
Reading State
final value = ref.watch(counterProvider);
Explanation:
- Returns
AsyncValue<int>. - Updates on every stream emission.
Calling Methods
ref.read(counterProvider.notifier).restart();
Explanation:
- Accesses notifier instance.
- Allows controlling stream behavior.
Mental Model
Think of StreamNotifier as a stream engine with controls.
Widget
│
▼
StreamNotifierProvider
│
▼
StreamNotifier
┌──────┴──────┐
▼ ▼
Business Logic Stream
│
▼
AsyncValue
│
┌─────────┬─────────┬─────────┐
▼ ▼ ▼
Loading Data Error
Unlike passive streams, this one is actively controlled.
Examples
Timer Stream
class TimerNotifier extends StreamNotifier<int> {
@override
Stream<int> build() async* {
int seconds = 0;
while (true) {
await Future.delayed(const Duration(seconds: 1));
yield seconds++;
}
}
}
Explanation:
- Emits a value every second.
- Automatically updates UI.
Chat Messages
class ChatNotifier extends StreamNotifier<Message> {
@override
Stream<Message> build() {
return chatRepository.messageStream();
}
Future<void> sendMessage(String text) async {
await chatRepository.send(text);
}
}
Explanation:
- Streams incoming messages.
- Provides method to send messages.
Live Notifications
class NotificationNotifier
extends StreamNotifier<Notification> {
@override
Stream<Notification> build() {
return notificationService.stream();
}
void markAllAsRead() {
notificationService.clear();
}
}
Explanation:
- Streams notifications.
- Exposes actions for managing them.
State Flow
build() called
│
▼
AsyncLoading
│
▼
First event emitted
│
▼
AsyncData
│
▼
New event arrives
│
▼
AsyncData updated
│
▼
Error occurs
│
▼
AsyncError
The stream continuously updates state until disposed.
When to Use
Use StreamNotifier when:
- You need a controlled stream
- You must expose actions (send, restart, reset)
- Stream depends on business logic
- You combine multiple streams
- You manage real-time systems (chat, notifications, tracking)
When NOT to Use
Avoid StreamNotifier when:
- You only consume an existing stream → use
StreamProvider - You only need a one-time async operation → use
FutureProvider - You only need mutable async state → use
AsyncNotifier - You only need simple sync state → use
Notifier
Best Practices
- Prefer
StreamProviderif no control logic is needed - Keep stream creation inside
build() - Use methods for user-triggered events
- Avoid manual subscriptions
- Ensure streams are properly closed if needed
- Keep emitted models immutable
Common Mistakes
1. Using StreamNotifier for Simple Streams
❌ Wrong
class Messages extends StreamNotifier<Message> {
@override
Stream<Message> build() {
return repository.messages();
}
}
Why it's wrong:
- No additional logic or control is needed.
- Adds unnecessary complexity.
✔ Correct
final messagesProvider =
StreamProvider((ref) {
return repository.messages();
});
2. Doing Work in Widgets Instead of Notifier
❌ Wrong
socket.send(message);
Why it's wrong:
- Business logic leaks into UI layer.
✔ Correct
ref.read(chatProvider.notifier).sendMessage(message);
3. Mixing Stream and State Logic
❌ Wrong
state = AsyncData(stream.value);
Why it's wrong:
- Streams should not be manually converted into state.
✔ Correct
Let StreamNotifier handle emissions automatically.
Related APIs
- StreamNotifierProvider
- StreamProvider
- AsyncNotifier
- Notifier
- AsyncValue
- ref.watch()
- ref.read()
Summary
StreamNotifier is Riverpod’s advanced tool for managing controlled streams with business logic. It allows you to create, manage, and interact with streams while keeping state reactive, centralized, and lifecycle-aware. It is ideal for real-time systems where streams must be both consumed and controlled.