Flutter – StreamBuilder Widget

A StreamBuilder in Flutter is used to listen to a stream of data and rebuild its widget subtree whenever new data is emitted by the stream. It’s commonly used for real-time updates, such as when working with streams of data from network requests, databases, or other asynchronous sources. In this article, we are going to see an example of a Streambuilder Widget by taking an Example.

Syntax of StreamBuilder

StreamBuilder<T>(
stream: yourStream, // The stream to listen to
initialData: initialData, // Optional initial data while waiting for the first event
builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return YourLoadingWidget(); // Display a loading indicator while waiting for data
} else if (snapshot.hasError) {
return YourErrorWidget(error: snapshot.error); // Handle errors
} else if (!snapshot.hasData) {
return YourNoDataWidget(); // Handle the case when there's no data
} else {
return YourDataWidget(data: snapshot.data); // Display your UI with the data
}
},
)

Here we are going to see a simple example of a StreamBuilder in Flutter that listens to a stream of numbers and displays the latest number in real-time:

Required Tools

  • To build this app, you need the following items installed on your machine:
  • Visual Studio Code / Android Studio
  • Android Emulator / iOS Simulator / Physical Device device.
  • Flutter Installed
  • Flutter plugin for VS Code / Android Studio.

A sample video is given below to get an idea about what we are going to do in this article.

Step By Step Implementations

Step 1: Create a New Project in Android Studio

To set up Flutter Development on Android Studio please refer to Android Studio Setup for Flutter Development, and then create a new project in Android Studio please refer to Creating a Simple Application in Flutter.

Step 2: Import the Package

First of all import material.dart file.

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

Step 3: Execute the main Method

Here the execution of our app starts.

Dart




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


Step 4: Create MyApp Class

In this class we are going to implement the MaterialApp , here we are also set the Theme of our App.

Dart




class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.green, // Set the app's primary theme color
      ),
      title: 'StreamBuilder Example',
      home: NumberStreamPage(),
    );
  }
}


Step 5: Create NumberStreamPage Class

In this class we are going to Implement the StreamBuilder to display the numbers changes in real time.Here we are going to run a for loop from 0 to 9 and display the updates number by the help of StreamBuilder.Comments are added for better understanding.

StreamBuilder<int>(
stream: _numberStreamController.stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // Display a loading indicator when waiting for data.
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}'); // Display an error message if an error occurs.
} else if (!snapshot.hasData) {
return Text('No data available'); // Display a message when no data is available.
} else {
return Text(
'Latest Number: ${snapshot.data}',
style: TextStyle(fontSize: 24),
); // Display the latest number when data is available.
}
},
),

Dart




class _NumberStreamPageState extends State<NumberStreamPage> {
  late StreamController<int> _numberStreamController;
  
  @override
  void initState() {
    super.initState();
  
    // Create a stream controller and add numbers to the stream.
    _numberStreamController = StreamController<int>();
    _startAddingNumbers(); // Start adding numbers to the stream.
  }
  
  void _startAddingNumbers() async {
    for (int i = 0; i < 10; i++) {
      await Future.delayed(Duration(seconds: 2)); // Delay for 2 seconds.
      _numberStreamController.sink.add(i); // Add the number to the stream.
    }
  }
  
  @override
  void dispose() {
    _numberStreamController.close(); // Close the stream when disposing.
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StreamBuilder Example'),
      ),
      body: Center(
        child: StreamBuilder<int>(
          stream: _numberStreamController.stream,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator(); // Display a loading indicator when waiting for data.
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}'); // Display an error message if an error occurs.
            } else if (!snapshot.hasData) {
              return Text('No data available'); // Display a message when no data is available.
            } else {
              return Text(
                'Latest Number: ${snapshot.data}',
                style: TextStyle(fontSize: 24),
              ); // Display the latest number when data is available.
            }
          },
        ),
      ),
    );
  }
}


Here is the full Code of main.dart file

Dart




import 'dart:async';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false, // Remove the debug banner
      theme: ThemeData(
        primarySwatch: Colors.green, // Set the app's primary theme color to green
      ),
      title: 'StreamBuilder Example',
      home: NumberStreamPage(),
    );
  }
}
  
class NumberStreamPage extends StatefulWidget {
  @override
  _NumberStreamPageState createState() => _NumberStreamPageState();
}
  
class _NumberStreamPageState extends State<NumberStreamPage> {
  late StreamController<int> _numberStreamController;
  
  @override
  void initState() {
    super.initState();
  
    // Create a stream controller and add numbers to the stream.
    _numberStreamController = StreamController<int>();
    _startAddingNumbers(); // Start adding numbers to the stream.
  }
  
  void _startAddingNumbers() async {
    for (int i = 0; i < 10; i++) {
      await Future.delayed(Duration(seconds: 2)); // Delay for 2 seconds.
      _numberStreamController.sink.add(i); // Add the number to the stream.
    }
  }
  
  @override
  void dispose() {
    _numberStreamController.close(); // Close the stream when disposing.
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StreamBuilder Example'),
      ),
      body: Center(
        child: StreamBuilder<int>(
          stream: _numberStreamController.stream,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator(); // Display a loading indicator when waiting for data.
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}'); // Display an error message if an error occurs.
            } else if (!snapshot.hasData) {
              return Text('No data available'); // Display a message when no data is available.
            } else {
              return Text(
                'Latest Number: ${snapshot.data}',
                style: TextStyle(fontSize: 24),
              ); // Display the latest number when data is available.
            }
          },
        ),
      ),
    );
  }
}


Output: