Handling Asynchronous Data In Flutter - The Power Of Streams
Tags: flutter, dart, mobile
Published: 2023-03-12 16:53:23

Flutter is a popular open-source mobile app development framework that allows developers to create high-performance, visually appealing, and feature-rich mobile apps. The stream concept is one of the essential features of Flutter, which will enable you to handle asynchronous data. This article will explore Flutter's stream, its uses, and how it works.

## What is a Stream?

A flutter stream is a sequence of asynchronous data that can be processed and consumed efficiently. In a stream, continuous data is delivered to an application in a continuous flow. This can handle input events, network responses, and even animations.

Streams are intended to handle asynchronous data that may take an unknown amount of time to arrive, allowing the developer to handle the data as soon as it becomes available. Streams are also very efficient because they only process available data and don't waste resources waiting for data.

Suppose you have a weather app that displays a list of weather updates. Instead of constantly checking for new updates, you can use a stream to listen for and display updates from a server in real-time. When a new update is received, it is sent through the stream, and your app can respond accordingly.

## Types of Streams

There are two types of streams in Flutter,

**Single Subscription Stream**

A stream that can only be listened to once. The stream is closed when the data has been consumed.

**Broadcast Stream**

A stream that can be listened to repeatedly. Multiple subscribers can listen to the same stream at the same time.

## How to work with Streams

To work with streams in Flutter, follow the steps below for effective stream integration in your application.

**STEP 1**

Import the `dart:async` library.

```dart
import 'dart:async';
```

The `dart:async` library is used in Dart and Flutter to work with asynchronous programming.

**STEP 2**

Create a StreamController object.

```dart
final StreamController<int> _controller = StreamController<int>();
```
In Flutter, the StreamController object allows you to create, add data, and listen to streams. In this case, we're making a new StreamController to handle integers.

**STEP 3**

Add data to the stream.

```dart
_controller.sink.add(1);
_controller.sink.add(2);
_controller.sink.add(3);
```

The StreamController sink property is used to add data to the stream. In this case, we're adding three integer values to the stream: 1, 2, and 3.

**STEP 4**

Create a StreamSubscription object:

```dart
final StreamSubscription<int> _subscription = _controller.stream.listen((event) {
  print(event);
});
```
The object `_subscription` is created to listen to the data stream. The StreamController's `listen` method registers a callback function that will be called whenever a new event is added to the stream. In this case, we're using the `print` function to print the value of each new event to the console.

**STEP 5**

Close the StreamSubscription and StreamController objects

```dart
_subscription.onDone(() {
  _controller.close();
});
```

The StreamSubscription's `onDone` method registers a callback function that will be called when the subscription has finished listening to the stream. In this case, we're calling the StreamController's `close` method to close the stream and release any associated resources.

Let's create a counter app with a stream,

We will now develop a basic counter application using streams, incorporating the concepts we have learned thus far.

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

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

class MyApp extends StatelessWidget {
 const MyApp({super.key});

 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
     ),
     home: const MyHomePage(title: 'Flutter Demo Home Page'),
   );
 }
}

class MyHomePage extends StatefulWidget {
 const MyHomePage({super.key, required this.title});

 final String title;

 @override
 State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 final StreamController<int> _streamController = StreamController<int>();

 Stream<int> get counterStream => _streamController.stream;

 int _counter = 0;

 @override
 void dispose() {
   _streamController.close();
   super.dispose();
 }

 void _incrementCounter() {
   _counter++;
   _streamController.sink.add(_counter);
 }

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(widget.title),
     ),
     body: Center(
       child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
             const Text(
               'You have pushed the button this many times:',
             ),
             StreamBuilder<int>(
               stream: counterStream,
               initialData: _counter,
               builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                 return Text(
                   '${snapshot.data}',
                   style: Theme.of(context).textTheme.headlineMedium,
                 );
               },
             ),
           ]),
     ),
     floatingActionButton: FloatingActionButton(
       onPressed: _incrementCounter,
       tooltip: 'Increment',
       child: const Icon(Icons.add),
     ), // This trailing comma makes auto-formatting nicer for build methods.
   );
 }
}
```

We used a StreamBuilder widget to create a user interface that updates based on a data stream. A stream is a sequence of asynchronous events that can be observed, and the StreamBuilder widget listens to this stream and updates the user interface whenever a new event is received.

To use a StreamBuilder widget, you provide it with a stream and a builder function. The builder function takes two arguments: a BuildContext for building the user interface and an AsyncSnapshot containing the most recent data emitted by the stream.

## Use cases of Stream

In Flutter, streams are versatile tools that can be used in many ways. Here are some examples,

**Network Requests**

A stream can receive data from a server in real-time and process it.

**Animation**

Streams can update the animation's state as it plays.

**User Input**

Streams can listen to events in text fields and buttons and update the app's state accordingly.

**State Management**

Streams can be used to manage the app state. You can create a stream that emits events when the state changes and use it to update the UI.

## Conclusion

Asynchronous data handling is one of the most important aspects of Flutter development. They are efficient, user-friendly, and can be used in various contexts, such as network requests, animations, and user input. A solid understanding of streams is essential for any Flutter developer, and we hope this article has provided you with an excellent introduction to this vital concept.