Understanding Future
andStream
in Dart with Real-Life Examples
Asynchronous programming is at the core of responsive Flutter apps. In Dart, two main abstractions handle async operations: Future
and Stream
. While they both deal with values that come over time, their use cases and behaviors differ significantly. Understanding when to use Future
versus Stream
is key to writing efficient, readable, and scalable Flutter code.
Futures and Streams in Dart
In Dart, asynchronous programming is fundamental to creating smooth, non-blocking user experiences. Two important constructs used for handling asynchronous operations are Futures and Streams. Let’s dive deep into each with practical examples.
Future in Dart
A Future
represents a potential value or error that will be available at some time in the future. You typically work with futures when:
- Fetching data from a server
- Querying a database
- Reading a file
- Performing computationally heavy tasks
Example 1: Fetching Data from a Server
Future<String> fetchDataFromServer() async {
// Simulate fetching data
await Future.delayed(Duration(seconds: 2));
return "Data from server";
}
void main() {
fetchDataFromServer()
.then((data) => print(data))
.catchError((error) => print('Error: $error'));
}
Example 2: Querying a Database
Future<List<Map<String, dynamic>>> queryDatabase() async {
await Future.delayed(Duration(seconds: 2));
return [{'id': 1, 'name': 'Item 1'}, {'id': 2, 'name': 'Item 2'}];
}
void main() {
queryDatabase()
.then((result) => print(result))
.catchError((error) => print('Error: $error'));
}
Example 3: Reading a File
Future<String> readFile() async {
await Future.delayed(Duration(seconds: 2));
return "File contents here...";
}
void main() {
readFile()
.then((contents) => print(contents))
.catchError((error) => print('Error: $error'));
}
Practical Example: Fetching Data Using http
Package
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<List<dynamic>> fetchData() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to fetch data');
}
}
void main() {
print('Fetching data...');
fetchData()
.then((data) {
for (var item in data) {
print('Title: ${item['title']}\nBody: ${item['body']}\n---');
}
})
.catchError((error) => print('Error occurred: $error'))
.whenComplete(() => print('Program completed'));
}
Stream in Dart
Unlike Future
(which represents a single value), a Stream
represents a sequence of values over time.
A stream can:
- Emit multiple data events
- Emit error events
- Signal completion
Basic Stream Example
import 'dart:async';
void main() {
final streamController = StreamController<int>();
// Adding data to the stream
for (var i = 1; i <= 5; i++) {
streamController.sink.add(i);
}
final subscription = streamController.stream.listen(
(data) => print('Received data: $data'),
onError: (error) => print('Error occurred: $error'),
onDone: () => print('Stream closed'),
);
streamController.sink.addError('Some error occurred');
streamController.close();
subscription.cancel();
}
Single Subscription Stream
import 'dart:async';
void main() {
final stream = Stream<int>.periodic(Duration(seconds: 1), (count) => count).take(4);
final subscription = stream.listen(
(data) {
String message;
switch (data) {
case 1:
message = 'Hey';
break;
case 2:
message = 'Hello';
break;
case 3:
message = "It's a sunny day";
break;
default:
message = '';
}
if (message.isNotEmpty) {
print('Received data: $message');
}
},
onError: (error) => print('Error occurred: $error'),
onDone: () => print('Stream closed'),
);
Future.delayed(Duration(seconds: 10), () {
subscription.cancel();
});
}
Broadcast Stream
import 'dart:async';
void main() {
final streamController = StreamController<int>.broadcast();
// Adding data
for (var i = 1; i <= 5; i++) {
streamController.sink.add(i);
}
final subscription1 = streamController.stream.listen(
(data) => print('Listener 1: Received data: $data'),
onError: (error) => print('Listener 1: Error occurred: $error'),
onDone: () => print('Listener 1: Stream closed'),
);
final subscription2 = streamController.stream.listen(
(data) => print('Listener 2: Received data: $data'),
onError: (error) => print('Listener 2: Error occurred: $error'),
onDone: () => print('Listener 2: Stream closed'),
);
streamController.close();
subscription1.cancel();
subscription2.cancel();
}
Real-World Example: Messaging App Using Broadcast Stream
import 'dart:async';
class Message {
final String sender;
final String content;
Message(this.sender, this.content);
}
class MessagingService {
final StreamController<Message> _messageController = StreamController<Message>.broadcast();
Stream<Message> get messageStream => _messageController.stream;
void sendMessage(String sender, String content) {
final message = Message(sender, content);
_messageController.sink.add(message);
}
void dispose() {
_messageController.close();
}
}
void main() {
final messagingService = MessagingService();
final subscription1 = messagingService.messageStream.listen((message) =>
print('User received a message from ${message.sender}: ${message.content}'));
final subscription2 = messagingService.messageStream.listen((message) =>
print('User received a message from ${message.sender}: ${message.content}'));
final subscription3 = messagingService.messageStream.listen((message) =>
print('User received a message from ${message.sender}: ${message.content}'));
messagingService.sendMessage('User 1', 'Hello!');
messagingService.sendMessage('User 2', 'How are you doing?');
messagingService.sendMessage('User 3', 'I have an important announcement!');
subscription1.cancel();
subscription2.cancel();
subscription3.cancel();
messagingService.dispose();
}
Summary:
- Use Futures when you need a single result.
- Use Streams when you expect multiple values over time.
- Broadcast Streams allow multiple listeners.
Dart’s async/await, future, and stream mechanisms together provide robust ways to manage asynchronous operations elegantly.
Stay tuned for more Dart async programming tips!
🙏 If you found this article helpful, consider leaving a clap (or a few!) — it really motivates me to keep sharing more content like this. Thanks for reading and supporting! 🚀