Flutter StreamBuilder examples (null safety)

Last updated on March 21, 2022 A Goodman Loading... 6 comments

The StreamBuilder widget is used in many kinds of Flutter applications, especially chat applications, social networks, real-time content updates, etc. In this article, we will go over 2 complete examples of implementing StreamBuilder: the first example is a real-time clock app and the second one is a demo chat app.

Overview

StreamBuilder is a widget that builds itself based on the latest snapshot of interaction with a stream.

Constructor:

StreamBuilder({
  Key? key, 
  T? initialData, 
  Stream<T>? stream, 
  required AsyncWidgetBuilder<T> builder
})

Main arguments:

  • builder: The build strategy currently used by this builder.
  • stream: The asynchronous computation to which this builder is currently connected, possibly null. When changed, the current summary is updated.

Understanding async* and yield

In the following example, you will notice these keywords: async* and yield. They are all keywords used in generator functions.

A generator function is a function that produces a sequence of values (in contrast to regular functions that return a single value) and is often used with Stream.

Yield is a keyword that “returns” a single value to the sequence but does not stop the generator function.

Example 1: Real-time Clock App

Preview

This app displays a real-time clock in the center of the screen.

The Code

Here’s the full source code in main.dart file:

// main.dart
import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Remove the debug banner
      debugShowCheckedModeBanner: false,
      title: 'Kindacode.com',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final bool _running = true;

  Stream<String> _clock() async* {
    // This loop will run forever because _running is always true
    while (_running) {
      await Future<void>.delayed(const Duration(seconds: 1));
      DateTime _now = DateTime.now();
      // This will be displayed on the screen as current time
      yield "${_now.hour} : ${_now.minute} : ${_now.second}";
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Kindacode.com'),
      ),
      body: Center(
        child: StreamBuilder(
          stream: _clock(),
          builder: (context, AsyncSnapshot<String> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            }
            return Text(
              snapshot.data!,
              style: const TextStyle(fontSize: 50, color: Colors.blue),
            );
          },
        ),
      ),
    );
  }
}

Example 2: Demo Chat App

This example is too simple compared to a real-world chat application, but it is a good thing to help you get a better understanding of how to use StreamBuilder.

Preview

The Code

// main.dart
import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

// Define how a chat message looks like
class ChatMessage {
  String username;
  String message;
  ChatMessage({required this.username, required this.message});
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Remove the debug banner
      debugShowCheckedModeBanner: false,
      title: 'Kindacode.com',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // This list holds the conversation
  // the ChatMessage class was declared above
  final List<ChatMessage> _chatMessages = [];

  // More messages will be yielded overtime
  Stream<ChatMessage> _chat() async* {
    await Future<void>.delayed(const Duration(seconds: 3));
    yield ChatMessage(username: 'Trump', message: "Hello");

    await Future<void>.delayed(const Duration(seconds: 3));
    yield ChatMessage(username: "Biden", message: "Hi baby");

    await Future<void>.delayed(const Duration(seconds: 3));
    yield ChatMessage(
        username: "Trump", message: "Would you like to have dinner with me?");

    await Future<void>.delayed(const Duration(seconds: 3));
    yield ChatMessage(
        username: "Biden", message: "Great. I am very happy to accompany you.");

    await Future<void>.delayed(const Duration(seconds: 3));
    yield ChatMessage(
        username: "Trump", message: "Nice. I love you, my honney!");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Kindacode.com'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(30),
        child: StreamBuilder(
          stream: _chat(),
          builder: (context, AsyncSnapshot<ChatMessage> snapshot) {
            if (snapshot.hasData) {
              _chatMessages.add(snapshot.data!);

              return ListView.builder(
                itemCount: _chatMessages.length,
                itemBuilder: (context, index) {
                  final ChatMessage chatItem = _chatMessages[index];
                  return ListTile(
                    // user name
                    leading: Text(
                      chatItem.username,
                      style: const TextStyle(
                          fontWeight: FontWeight.bold, fontSize: 20),
                    ),
                    // message
                    title: Text(
                      chatItem.message,
                      style: TextStyle(
                          fontSize: 20,
                          // use different colors for different people
                          color: chatItem.username == 'Trump'
                              ? Colors.pink
                              : Colors.blue),
                    ),
                  );
                },
              );
            }
            return const LinearProgressIndicator();
          },
        ),
      ),
    );
  }
}

If you want to implement beautiful chat bubbles, see this article: Flutter: Making Beautiful Chat Bubbles (2 Approaches).

Conclusion

This article covered the fundamentals and went over some examples of the StreamBuilder widget. If you would like to explore more things about Flutter, take a look at the following articles:

You can also check out our Flutter topic page or Dart topic page for the latest tutorials and examples.

Subscribe
Notify of
guest
6 Comments
Inline Feedbacks
View all comments
JoaoALmeida-dev
JoaoALmeida-dev
4 months ago

Hello, WHy did you use Stream<List<Map<String, dynamic>>> instead of Stream<Map<String, dynamic>>?

The way you are doing it, it yields the list every time, why not return only single items each time?
Im new to streams, thats why im asking

kamal baloch
kamal baloch
10 months ago

sir if i use like this Stream<List<Map<String, dynamic>>> ?????

Steve
Steve
1 year ago

Excellent ! quite simple.. cool stuff. Didnt know about the yield keyword.. thanx !

Related Articles