Open In App

Dart – Streams

Last Updated : 17 Sep, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

A Stream is a sequence of asynchronous events. It is like an asynchronous Iterable—where, instead of getting the next event when you ask for it, the stream tells you that there is an event when it is ready. 

In other words, streams are a source of asynchronous events delivered sequentially. There are data events, which are sometimes referred to as elements of the stream due to a stream’s similarity to a list, and there are error events, which are notifications of failure. Once all data elements have been emitted, a special event signaling the stream is done will notify any listeners that there is no more.

Advantage of Streams:

The primary advantage of using streams to communicate is that it keeps code loosely coupled. The owner of a stream can emit values as they become available, and it doesn’t need to know anything about who’s listening or why. Similarly, consumers of the data need only adhere to the stream interface, and the means by which the stream’s data is generated are entirely hidden.

Points to remember:

  • A stream is like a pipe, you put a value on the one end, and if there’s a listener on the other end that listener will receive that value.
  • You can process a stream using either await for or listen() from the Stream API.

How Streams are created?

Streams can be created in many ways: the asynchronous await for iterates over the events of a stream like the for loop iterates over an Iterable.

Example 1:

Dart




Future<int> sumStream(Stream<int> stream) async {
  var sum=0;
  await for(var value in stream) {
    sum += value;
  }
  return sum;
}
  
Future<void> main() async {
  final stream = Stream<int>.fromIterable([1,2,3,4,5]);
  final sum = await sumStream(stream);
  print('Sum: $sum');
}


Output: 

Explanation: This code simply receives each event of a stream of integer events, adds them up, and returns (a future of) the sum. When the loop body ends, the function is paused until the next event arrives or the stream is done.

In function, we have used the async keyword, which is required when using the await for a loop.

Important concepts of Streams in Flutter:

Stream Controller: A StreamController simplifies stream management, automatically creating a stream and sink, and providing methods for controlling a stream’s behavior. A StreamController object in Dart does exactly what the name suggests, it controls Dart Streams. The object is used to create streams and send data, errors, and done events on them. Controllers also help check a stream’s properties, such as how many subscribers it has or if it’s paused.

Stream Builders: StreamBuilder is a widget that uses stream operations and basically, it rebuilds its UI when it gets the new values that are passed via Stream it listens to.

StreamBuilder requires 2 parameters:

  1.  stream: A method that returns a stream object
  2.  builder: widgets that will be returned during different states of a streambuilder.

Practical implementation of Stream Controller and Stream builder using Count down app:

We will create a Flutter count-down app using Stream Controller and Stream builder. In this Flutter StreamController Example, We will simply build an app that can count down a value from n to 0 using the stream Controller sink & update the UI.

example1: main.dart

Dart




import 'dart:async';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
  
        primarySwatch: Colors.blue,
      ),
      home: CounterApp()
    );
  }
}
  
class CounterApp extends StatefulWidget {
  const CounterApp({Key? key}) : super(key: key);
  
  @override
  _CounterAppState createState() => _CounterAppState();
}
  
class _CounterAppState extends State<CounterApp> {
  
  // create instance of streamcontroller class
  StreamController _controller = StreamController();
  int _counter = 10;
  
  void StartTimer() async{
      
    // Timer Method that runs every second
    Timer.periodic(Duration(seconds: 1), (timer) {
      _counter--;
  
      // add event/data to stream controller using sink
      _controller.sink.add(_counter);
  
      // will stop Count Down Timer when _counter value is 0
      if(_counter<=0){
        timer.cancel();
        _controller.close();
      }
    });
  }
  
  @override
  void dispose() {
    super.dispose();
      
     // Destroy the Stream Controller when use exit the app
    _controller.close();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            StreamBuilder(
              initialData: _counter,
              stream: _controller.stream,
                builder: (context,snapshot){
                return Text('${snapshot.data}');
                }
            ),
            SizedBox(
              height: 20,
            ),
            ElevatedButton(onPressed: (){
                
              // start the timer
              StartTimer();
            }, child: Text('Start Count Down'))
          ],
        ),
      ),
    );
  }
}


Output: 

