What is Async and Await in Flutter?

In Flutter, as in many modern programming environments, dealing with asynchronous operations is crucial for creating smooth, responsive applications. Asynchronous operations, such as network requests, file I/O, or even heavy computations, are operations that take time to complete and should not block the main thread, ensuring that the UI remains responsive. Flutter provides powerful tools to handle these operations, specifically through the async and await keywords, which make it easier to write asynchronous code in a readable and maintainable way.

In this blog, we’ll dive deep into how async and await work in Flutter, explore their usage, and provide practical examples to help you integrate them into your projects.

Async and Await in Flutter

What is Asynchronous Programming?

Asynchronous programming allows a program to perform tasks concurrently, meaning multiple tasks can be in progress at the same time. In Flutter, this is particularly important because the UI runs on the main thread, and blocking this thread can lead to a poor user experience, such as unresponsive interfaces or janky animations.

For example, when fetching data from a remote server, the process might take a few seconds. Instead of freezing the app until the data is retrieved, Flutter allows the operation to run in the background, and the app remains interactive.

The async and await Keywords in Flutter

async and await are the primary tools in Dart (the language used for Flutter) for writing asynchronous code. These keywords help manage asynchronous operations, making the code easier to read and write by avoiding complex callback chains.

How does async work?

The async keyword is used to mark a function as asynchronous. An asynchronous function can perform long-running tasks without blocking the main thread. When a function is marked as async, it automatically returns a Future, even if the function doesn’t explicitly return a Future.

How does await work?

The await keyword is used to pause the execution of an async function until the awaited Future completes. This allows you to write code that looks synchronous but operates asynchronously under the hood.

Using async and await in Flutter

Let’s explore how to use async and await in Flutter with practical examples.

1. Basic Example: Delayed Operation

Consider a simple example where we simulate a delay to represent a time-consuming operation:

Future<void> fetchData() async {
  print('Fetching data...');
  await Future.delayed(Duration(seconds: 3));
  print('Data fetched!');
}

void main() {
  fetchData();
  print('Waiting for data...');
}

Output:

Fetching data...
Waiting for data...
Data fetched!

Here’s what happens:

  1. The fetchData function is marked as async, which means it runs asynchronously.
  2. The await Future.delayed(Duration(seconds: 3)) line pauses the execution of fetchData for 3 seconds.
  3. The main function continues running, printing “Waiting for data…” before the data is fetched.
2. Handling Network Requests

A common use case for async and await in Flutter is making network requests. Suppose you need to fetch data from a REST API.

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

Future<void> fetchUser() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1'));

  if (response.statusCode == 200) {
    final userData = jsonDecode(response.body);
    print('User Name: ${userData['name']}');
  } else {
    print('Failed to load user data');
  }
}

void main() {
  fetchUser();
  print('Fetching user...');
}

Output:

Fetching user...
User Name: Leanne Graham

Explanation:

  1. The fetchUser function sends an HTTP GET request to fetch user data from a placeholder API.
  2. The await keyword ensures that the code waits for the HTTP request to complete before continuing.
  3. Once the data is fetched, it’s decoded and printed to the console.
3. Error Handling with Async and Await

Handling errors in asynchronous code is crucial to ensure that your app doesn’t crash unexpectedly. In Dart, you can handle errors using try, catch, and finally blocks.

Future<void> fetchUser() async {
  try {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1'));

    if (response.statusCode == 200) {
      final userData = jsonDecode(response.body);
      print('User Name: ${userData['name']}');
    } else {
      print('Failed to load user data');
    }
  } catch (e) {
    print('An error occurred: $e');
  } finally {
    print('Fetch user operation completed.');
  }
}

void main() {
  fetchUser();
  print('Fetching user...');
}

Output:

Fetching user...
User Name: Leanne Graham
Fetch user operation completed.

Explanation:

  1. The try block contains the code that might throw an error.
  2. The catch block captures and handles the error if one occurs.
  3. The finally block contains code that will run regardless of whether an error occurred, ensuring that any necessary cleanup or final steps are taken.
4. Async and Await in Widgets

In Flutter, you often need to work with asynchronous data within widgets, especially when fetching data from a server or database.

class UserWidget extends StatefulWidget {
  @override
  _UserWidgetState createState() => _UserWidgetState();
}

class _UserWidgetState extends State<UserWidget> {
  Future<String> fetchUserName() async {
    await Future.delayed(Duration(seconds: 2)); // Simulate network delay
    return 'Leanne Graham';
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchUserName(),
      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}');
        }
      },
    );
  }
}

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

Explanation:

  1. The fetchUserName function simulates a delay (representing a network request) and then returns a user name.
  2. The FutureBuilder widget is used to handle the asynchronous operation and update the UI based on the state of the Future.
    • When the future is still running (ConnectionState.waiting), a loading indicator is shown.
    • If an error occurs, an error message is displayed.
    • Once the data is available, it is displayed on the screen.
5. Chaining Async Operations

Sometimes, you need to perform multiple asynchronous operations in sequence. You can chain them together using await.

Future<void> performOperations() async {
  await operation1();
  await operation2();
  await operation3();
}

Future<void> operation1() async {
  print('Operation 1');
  await Future.delayed(Duration(seconds: 1));
}

Future<void> operation2() async {
  print('Operation 2');
  await Future.delayed(Duration(seconds: 1));
}

Future<void> operation3() async {
  print('Operation 3');
  await Future.delayed(Duration(seconds: 1));
}

void main() {
  performOperations();
  print('Performing operations...');
}

Output:

Performing operations...
Operation 1
Operation 2
Operation 3

Explanation:

Best Practices for Using Async and Await in Flutter

  1. Keep UI Responsive: Always use async operations for tasks that may take time, such as network requests or heavy computations, to keep the UI responsive.
  2. Handle Errors Gracefully: Always include error handling in your async functions to prevent the app from crashing unexpectedly.
  3. Use FutureBuilder for Asynchronous Data in Widgets: FutureBuilder is the preferred way to handle asynchronous data in Flutter widgets.
  4. Avoid Blocking the Main Thread: Never use await in a way that could block the main thread and freeze the UI. If an operation is too heavy, consider running it in a background isolate.

Conclusion

The async and await keywords are essential tools in Flutter for managing asynchronous operations effectively. They help you write clean, readable, and maintainable code without resorting to complex callback chains or deeply nested .then() statements. By mastering async and await, you can ensure that your Flutter applications remain smooth, responsive, and user-friendly, even when performing time-consuming tasks.

Start incorporating these concepts into your Flutter projects today, and you’ll be well on your way to building efficient and high-performing mobile applications!

Show Your Support

Explore Other Flutter Topics…

  1. What is Flutter
  2. Stateful VS Stateless Widget
  3. Page Transition in Flutter
  4. Web Animation in Flutter
  5. Flutter Injectable
  6. Dependency Injection in Flutter
  7. Flutter Reusable custom Widgets
  8. Flutter Custom Shapes
  9. Build Responsive UIs in Flutter using MediaQuery

2 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *