Brainwave Cafe

Tech Education Blog

June 12, 2024

Understanding Dart Streams and How to Use Streams in Flutter

How to use stream in Dart

In modern app development, handling asynchronous data efficiently is crucial. Flutter, a popular framework for building cross-platform mobile applications, offers a powerful tool for this purpose: Streams.

In Dart and Flutter, futures and streams are the main tools for handling asynchronous programming. A Future represents a single value that will be provided later, while a Stream is a sequence of values (0 or more) that can be delivered asynchronously at any time. It is essentially a continuous flow of data.

To get data from a stream, you subscribe (or listen) to it. Whenever data is emitted, you can receive and process it according to your app’s logic.

This chapter explores various uses of streams in a Flutter app. You’ll see how to use streams in different scenarios, read and write data to streams, build user interfaces with streams, and implement the BLoC state management pattern. Like futures, streams can produce data or errors, and this article will show you how to handle both.

How To Use Dart Streams

To explain how to use Dart Streams, we’ll create a simple project that changes a screen’s background colour every second. We’ll create a list of five colours, and every second, the background colour of a Container widget filling the whole screen will change.

A Stream of data will emit the colour information. The main screen will listen to this Stream to get the current colour and update the background.

In this article, I’ll walk you through the core parts of an app that utilizes Dart Streams. We’ll create a stream of data and listen (or subscribe) to that stream.

Step 1

In the stream.dart file, I created a stream of data by adding a method that returns a Stream of Color, and I marked the method as async*:

Stream<Color> getColors() async* {}

Previously, we’ve marked functions as async (without the asterisk * symbol). In Dart and Flutter, async is used for futures, while async* (with the asterisk ) is for streams. The main difference between a stream and a future is the number of events returned: a Future returns just one event, while a Stream returns zero to many events. Marking a function as async creates a generator function, which generates a sequence of values (a stream).

So, To return a stream in an async* method, we use the yield* statement. You can think of yield* as a return statement, but with a crucial difference: yield* does not end the function.

Step 2

Next, we use Stream.periodic() to create a Stream that emits events at specified intervals. In our code, the stream emits a value each second. Inside the method within the Stream.periodic constructor, we use the modulus operator to choose which colour to display based on the number of seconds that have passed since the method was called and return the appropriate colour.

yield* Stream.periodic(const Duration(seconds: 1), (int t) {
  int index = t % colors.length;
  return colors[index];
});

This code creates the stream of data.

Step 3

In the main.dart file, I added the code to listen to the stream with the changeColor method;

changeColor() async {
  await for (var eventColor in colorStream.getColors()) {
    setState(() {
      bgColor = eventColor;
    });
  }
}

The core of this method is the await for command, which is an asynchronous for loop that iterates over the events of a stream. It’s like a regular for loop, but instead of iterating over a set of data (like a list), it asynchronously listens to each event in a stream. From there, we call the setState method to update the bgColor property.

Complete Code

in the main.dart file

import 'package:flutter/material.dart';
import 'package:cookbook/stream.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  Color bgColor = Colors.blueGrey;
  late ColorStream colorStream;

  void changeColor() async {
    await for (var eventColor in colorStream.getColors()) {
      setState(() {
        bgColor = eventColor;
      });
    }
  }

  @override
  void initState() {
    super.initState();
    colorStream = ColorStream();
    changeColor();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Stream')),
      body: Container(
        decoration: BoxDecoration(color: bgColor),
      ),
    );
  }
}

In the stream.dart file

import 'package:flutter/material.dart';

class ColorStream {
  final List<Color> colors = [
    Colors.blueGrey,
    Colors.amber,
    Colors.deepPurple,
    Colors.lightBlue,
    Colors.teal
  ];
  Stream<Color> getColors() async* {
    yield* Stream.periodic(const Duration(seconds: 1), (int t) {
      int index = t % colors.length;
      return colors[index];
    });
  }
}

Final UI

Conclusion

There’s more to explore. Instead of using an asynchronous for loop, you can leverage the listen method over a stream. Here’s how:

  • Remove or comment out the content of the changeColor method in the main.dart file.
  • Add the following code to the changeColor method:
colorStream.getColors().listen((eventColor) {
  setState(() {
    bgColor = eventColor; });
});
  • Run the app. You’ll notice the app behaves just like before, changing the screen colour each second.
  • The main difference between listen and await for is that with listen, execution continues even if there is code after the loop, while await for stops execution until the stream completes.

In this app, we never stop listening to the stream, but it’s important to close a stream when it has completed its tasks. You can use the close() method for this, as shown in the next recipe.

Streams in Flutter are powerful tools for handling asynchronous data and can be applied in various real-world scenarios, such as real-time messaging, file uploads and downloads, user location tracking, and handling sensor data from devices.

Article by Lucky Ekpebe / Uncategorized / async await in flutter, asynchronous, asynchronous programming, Dart Streams, flutter, Flutter Future then example, future, future in dart, Future in dart and flutter example, Future in Flutter, Streams Leave a Comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Understanding Dart Streams and How to Use Streams in Flutter
  • How to Use FutureBuilder in flutter | Step by Step
  • How to use Futures with StatefulWidgets
  • Understanding Future in Dart and Flutter
  • BloC Vs Riverpod in Flutter

Recent Comments

  1. How to Use FutureBuilder in flutter | Step by Step on How to use Futures with StatefulWidgets

Copyright © 2025 · Education Pro on Genesis Framework · WordPress · Log in