Flutter Course: 14.6 Outputting Future in the App

In this course, we will explain in detail how to create and output Future in Flutter. Future is a core concept in Dart’s asynchronous programming, representing an object that returns a value or error when an asynchronous task is completed. This allows us to perform time-consuming tasks without blocking the UI.

1. What is Future?

Future is an object that represents a future result in asynchronous programming, allowing you to wait for the asynchronous task to complete and receive the result. For example, when fetching data through an HTTP request, you can proceed with other UI tasks without waiting for the request to finish.

1.1 States of Future

  • Pending: State where the task has not yet completed.
  • Completed: State where the task has completed and returned a result.
  • Error: State where an error occurred during the task.

2. Creating a Future

There are two main ways to create a Future. The first is to use built-in methods, and the second is to create it through custom functions.

2.1 Using Built-in Methods

You can create a Future object that completes after a certain time using the Future.delayed method. The following is an example that returns a message after 2 seconds.

Future fetchData() {
        return Future.delayed(Duration(seconds: 2), () {
            return 'Data loading complete';
        });
    }

2.2 Creating Custom Functions

You can directly create a function that returns a Future to handle database or API requests. For example, let’s create a function to fetch user information from an API.

Future fetchUser(int userId) async {
        final response = await http.get('https://api.example.com/user/$userId');
        if (response.statusCode == 200) {
            return User.fromJson(json.decode(response.body));
        } else {
            throw Exception('Failed to load user');
        }
    }

3. Outputting Futures

Now, let’s look at how to output the values of the Future object we created in a Flutter app. To do this, we use the FutureBuilder widget. The FutureBuilder dynamically updates the UI based on the state of the Future.

3.1 Using FutureBuilder

To use FutureBuilder, you need to define the future and builder parameters. You specify the Future object that performs the asynchronous task in future, and define the function that builds the UI based on the state of the asynchronous task in builder.

class UserProfile extends StatelessWidget {
        final int userId;
        UserProfile(this.userId);
        
        @override
        Widget build(BuildContext context) {
            return FutureBuilder(
                future: fetchUser(userId),
                builder: (context, snapshot) {
                    if (snapshot.connectionState == ConnectionState.waiting) {
                        return CircularProgressIndicator();
                    } else if (snapshot.hasError) {
                        return Text('Error: ${snapshot.error}');
                    } else {
                        return Text('User Name: ${snapshot.data.name}');
                    }
                },
            );
        }
    }

3.2 Exception Handling

Since errors may occur during asynchronous tasks, it’s important to handle errors in FutureBuilder using snapshot.hasError. Proper exception handling can provide clear feedback to users.

4. Complete Code Example

Based on what we have learned so far, let’s look at a complete example. This example is a Flutter app that calls an API to fetch user information and displays it on the screen.

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

class User {
    final String name;
    
    User({required this.name});
    
    factory User.fromJson(Map json) {
        return User(name: json['name']);
    }
}

Future fetchUser(int userId) async {
    final response = await http.get('https://api.example.com/user/$userId');
    if (response.statusCode == 200) {
        return User.fromJson(json.decode(response.body));
    } else {
        throw Exception('Failed to load user');
    }
}

class UserProfile extends StatelessWidget {
    final int userId;
    UserProfile(this.userId);
    
    @override
    Widget build(BuildContext context) {
        return FutureBuilder(
            future: fetchUser(userId),
            builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                    return CircularProgressIndicator();
                } else if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                } else {
                    return Text('User Name: ${snapshot.data.name}');
                }
            },
        );
    }
}

void main() => runApp(MaterialApp(home: Scaffold(body: UserProfile(1))));

5. Conclusion

In this course, we learned how to create Future in Flutter and efficiently handle asynchronous operations using it. The UI composed with FutureBuilder can change dynamically based on data loading states, which is very useful. These asynchronous programming techniques greatly help improve user experience even in complex apps.

Tip: If you want to delve deeper into asynchronous programming, also study the concepts of Stream and async/await.