Future and Stream in Flutter and Dart

Binod Dangi
8 min readJun 2, 2023

--

Both futures and streams are parts of Dart. The main similarity between them is that they are both used for asynchronous programming.

Stream

In Flutter, a stream is a sequence of asynchronous events. It represent a sequence of data that is emitted over time. For example, a stream could be used to represent the real-time updates of a stock ticker or the location of a user’s device. Streams are typically used to handle data that is constantly changing.

In Dart, streams are based on the Stream and StreamController classes. The Stream represents a stream of events, and the StreamController is responsible for producing and controlling the stream. You can think of a StreamController as a source that emits events, and the Stream as the channel through which those events are transmitted

Streams are implemented using the Stream class. The Stream class provides a number of methods for creating, listening to, and managing streams.

To create a stream, you can use the StreamController class. The StreamController class provides a number of methods for adding events to a stream, closing a stream, and listening to a stream.

To listen to a stream, you can use the StreamBuilder widget. The StreamBuilder widget listens to a stream and rebuilds its child whenever the stream emits a new event.

StreamBuilder

Streams and StreamBuilders can be used to build a variety of user interfaces that are based on asynchronous data. For example, you could use them to build a chat application, a weather app, or a stock ticker.

Here are some of the benefits of using streams and StreamBuilders:

  • They are efficient. Streams only emit events when there is new data, so they don’t waste resources by repeatedly emitting the same data.
  • They are easy to use. Streams and StreamBuilders are well-documented and easy to learn.
  • They are powerful. Streams and StreamBuilders can be used to build a wide variety of user interfaces.

If you are working on a Flutter project that involves asynchronous data, I recommend using streams and StreamBuilders. They are a powerful tool that can help you build efficient, easy-to-use, and powerful user interfaces.

Here is an example of using a stream to represent user input:

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

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
StreamController<int> _counterStreamController = StreamController<int>();

int _counter = 0;

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

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('StreamBuilder Example'),
),
body: Center(
child: StreamBuilder<int>(
stream: _counterStreamController.stream,
initialData: _counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text(snapshot.data.toString());
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_counter++;
_counterStreamController.add(_counter);
});
},
child: Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
),
);
}
}

In this example, the StreamController<int> named _counterStreamController is used to create a custom stream that emits integer values. The initial value of the counter is set to 0.

Inside the build method, the StreamBuilder listens to the custom stream _counterStreamController.stream. The initial data is set to the current value of the _counter variable using the initialData parameter. The Text widget then displays the latest counter value from the stream using snapshot.data.toString().

In the onPressed callback of the FloatingActionButton, the _counter variable is incremented, and the updated counter value is added to the stream using _counterStreamController.add(_counter). Additionally, the setState method is called to trigger a rebuild and update the UI with the new counter value.

When the app runs, the initial counter value is displayed as ‘0’. Each time the FloatingActionButton is pressed, the counter value increments by one, and the updated value is displayed in the UI.

Overall, the code correctly demonstrates the usage of a StreamBuilder with a custom stream to display and increment a counter value.

Streams can also be used to represent other types of data, such as network data and file I/O. For example, the following code uses a stream to download a file from the internet:

void main() {
Stream<String> data = fetchData();
  data.listen((String data) {
print(data);
}, onError: (error) {
print(error);
});
}
Future<Stream<String>> fetchData() async {
String url = 'https://example.com/data.txt';
HttpClient client = new HttpClient();
HttpResponse response = await client.get(url);
StreamController<String> controller = new StreamController<String>(); response.transform(utf8.decoder).listen((String data) {
controller.add(data);
}, onDone: () {
controller.close();
});
return controller.stream;
}

In this example, the fetchData() function uses the HttpClient class to download a file from the internet. The fetchData() function returns a stream of the file data. The data variable is used to listen to the stream and print the file data.

Streams are a powerful tool for managing asynchronous data in Flutter. They can be used to represent a variety of data sources, and they can be used to create complex asynchronous workflows.

Future

A future represents a value that may not be available yet. It’s used to represent the result of an asynchronous operation that will be completed at some point in the future. Futures are commonly used for handling asynchronous tasks like fetching data from a network or reading from a file.

In Dart, a future is an instance of the Future class. When you invoke an asynchronous operation, it returns a future immediately. You can then attach callbacks to the future using methods like then, catchError, and whenComplete to handle the eventual result or error.