Explanation:

There are two important points to know –

  1. Sink: In Flutter Streams, a Sink is a point from where we can add data into the stream pipe.
  2. Source: In Flutter Stream, a Source is a point from where we can keep listening to stream data or get the data that is been added into the stream.

In the above code we have created StreamController & add data to it using sink.add().  

We can see below this is how we used the stream controller in the above code:

// Create Stream
StreamController _controller = StreamController();
int _counter = 60;
// add event/data to stream controller using sink
_controller.sink.add(_counter);

Here we put the initial count to 10. So from 10, it will go in reverse order till the value gets to 0. 

Now we need to listen to the data coming into the stream & print the data on the screen, so for that, we used the StreamBuilder widget to listen to the asynchronous event.

Below is a pseudo-code that how we used stream builder in the above program to collect data and print on the screen –

StreamBuilder(
 initialData: _counter,
 stream: _controller.stream,
   builder: (context,snapshot){
   return Text('${snapshot.data}');
   }
)

Two types of Streams:

  1. Single subscription streams
  2. Broadcast streams

1. Single Subscription Streams:

Single subscription streams are the default. They work well when you’re only using a particular stream on one screen.

A single subscription stream can only be listened to once. It doesn’t start generating events until it has a listener and it stops sending events when the listener stops listening, even if the source of events could still provide more data.

Single subscription streams are useful to download a file or for any single-use operation. For example, a widget can subscribe to a stream to receive updates about a value, like the progress of a download, and update its UI accordingly.

Example 1.

Dart




import 'dart:convert';
import 'dart:async';
  
// Initializing a stream controller
StreamController<String> controller = StreamController<String>();
  
// Creating a new stream through the controller
Stream<String> stream = controller.stream;
  
void main() {
  
    // Setting up a subscriber to listen for any events sent on the stream
    StreamSubscription<String> subscriber = stream.listen((String data) {
        print(data);
    },
    onError: (error) {
        print(error);
    },
    onDone: () {
        print('Stream closed!');
    });
  
    // Adding a data event to the stream with the controller
    controller.sink.add('GeeksforGeeks!');
  
    // Adding an error event to the stream with the controller
    controller.addError('Error!');
  
    // Closing the stream with the controller
    controller.close();
  
}


Output: 

2. Broadcast streams:

If you need multiple parts of your app to access the same stream, use a broadcast stream, instead. A broadcast stream allows any number of listeners. It fires when its events are ready, whether there are listeners or not. To create a broadcast stream, you simply call asBroadcastStream() on an existing single subscription stream.

Syntax:  final broadcastStream =  singleStream.asBroadcastStream();

 Example 2.

Dart




import 'dart:convert';
import 'dart:async';
  
// Initializing a stream controller for a broadcast stream
StreamController<String> controller = StreamController<String>.broadcast();
  
// Creating a new broadcast stream through the controller
Stream<String> stream = controller.stream;
  
void main() {
    // Setting up a subscriber to listen for any events sent on the stream
    StreamSubscription<String> subscriber1 = stream.listen((String data) {
        print('Subscriber1: ${data}');
    },
    onError: (error) {
        print('Subscriber1: ${error}');
    },
    onDone: () {
        print('Subscriber1: Stream closed!');
    });
  
    // Setting up another subscriber to listen for any events sent on the stream
    StreamSubscription<String> subscriber2 = stream.listen((String data) {
        print('Subscriber2: ${data}');
    },
    onError: (error) {
        print('Subscriber2: ${error}');
    },
    onDone: () {
        print('Subscriber2: Stream closed!');
    });
  
    // Adding a data event to the stream with the controller
    controller.sink.add('GeeksforGeeks!');
  
    // Adding an error event to the stream with the controller
    controller.addError('Error!');
  
    // Closing the stream with the controller
    controller.close();
}


Output :

Let’s look into some of the classes’ useful methods that are used in the above programs:

  1. add() method: handles forwarding any data to the sink.
  2. addError() method:  If an error occurs and your stream’s listeners need to be informed then addError() is used.
  3. listen() method:  We listen to the stream for the data incoming with .listen() method.


Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads