← Flutter & Dart

Advanced Flutter

What are `FutureBuilder` and `StreamBuilder` for, and when do you use each one?

Show answer — try answering out loud first

Both connect asynchronous data to the UI. FutureBuilder builds the interface from a Future (a single result, like an HTTP request). StreamBuilder builds it from a Stream (multiple values over time, like live data). Each one rebuilds the widget according to the state of the async data (loading, has data, has error).

Instead of handling async state by hand with setState, these widgets do it for you:

  • FutureBuilder: you give it a Future and a builder. The builder receives a snapshot with the state. Use it for operations that yield one result: loading a profile, reading a file.
  • StreamBuilder: you give it a Stream. It rebuilds every time the stream emits a value. Use it for data that changes continuously: location, chat messages, a live counter.

The snapshot tells you: whether there's an error (hasError), whether there's data (hasData), or whether it's still waiting (connectionState == waiting). With that you decide what to show.

import 'package:flutter/material.dart';

Future<String> loadName() async {
  await Future.delayed(const Duration(seconds: 1));
  return "Mike";
}

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

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: loadName(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator(); // loading
        }
        if (snapshot.hasError) {
          return Text("Error: ${snapshot.error}"); // failed
        }
        return Text("Hello, ${snapshot.data}"); // ready
      },
    );
  }
}

Creating the Future inside build directly in the future: parameter. Since build runs many times, the async operation would fire again and again on every rebuild. The right way is to create the Future only once: store it in a state variable (in initState) or use a StatefulWidget. The same care applies to the Stream of StreamBuilder.

"FutureBuilder and StreamBuilder connect asynchronous data to the UI without me handling setState by hand. FutureBuilder is for a single result, like an HTTP request; StreamBuilder is for data that changes over time, like a chat or a location. Both give me a snapshot with the state: loading, has data, or has error, and based on that I show a spinner, the content, or an error message. One important thing is not to create the Future inside build, because it would be relaunched on every rebuild; I create it once in initState."

Quick challenge

Why is it a problem to write future: myAsyncFunction() directly inside build?

See answer

Because build is called many times (on every rebuild), and each time a new Future would be created, relaunching the async operation (for example, repeating the network request) and resetting the loading state. The solution is to create the Future only once, typically by storing it in a variable in initState of a StatefulWidget, and pass that variable to the FutureBuilder.