Future to Stream
Dart programming
asynchronous programming
Dart streams
Future conversion

How to convert a Future into a Stream?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Dart, Future and Stream both represent asynchronous work, but they model different delivery patterns. A Future completes once with either a value or an error, while a Stream can deliver zero or more events over time. Converting a Future into a Stream is useful when an API expects stream input, such as StreamBuilder, Rx style pipelines, or operators that compose multiple async sources.

Core Sections

Understand when conversion makes sense

A single result operation should usually remain a Future until you have a real reason to treat it as a stream. Converting too early can add unnecessary complexity. Good reasons include integrating with stream based UI widgets, applying stream operators like debounce or merge, and keeping a consistent API surface where all async values are exposed as streams.

A converted stream from a future emits exactly one data event followed by completion, or one error event followed by completion. That behavior helps you reason about lifecycle and avoids accidental assumptions about repeated updates.

Convert with asStream

The most direct conversion is future.asStream(). This is concise and works for most cases.

dart
1import 'dart:async';
2
3Future<int> loadCount() async {
4  await Future<void>.delayed(const Duration(milliseconds: 300));
5  return 42;
6}
7
8void main() {
9  final Future<int> future = loadCount();
10  final Stream<int> stream = future.asStream();
11
12  stream.listen(
13    (value) => print('value: $value'),
14    onError: (error) => print('error: $error'),
15    onDone: () => print('done'),
16  );
17}

If the original future has already completed, the stream still emits asynchronously when listened to, which keeps behavior predictable across call sites.

Convert with Stream.fromFuture

Stream.fromFuture creates the same single event pattern but can be clearer when you are constructing stream pipelines explicitly.

dart
1import 'dart:async';
2
3Future<String> fetchUsername() async {
4  await Future<void>.delayed(const Duration(milliseconds: 200));
5  return 'mark';
6}
7
8void main() {
9  final stream = Stream<String>.fromFuture(fetchUsername())
10      .map((name) => name.toUpperCase());
11
12  stream.listen(print, onDone: () => print('completed'));
13}

Use this form when you want to express stream creation in one place and chain operators immediately after creation.

Use conversion in Flutter StreamBuilder

When a screen is written around StreamBuilder, a one shot fetch can still be connected by converting the future. This avoids rewriting widget structure just to support one async source.

dart
1import 'package:flutter/material.dart';
2
3class ProfileName extends StatelessWidget {
4  const ProfileName({super.key});
5
6  Future<String> _loadName() async {
7    await Future<void>.delayed(const Duration(milliseconds: 250));
8    return 'Alice';
9  }
10
11  
12  Widget build(BuildContext context) {
13    return StreamBuilder<String>(
14      stream: _loadName().asStream(),
15      builder: (context, snapshot) {
16        if (snapshot.connectionState == ConnectionState.waiting) {
17          return const CircularProgressIndicator();
18        }
19        if (snapshot.hasError) {
20          return Text('Error: ${snapshot.error}');
21        }
22        return Text('Hello, ${snapshot.data}');
23      },
24    );
25  }
26}

For repeated refresh behavior, do not keep converting new futures inside build without control. Trigger conversion from state or a dedicated stream source so rebuilds do not spawn redundant work.

Error handling and cancellation notes

A future converted to stream cannot be cancelled in the same way a long running stream subscription might be cancelled at source. Cancelling the listener only stops event delivery to that listener. If underlying work must be cancellable, design a real stream producer using StreamController and explicit cancellation callbacks.

Common Pitfalls

  • Converting every future to stream by default, even when no stream features are needed. Keep simple one shot tasks as futures.
  • Creating a new converted stream inside every widget rebuild. Store the stream in state when lifecycle must be stable.
  • Assuming converted streams emit multiple values. A future based stream emits at most one value.
  • Ignoring error events from the original future. Always attach onError handling in listeners or builders.
  • Expecting subscription cancellation to stop underlying future execution. Design cancellation separately when required.

Summary

  • future.asStream() and Stream.fromFuture both convert one shot async results into streams.
  • A converted stream emits one value or one error, then closes.
  • Conversion is useful for stream based APIs like StreamBuilder and operator pipelines.
  • Keep lifecycle stable in UI code by avoiding repeated conversion on rebuild.
  • Treat cancellation and repeated updates as separate design concerns.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.