<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/flutter-streams-explained-listen-cancel-and-streambuilder-4wumgjgry" -->

---
title: Flutter Streams, Explained: listen(), cancel(), and...
description: A tutorial explains Dart's Stream type by contrasting it with Future, then walks through listen() callbacks, StreamSubscription lifecycle management (cancel in...
canonical: https://daily.dev/posts/flutter-streams-explained-listen-cancel-and-streambuilder-4wumgjgry
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Flutter Streams, Explained: listen(), cancel(), and StreamBuilder | daily.dev
og:description: A tutorial explains Dart's Stream type by contrasting it with Future, then walks through listen() callbacks, StreamSubscription lifecycle management (cancel in...
og:url: https://daily.dev/posts/flutter-streams-explained-listen-cancel-and-streambuilder-4wumgjgry
og:image: https://api.daily.dev/og/posts/4wUmGjgRY.png
og:image:alt: Flutter Streams, Explained: listen(), cancel(), and StreamBuilder
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter Streams, Explained: listen(), cancel(), and StreamBuilder

**[Kidpech Pianpithak](https://daily.dev/sources/8zbnrh39r9oomsddnxj0u)** · [@kidpechpianpithak](https://daily.dev/kidpechpianpithak) · 4 min read · 2 upvotes · 0 comments

## Summary

A tutorial explains Dart's Stream type by contrasting it with Future, then walks through listen() callbacks, StreamSubscription lifecycle management (cancel in dispose), and StreamBuilder for driving Flutter UI from stream data. Includes runnable Dart and Flutter code examples, a decision table for choosing between Future, Stream, listen(), and StreamBuilder, and a note about single-subscription vs broadcast streams.

## Content

If `Future` makes sense but `Stream` still feels abstract, think of a photo and a live video. A `Future<T>` eventually completes once, with a value or an error. A `Stream<T>` can deliver a sequence of events over time. A GPS location, a WebSocket message, or an authentication change is a natural example: the next update may arrive later, and there may be many updates.

![Future versus Stream timeline](https://media.daily.dev/image/upload/s--x2HoxYI1--/f_auto/v1790664945/ugc/content_e1eed44d-9f1e-4de9-a377-2b35617743e9?_a=BAMAMicg0)

The timeline is the key idea. A stream can emit zero or more data events, error events, and finally a _done_ event. Some streams, such as a live connection, may keep running until you stop listening or the source closes.

## What does `listen()` actually do?

`listen()` attaches a callback to a stream. Every data event calls that callback. It returns a `StreamSubscription<T>`, which represents **your active connection to that stream**.

Here is a complete Dart example. You can paste it into `main.dart` and run it with `dart run main.dart`:

```
void main() {
  final ticks = Stream<int>.periodic(
    const Duration(seconds: 1),
    (index) => index + 1,
  ).take(3);

  ticks.listen(
    (value) => print('data: $value'),
    onError: (Object error) => print('error: $error'),
    onDone: () => print('done'),
  );
}
```

The output arrives over time:

```
after 1 second  data: 1
after 2 seconds data: 2
after 3 seconds data: 3
                done
```

`take(3)` makes this stream finite. Without it, `Stream.periodic` would keep producing values. `onError` handles error events, while `onDone` runs when the stream closes. Canceling a subscription stops delivery to that listener; it does **not** call `onDone`.

## Why keep the subscription?

The return value of `listen()` lets you stop listening when the owner goes away:

```
final subscription = stream.listen(onData);

// Later, when this listener is no longer needed:
await subscription.cancel();
```

This matters with long running sources such as location updates or sockets. After cancellation, the subscription receives no more events. The source may also use cancellation to release resources; `cancel()` returns a `Future<void>` so you can wait for that cleanup when later work depends on it.

In a Flutter `State`, subscribe during `initState()` and cancel during `dispose()`:

```
StreamSubscription<String>? _subscription;

@override
void initState() {
  super.initState();
  _subscription = events.listen(
    (message) => debugPrint('Event: $message'),
    onError: (Object error) => debugPrint('Error: $error'),
  );
}

@override
void dispose() {
  _subscription?.cancel();
  super.dispose();
}
```

In this snippet, `events` is an existing `Stream<String>` owned or supplied by the widget. If the widget can receive a _different stream instance_ later, handle that change in `didUpdateWidget()` too. Never create a new subscription inside `build()`: rebuilding can happen many times.

## When the stream drives the UI, use `StreamBuilder`

If your only goal is “new value → new widget,” `StreamBuilder` handles the subscription lifecycle. This complete Flutter example renders a live counter:

```
import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: LiveCounterPage()));

class LiveCounterPage extends StatefulWidget {
  const LiveCounterPage({super.key});

  @override
  State<LiveCounterPage> createState() => _LiveCounterPageState();
}

class _LiveCounterPageState extends State<LiveCounterPage> {
  late final Stream<int> _ticks;

  @override
  void initState() {
    super.initState();
    _ticks = Stream<int>.periodic(
      const Duration(seconds: 1),
      (index) => index + 1,
    ).take(5);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Live counter')),
      body: Center(
        child: StreamBuilder<int>(
          stream: _ticks,
          initialData: 0,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            }

            final status = switch (snapshot.connectionState) {
              ConnectionState.waiting => 'waiting',
              ConnectionState.done => 'done',
              _ => 'active',
            };

            return Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  '${snapshot.data ?? 0}',
                  style: Theme.of(context).textTheme.displayLarge,
                ),
                Text(status),
              ],
            );
          },
        ),
      ),
    );
  }
}
```

![Illustrative live counter frames](https://media.daily.dev/image/upload/s--cGE-Dr87--/f_auto/v1790664963/ugc/content_ac3f97f9-3eb3-4691-b70a-57a4fa28d8c2?_a=BAMAMicg0)

Notice that `_ticks` is created once in `initState()`. Creating a new stream inside `build()` can restart the asynchronous work whenever the parent rebuilds. `initialData: 0` gives the first frame a useful value before the first event arrives. Flutter may render only a subsequence of very fast stream events, so use `listen()` for work that must run for **every** event and `StreamBuilder` for displaying the latest state.

## A quick decision guide

- **One eventual result:** `Future<T>`
- **A changing value over time:** `Stream<T>`
- **A callback or side effect for each event:** `listen()` plus a managed subscription
- **A widget that displays the latest value:** `StreamBuilder<T>`

One more trap: most streams are _single subscription_. If you see “Stream has already been listened to,” check whether your code subscribed twice or rebuilt with a new listener. Use a broadcast stream only when the source truly needs multiple concurrent listeners.

**The mental model:** `Stream` is the event source; `listen()` starts your subscription; `cancel()` ends your subscription; `StreamBuilder` connects a stream to UI and manages that connection for you.

What Stream bug took you the longest to spot?

#Flutter #Dart #Streams

Sources: [Dart Streams](https://dart.dev/libraries/async/using-streams) · [Stream.listen API](https://api.dart.dev/dart-async/Stream/listen.html) · [StreamSubscription.cancel API](https://api.dart.dev/dart-async/StreamSubscription/cancel.html) · [Flutter StreamBuilder API](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)

## Similar posts on daily.dev

- [How to Use Streams in Flutter](https://daily.dev/posts/how-to-use-streams-in-flutter-snnsiyope) · freeCodeCamp · 5 upvotes · 0 comments
- [Async widgets Future Builder \(Part -1\)](https://daily.dev/posts/async-widgets-future-builder-part--1--qdwufihmx) · Medium · 0 upvotes · 0 comments
- [Advanced Dart: Learn Asynchronous Programming with Streams, Isolates, and the Event Loop](https://daily.dev/posts/advanced-dart-learn-asynchronous-programming-with-streams-isolates-and-the-event-loop-ovmnj6jcy) · freeCodeCamp · 3 upvotes · 0 comments
- [One Badge, Three Deliveries: How Android, Flutter, and Compose Keep Your UI in Sync](https://daily.dev/posts/one-badge-three-deliveries-how-android-flutter-and-compose-keep-your-ui-in-sync-fph6mmdcv) · Medium · 0 upvotes · 0 comments
- [Flutter ChangeNotifier Explained: Efficient UI State Management Without Rebuilding Your Entire App](https://daily.dev/posts/flutter-changenotifier-explained-efficient-ui-state-management-without-rebuilding-your-entire-app-c6slr6s9r) · Medium · 0 upvotes · 0 comments

---

Tags: [#mobile](https://daily.dev/tags/mobile), [#flutter](https://daily.dev/tags/flutter), [#dart](https://daily.dev/tags/dart)

[View this post on daily.dev](https://daily.dev/posts/flutter-streams-explained-listen-cancel-and-streambuilder-4wumgjgry)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"DiscussionForumPosting","mainEntityOfPage":"https://daily.dev/posts/flutter-streams-explained-listen-cancel-and-streambuilder-4wumgjgry","headline":"Flutter Streams, Explained: listen(), cancel(), and StreamBuilder","text":"A tutorial explains Dart's Stream type by contrasting it with Future, then walks through listen() callbacks, StreamSubscription lifecycle management (cancel in dispose), and StreamBuilder for driving Flutter UI from stream data. Includes runnable Dart and Flutter code examples, a decision table for choosing between Future, Stream, listen(), and StreamBuilder, and a note about single-subscription vs broadcast streams.","url":"https://daily.dev/posts/flutter-streams-explained-listen-cancel-and-streambuilder-4wumgjgry","datePublished":"2026-09-29T06:58:15.806Z","dateModified":"2026-09-29T07:00:12.917Z","author":{"@type":"Person","name":"Kidpech Pianpithak","url":"https://daily.dev/kidpechpianpithak","image":"https://avatars.githubusercontent.com/u/85755801?v=4","description":"Senior Application Developer | Flutter & Go | Open Source Creator","interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"EndorseAction"},"userInteractionCount":210}},"image":"https://media.daily.dev/image/upload/s--ePiX3MKJ--/f_auto/v1790665097/posts/4wUmGjgRY?_a=BAMAMicg0","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"isPartOf":{"@type":"WebPage","url":"https://daily.dev/sources/8zbnrh39r9oomsddnxj0u","name":"Kidpech Pianpithak"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Kidpech Pianpithak","item":"https://daily.dev/sources/8zbnrh39r9oomsddnxj0u"},{"@type":"ListItem","position":3,"name":"Flutter Streams, Explained: listen(), cancel(), and StreamBuilder"}]}
```

