flutter
/

Flutter StreamBuilder Widget Tutorial for Beginners

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter StreamBuilder Widget Tutorial for Beginners

What is StreamBuilder in Flutter?

StreamBuilder is a widget that listens to a Stream and rebuilds its UI whenever a new event is emitted. It's perfect for handling real‑time data like live chats, location updates, Firebase snapshots, or any continuous flow of data. StreamBuilder works similarly to FutureBuilder, but for multiple asynchronous events over time.

Basic Usage

To use StreamBuilder, you provide a stream and a builder function. The builder is called with an AsyncSnapshot each time the stream emits data, completes, or throws an error.

DARTRead-only
1
StreamBuilder<int>(
  stream: _counterStream(), // your stream
  builder: (context, snapshot) {
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    if (!snapshot.hasData) {
      return CircularProgressIndicator();
    }
    return Text('Count: ${snapshot.data}');
  },
)

Understanding AsyncSnapshot with Streams

The AsyncSnapshot for a stream has the same properties as for a future, but the connectionState can be more nuanced:

  • connectionState:
    • ConnectionState.none: No stream provided yet.
    • ConnectionState.waiting: Stream is established but no data received yet.
    • ConnectionState.active: Stream is active and data is arriving.
    • ConnectionState.done: Stream has completed (closed).
  • hasData: Whether the snapshot contains non‑null data (the latest emitted value).
  • data: The latest value emitted by the stream.
  • hasError: Whether the stream has emitted an error.
  • error: The error object (if any).

Building UI for Different States

A typical pattern handles waiting, active, error, and done states. Here's a complete example:

DARTRead-only
1
StreamBuilder<int>(
  stream: _timerStream,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else if (!snapshot.hasData) {
      return Text('No data');
    } else {
      return Text('Current number: ${snapshot.data}');
    }
  },
)

Note: For streams that never complete (like a continuous timer), connectionState will remain ConnectionState.active forever. You should rely on hasData to check if a value is available.

Real‑time Example: A Counter Stream

Let's create a simple stream that emits an incrementing number every second and use StreamBuilder to display it.

DARTRead-only
1
class CounterStreamExample extends StatefulWidget {
  @override
  _CounterStreamExampleState createState() => _CounterStreamExampleState();
}

class _CounterStreamExampleState extends State<CounterStreamExample> {
  late Stream<int> _counterStream;

  @override
  void initState() {
    super.initState();
    _counterStream = _createCounterStream();
  }

  Stream<int> _createCounterStream() async* {
    int i = 0;
    while (true) {
      await Future.delayed(Duration(seconds: 1));
      yield i++;
    }
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: _counterStream,
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        if (!snapshot.hasData) {
          return CircularProgressIndicator();
        }
        return Text(
          'Count: ${snapshot.data}',
          style: TextStyle(fontSize: 48),
        );
      },
    );
  }
}

Important: Where to Create the Stream

Just like with FutureBuilder, you should never create the stream directly inside the build method. That would cause a new stream to be created on every rebuild, breaking the connection and causing memory leaks. Instead, create the stream in initState (for a stateful widget) or keep it as a final variable in a provider / repository.

Handling Stream Errors and Completion

Streams can emit errors or complete. Use snapshot.hasError to show an error widget, and you can detect completion by checking snapshot.connectionState == ConnectionState.done. For example, you might show a message when a live stream ends.

DARTRead-only
1
if (snapshot.connectionState == ConnectionState.done) {
  return Text('Stream finished');
}

Common Mistakes Beginners Make

  • Creating the stream inside build: Leads to stream re‑creation, losing previous events and causing memory leaks.
  • Not handling hasData correctly: For streams that may not emit data immediately, always check snapshot.hasData before accessing snapshot.data.
  • Forgetting to cancel streams: In a real app, you should cancel the stream subscription when the widget is disposed (e.g., using StreamController and disposing in dispose).
  • Misunderstanding connectionState: For infinite streams, connectionState is always ConnectionState.active. Don't wait for done to show data.
  • Assuming the stream will provide data immediately: Show a loading indicator until hasData becomes true.
  • Not handling errors: Always include an error case to avoid crashing the app.

Key Points to Remember

  • StreamBuilder listens to a stream and rebuilds on every new event.
  • Use AsyncSnapshot properties: connectionState, hasData, data, hasError, error.
  • For infinite streams, check hasData rather than waiting for ConnectionState.done.
  • Create the stream once (e.g., in initState) and reuse it.
  • Always handle errors and loading states for a robust UI.
  • Remember to cancel stream subscriptions in dispose if you manage streams manually (e.g., with StreamController).

Common Interview Questions

  1. How does StreamBuilder differ from FutureBuilder?
  2. What are the possible values of ConnectionState and what do they mean for a stream?
  3. Why should you avoid creating a stream inside the build method?
  4. How would you handle a stream that might not emit data immediately?
  5. How do you cancel a stream subscription when using StreamBuilder?
  6. Can StreamBuilder be used with a broadcast stream? What's the difference?
  7. How do you show a loading indicator while waiting for the first data from a stream?

Try it yourself

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('StreamBuilder Example')),
        body: Center(
          child: StreamExample(),
        ),
      ),
    );
  }
}

class StreamExample extends StatefulWidget {
  @override
  _StreamExampleState createState() => _StreamExampleState();
}

class _StreamExampleState extends State<StreamExample> {
  late Stream<int> _numberStream;

  @override
  void initState() {
    super.initState();
    _numberStream = _createNumberStream();
  }

  Stream<int> _createNumberStream() async* {
    for (int i = 1; i <= 10; i++) {
      await Future.delayed(Duration(seconds: 1));
      yield i;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        StreamBuilder<int>(
          stream: _numberStream,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}', style: TextStyle(color: Colors.red));
            }
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator();
            }
            if (!snapshot.hasData) {
              return Text('No data');
            }
            return Text(
              'Number: ${snapshot.data}',
              style: TextStyle(fontSize: 32),
            );
          },
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _numberStream = _createNumberStream();
            });
          },
          child: Text('Restart Stream'),
        ),
      ],
    );
  }
}

Test Your Knowledge

Q1
of 3

What does `ConnectionState.active` mean for a StreamBuilder?

A
The stream has not yet started
B
The stream is emitting events (or waiting for the next one)
C
The stream has completed
D
An error occurred
Q2
of 3

How do you access the latest value emitted by a stream inside StreamBuilder's builder?

A
snapshot.value
B
snapshot.data
C
snapshot.latest
D
snapshot.current
Q3
of 3

Where should you create the stream that you pass to a StreamBuilder to avoid issues?

A
Inside the build method
B
In initState (or once, outside build)
C
Inside the builder function
D
Anywhere, it doesn't matter

Previous

flutter futurebuilder

Next

flutter form

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.