In Flutter, futures are frequently used with widgets to handle asynchronous operations and update the UI when the future completes. For example, you might use a FutureBuilder widget to display different UI states based on the state of a future. It can show a loading indicator while the future is still in progress, display the fetched data when it’s available, or handle errors if they occur.

Both streams and futures are essential concepts in Dart and Flutter for handling asynchronous programming and building responsive applications. By utilizing streams and futures effectively, you can create applications that respond to events, fetch data from external sources, and provide a smooth user experience.

Futures are implemented using the Future class. The Future class provides a number of methods for working with futures, such as then(), catchError(), and whenComplete().

To use a future, you first need to create it. You can create a future using the Future.value() constructor, or you can use an asynchronous function that returns a future.

Once you have created a future, you can use it to access the result of the asynchronous operation. You can do this by using the then() method. The then() method takes a function as its argument. This function will be called when the future is completed. The function will have access to the result of the asynchronous operation.

Here is an example of how to use a future:

Future<String> fetchData() async {
String url = 'https://example.com/data.txt';
  HttpClient client = new HttpClient();
HttpResponse response = await client.get(url);
return response.body;
}
void main() {
Future<String> data = fetchData();
data.then((String data) {
print(data);
});
}

In this example, the fetchData() function is an asynchronous function that returns a future. The main() function uses the then() method to access the result of the asynchronous operation. The then() method will call the function with the result of the asynchronous operation.

Futures are a powerful tool for working with asynchronous operations in Flutter. They can be used to represent the result of network requests, file I/O, and other asynchronous operations.

FutureBuilder

The FutureBuilder widget is a Flutter widget that is used to build a widget tree based on the result of a future.

The FutureBuilder widget takes a number of arguments, including:

  • future: The future that will be used to build the widget tree.
  • builder: A function that will be called to build the widget tree. The function will have access to the result of the future.

Here is an example of how to use the FutureBuilder widget:

Future<String> fetchData() async {
String url = 'https://example.com/data.txt';
  HttpClient client = new HttpClient();
HttpResponse response = await client.get(url);
return response.body;
}
void main() {
Future<String> data = fetchData();
Widget build(BuildContext context) {
return FutureBuilder(
future: data,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else {
return CircularProgressIndicator();
}
},
);
}
}

In this example, the FutureBuilder widget is used to build a text widget based on the result of the fetchData() future. If the future is completed, the FutureBuilder widget will build a text widget with the result of the future. If the future is not completed, the FutureBuilder widget will build a circular progress indicator.

The FutureBuilder widget is a powerful tool for building user interfaces that are based on the result of asynchronous operations.

Now, Let’s discuss the differences between streams and futures in Flutter and Dart:

1. Purpose:
— Stream: Streams represent a sequence of asynchronous events that can occur over time. They are used for handling continuous data or events, such as user input, real-time updates, or data streams from network connections.
— Future: Futures represent a single asynchronous operation that will be completed at some point in the future. They are used for handling one-time asynchronous tasks, such as network requests, file operations, or time-consuming computations.

2. Multiple values vs. Single value:
— Stream: Streams can emit multiple values over time. They can emit an indefinite number of events, and listeners can receive these events as they occur.
— Future: Futures represent a single value that will be available at some point in the future. Once a future is completed, it can only produce one value.

3. Event-driven vs. Result-driven:
— Stream: Streams are event-driven. They allow you to respond to events as they happen. You can listen to a stream and handle each emitted event individually.
— Future: Futures are result-driven. They represent the eventual result of an asynchronous operation. You can attach callbacks to a future to handle the completion of the operation, either with a successful result or an error.

4. Continuity vs. One-time operation:
— Stream: Streams are continuous in nature. They allow for ongoing data processing and listening to events as they occur. They are suitable for scenarios where you need to handle a continuous flow of data or events.
— Future: Futures represent a one-time operation that completes with a result or an error. Once a future completes, it cannot produce any more values.

5. Handling multiple values vs. Handling a single value:
— Stream: When working with streams, you can listen to and process each emitted event individually. You can apply operations like mapping, filtering, or reducing to transform the stream data.
— Future: When working with futures, you typically wait for the completion of a single asynchronous operation. You can use the `then` method to handle the result of the future or the `catchError` method to handle any errors that occur during the operation.

In summary, streams are used for handling continuous data or events, allowing for ongoing processing and listening, while futures represent a one-time asynchronous operation with a single result or error. Streams are event-driven and can emit multiple values, while futures are result-driven and handle a single value.

--

--