Dart - Streams - GeeksforGeeks

17 Sep, 2021
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.
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.
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.
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.
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:
 stream: A method that returns a stream object builder: widgets that will be returned during different states of a streambuilder.
 stream: A method that returns a stream object
 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 –
Sink: In Flutter Streams, a Sink is a point from where we can add data into the stream pipe.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.
Sink: In Flutter Streams, a Sink is a point from where we can add data into the stream pipe.
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}');
   }
)
Single subscription streamsBroadcast streams
Single subscription streams
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 controllerStreamController<String> controller = StreamController<String>();  // Creating a new stream through the controllerStream<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 streamStreamController<String> controller = StreamController<String>.broadcast();  // Creating a new broadcast stream through the controllerStream<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:
add() method: handles forwarding any data to the sink.addError() method:  If an error occurs and your stream’s listeners need to be informed then addError() is used.listen() method:  We listen to the stream for the data incoming with .listen() method.
add() method: handles forwarding any data to the sink.
addError() method:  If an error occurs and your stream’s listeners need to be informed then addError() is used.
listen() method:  We listen to the stream for the data incoming with .listen() method.
Blogathon-2021
Dart-Function
Blogathon
Dart
Flutter
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
How to Import JSON Data into SQL Server?
How to Install Tkinter in Windows?
SQL - Multiple Column Ordering
How to pass data into table from a form using React Components
How to Create a Table With Multiple Foreign Keys in SQL?
Listview.builder in Flutter
Flutter - DropDownButton Widget
Flutter - Asset Image
Splash Screen in Flutter
Flutter - Custom Bottom Navigation Bar