Unit 3
Unit 3
ASYNCHRONOUS PROGRAMMING
Future:
The Future class represents a potential value or error that will be available at some
time in the future.
It essentially allows you to work with values that may not be immediately available.
These values can be the result of asynchronous operations, like fetching data from
a server or reading a file from storage.
It is often used to handle one-time asynchronous operations. For example, when you
make an HTTP request, you don’t immediately have the response data. Instead, you
get a Future that promises to deliver the data once it is ready.
FutureBuilder:
The FutureBuilder widget is a powerful tool that helps to manage and present the
result of a Future within your user interface (UI).
It is designed to work seamlessly with Future objects to make it easier to handle
asynchronous data and update the UI when the Future completes.
It rebuilds itself when the Future completes (when it has a value or an error). This
allows you to show different widgets or UI elements based on the state of the Future.
Async Functions:
Asynchronous functions can be defined using the async keyword. These functions return a
Future object that represents the result of the asynchronous computation.
Example:
Future<void> fetchData() async {
// Asynchronous operation
}
Future - ‘Future’ is a generic class used to represent a value or error that will be available at
some point in the future.
Future<void>, which means it represents a future value that will be of type ‘void’.
async -The async keyword is used to mark a function as asynchronous.
An asynchronous function can perform operations that may take some time to complete, such
as network requests, file I/O, or other asynchronous tasks, without blocking the execution of
the program.
Inside the ‘fetchData’ function, you typically perform some asynchronous operation, such as
making an HTTP request, reading from a file, or performing any other non-blocking task.
“// Asynchronous operation” indicates where you would include the actual code for the
asynchronous operation you want to perform.
Error Handling:
When working with asynchronous operations, it is crucial to handle errors properly. try-catch
blocks are used to capture exceptions.
Example:
try {
// Perform asynchronous operation
} catch (e) {
// Handle the error
}
Awaiting Results:
The await keyword can be used to pause the execution of an async function until the awaited
operation is completed.
Example:
Future<String> fetchUserData() async {
// Simulate a network request
await Future.delayed(Duration(seconds: 2));
return ‘User Data’;
}
The above example defines an asynchronous function called ‘fetchUserData’, which returns a
‘Future<String>’
The async keyword is used to mark the function as asynchronous, allowing it to perform non-
blocking operations without freezing the program’s execution.
Future.delayed creates a ‘Future’ that completes after a specified duration. In this case, it
waits for 2 seconds (simulating a delay that often occurs during a network request).
The await keyword is used to wait for this delay to complete before proceeding further in the
function.
After the delay, the function returns the string ‘User Data’.
Here is how you might use the fetchUserData function in your code:
void main() async {
print(‘Fetching user data...’);
final userData = await fetchUserData();
print(‘User data received: $userData’);
}
In this example:
1. ‘main’ is marked as async, allowing the use of await.
2. It starts by printing a message, "Fetching user data"
3. It then calls fetchUserData using await, which ensures that the program waits for the
fetchUserData function to complete.
4. After fetchUserData completes (in this case, after a 2-second delay), it prints “User data
received: User Data.”
By using async and await, it is easier to handle asynchronous operations like network
requests in a synchronous and more readable manner, which can improve code clarity and
maintainability.
Multiple Awaits:
You can use multiple await statements in a row to await multiple asynchronous operations.
Example:
String result1 = await operation1();
String result2 = await operation2();
There are two await statements in a row, each awaiting the result of a different asynchronous
operation.
By using multiple ‘await’ statements in this way, you ensure that each asynchronous
operation is performed sequentially. The second operation (operation2) will not start until the
first one (operation1) is completed.
In this example, fetchData sequentially fetches data from two different sources using
asynchronous operations, waiting for the result of each operation before moving on to the
next one.
This ensures that the operations are executed in order, making it easier to handle and process
data in a controlled manner.
Using multiple await statements in a row is a powerful mechanism for dealing with
asynchronous operations in a more synchronous and readable way, enhancing code
organization and maintainability.
Future<int> fetchNumber() {
return Future.value(42);
}
fetchNumber().then((value) {
// Do something with the value
}).catchError((error) {
// Handle the error
});
The Future and then constructs provide a powerful way to manage such asynchronous
workflows in Dart and Flutter.
import ‘package:flutter/material.dart’;
void main() {
runApp(MyApp());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Future with then’),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// When the button is pressed, fetch data asynchronously
fetchData().then((result) {
// This block executes when the Future is completed
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(‘Fetched Data’),
content: Text(result),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(‘OK’),
),
],
),
);
}).catchError((error) {
// Handle errors that may occur during the Future execution
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(‘Error’),
content: Text(‘An error occurred: $error’),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(‘OK’),
),
],
),
);
});
},
child: Text(‘Fetch Data’),
),
),
);
}
}
The fetchData function returns a Future<String>. This function simulates a network request
using ‘Future.delayed’ to resolve the ‘Future’ after 2 seconds.
Traditionally, asynchronous operations were managed using callbacks, which could lead to
callback hell.
A callback is a function that you provide to another function, typically as an argument. This
function is then called (or “called back”) at a later point in the program’s execution when a
specific event or condition occurs.
It is commonly used in programming to allow you to define what should happen when
something happens.
For example, in JavaScript, you might use a callback function to specify what should occur
after a file is successfully loaded, after a user clicks a button, or when a timer expires.
Callback hell, also known as “pyramid of doom” is a term used in programming to describe
a situation where there is a deeply nested structure of callback functions in the code.
This typically occurs in asynchronous programming, where multiple operations are
performed one after another, and each operation relies on the result of the previous one.
Callback hell can make the code difficult to read, understand, and maintain, as it creates a
pyramid-like structure of indentation with numerous callback functions, making it hard to
follow the logic and control flow of your program.
Async/await simplifies this by making asynchronous code look more like synchronous code.
Using async Function:
To create an asynchronous function, mark it with the async keyword.
Example:
Future<int> fetchData() async {
// Asynchronous code here
}
Future<int> - This indicates the return type of the fetchData function. It specifies that the
function will return a value of type int at some point in the future, but it may not be
immediately available.
async - The async keyword is used to declare that this function contains asynchronous code.
It allows you to use await inside the function to pause its execution until an asynchronous
operation is finished.
Inside the fetchData function, perform asynchronous operations like making network
requests, reading/writing files, or other tasks that can take time. These operations often return
Future objects.
Example: Using the ‘fetchData’ function to make an HTTP request using the http
package in Flutter
import ‘package:http/http.dart’ as http;
This function is marked as async, indicating that it may perform asynchronous operations,
such as making an HTTP request. It returns a ‘Future’ object of type void, it does not return a
value.
Here, the ‘http.get’ function is used to send an HTTP GET request to the URL
‘https://jsonplaceholder.typicode.com/todos/1’. The await keyword is used to wait for the
response to be received before proceeding. The response is stored in the response variable.
if (response.statusCode == 200) {
The code checks if the HTTP response status code is 200, which indicates a successful
request.
If the status code is 200, the response body is printed. This typically contains the data
retrieved from the API.
6. Handling errors:
print(‘Error: ${response.statusCode}’);
}
If the status code is not 200 (indicating an error), an error message is printed, including the
actual status code returned by the server.
In summary, this code defines an asynchronous function ‘fetchData’ that makes an HTTP
GET request to a specified URL using the http package.
It then checks the response status code, and if it is 200, it prints the response data. If the
status code is anything other than 200, it prints an error message along with the status code.
Using await:
Inside an asynchronous function, you can use the await keyword to pause the function’s
execution until a Future is completed. For example:
void process() async {
var result = await fetchData();
print(result);
}
Here, when you call the process function, it will begin execution.
It encounters the await fetchData() line and pauses.
The fetchData function is called asynchronously.
The await keyword waits until the fetchData function is completed.
Once the fetchData function finishes its asynchronous operation and returns a result, the
result is stored in the result variable.
The print(result) line is executed, and the result is printed to the console.
Error Handling:
1. try Block:
The code within the ‘try’ block contains the asynchronous operation that you want to
perform.
This operation might involve actions like making network requests, reading files, or any other
potentially error-prone tasks.
2. catch Block:
The ‘catch’ block is used to catch and handle any exceptions or errors that occur within the
try block.
The variable ‘e` in the parentheses ‘(e)’ is typically used to capture the exception that caused
the error.
3. Error Handling:
If an exception is thrown within the try block, the execution of the try block is immediately
halted, and control is transferred to the catch block.
In the ‘catch’ block, you can write code to handle the error. This might involve logging the
error, showing an error message to the user, or taking some corrective action.
Explanation:
A function that uses the ‘http’ package to make an HTTP GET request to a URL. The
function is named fetchData and is marked as async. It is designed to fetch data from a
remote server and handle various outcomes, including successful data retrieval, HTTP errors,
and exceptions.
1. Importing the http Package: The first line of the code imports the ‘http’ package. This
allows you to use the functions and classes provided by the http package in the code.
2. fetchData Function: Declared as an asynchronous function with the ‘async’ keyword. This
means it can perform asynchronous operations without blocking the program’s execution.
3. Try-Catch Block:
The majority of the code is contained within a try-catch block. This structure is used to
handle exceptions and HTTP errors that might occur during the execution of the HTTP
request.
5.Response Handling:
The code then checks the status code of the HTTP response (‘response.statusCode’):
If the status code is ‘200’, it is considered a successful data retrieval, and the response body
is printed to the console.
If the status code is not ‘200’, it is considered an error, and the status code is printed to the
console.
You can customize the error handling logic here, such as displaying an error message to the
user or taking appropriate action.
6. Exception Handling:
The catch block is used to catch exceptions. In this case, it is used to handle exceptions
related to the HTTP request. If an exception occurs (e.g., network request failure), the
exception message (‘e’) is printed to the console.
In this example:
The try block contains the asynchronous operation to make an HTTP request.
The catch block handles exceptions or errors that might occur during the request, such
as network errors, invalid URLs, or any other issues.
Within the catch block, you can log the error (‘e’) or take any necessary corrective
actions.
Concurrent Operations:
await does not block the entire application. It only pauses the current function, allowing other
tasks to run in the meantime.
Sequential Execution:
When you use await within a function, it ensures that the subsequent code is executed only
after the awaited Future is completed.
Futures in Flutter are a way to represent asynchronous operations that may complete
at some point in the future.
These asynchronous operations can include network requests, file I/O, and other tasks
that may take some time to complete.
To handle the results of these asynchronous operations, you often need to complete or
resolve the Future. This process is crucial for ensuring that your app remains
responsive and can provide feedback to the user when the operation is finished.
COMPLETER CLASS:
It allows you to create a Future and manually control when it is completed or resolved with a
value.
1. CREATING A COMPLETER
Here, ‘String’ is the type of the value you expect to return when completing the Future. It can
be any data type or even dynamic.
You can complete the ‘Completer’ and return a value using the complete() method:
completer.complete(“Data to be returned”);
3. HANDLING ERRORS
If an error occurs, you can use completeError() to complete the Future with an error:
You can then use this Future to handle the asynchronous result.
async/await:
The async/await syntax is another way to complete Futures in a more readable and
synchronous-looking manner.
It simplifies working with asynchronous code by allowing you to write code that looks
similar to synchronous code, making it easier to understand.
2. Awaiting a Future:
Within an ‘async’ function use the ‘await’ keyword to wait for the completion of a Future:
You can also use callbacks and the ‘.then()’ method to handle the completion of a Future.
fetchSomeData().then((result) {
}).catchError((error) {
});
4. USING FUTUREBUILDER WIDGET:
Flutter provides the FutureBuilderwidget, which simplifies working with Futures in your UI.
It allows you to asynchronously build and update widgets based on the completion status of a
Future.
FutureBuilder<String>(
future: fetchData(),
if (snapshot.connectionState == ConnectionState.done) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
} else {
// Loading state
return CircularProgressIndicator();
},
);
Future<int> fetchError() {
return Future.error(Exception(‘An error occurred’));
}
Completing Asynchronously:
You can also complete a Future asynchronously using async/await or by returning another
Future.
Future<int> fetchAsyncValue() async {
await someTimeConsumingOperation();
return 42; }
In summary, completing Futures in Flutter is a crucial part of handling asynchronous
operations.
You can use the ‘Completer’ class, ‘async/await’, callbacks, or the FutureBuilder
widget to work with Futures in a way that makes the code readable and maintainable
while providing a responsive user experience.
The choice of method depends on the complexity of the asynchronous task and the
structure of the Flutter application.
This is particularly useful when dealing with independent asynchronous tasks that can be
executed concurrently.
Using FutureGroup:
FutureGroup is part of the async package, which provides utilities for asynchronous
programming.
dependencies:
async: ^2.8.2
import ‘package:async/async.dart’;
print(“Start”);
Benefits of FutureGroup:
print(“Start”);
print(result);
print(“End”);
This is useful when there are independent asynchronous tasks that can be performed
simultaneously.
print(“Start”);
List<Future<String>> futures = [
fetchData(“Task 1”),
fetchData(“Task 2”),
fetchData(“Task 3”),
];
print(“End”);
}
Using async and await with Loops:
You can use async and await with loops to fire multiple futures dynamically.
print(“Start”);
futures.add(fetchData(task));
print(“End”);
Efficient error handling is crucial in asynchronous programming to ensure the stability and
reliability of Flutter applications.
The ‘then()’ method is employed to handle the result of a Future and manage errors.
fetchData().then(
(result) {
},
onError: (error) {
// Handle errors
print(‘Error: $error’);
},
);
fetchData()
try {
String result = await fetchData();
print(“Data: $result”);
} catch (error) {
print(“Error: $error”);
then() Callback:
async/await:
Combining the then() callback and async/await provides a versatile and robust approach to
error handling in Flutter.
3. Manage State:
Use the setState method to manage the state of the widget when the asynchronous operation
completes. Update the state within the then callback of the Future or within an async
function.
@override
void initState() {
super.initState();
fetchData().then((data) {
setState(() {
// Update the widget state with the fetched data
});
});
}
// ...
}
@override
void initState() {
super.initState();
fetchData().then((data) {
setState(() {
// Update the widget state with the fetched data
isLoading = false;
});
});
}
@override
Widget build(BuildContext context) {
if (isLoading) {
return CircularProgressIndicator();
} else {
// Build your widget with the fetched data
}
}
//
}
5. Error Handling:
Handle errors that might occur during the asynchronous operation. Use a try-catch block or
the catchError method on the Future.
5. Complete the UI
Once the Future completes, update the UI with the fetched data. This might involve
creating widgets or modifying existing ones based on the data.
void refreshData() {
setState(() {
_data = fetchData();
});
}
refreshData(): This is a custom function that is defined. It is intended to be called when the
user triggers a refresh action, such as tapping a refresh button in the user interface.
setState(): This is a method is used to mark the part of the widget tree as dirty and request a
rebuild of the widget.
When you call setState method, it schedules a call to the build method, effectively triggering
a UI update.
_data: This is a variable that holds a Future representing the result of an asynchronous
operation, like fetching data from a network or database.
By setting ‘_data’ to null, you are indicating that a new operation is about to start, and the
widget should display a loading state or indicator.
Fetching Data
If you need to fetch data before navigating, you can await the data retrieval operation. For
example, using the http package for API calls:
Updating the UI
After fetching data asynchronously, you can update the UI with the new data, either by
using setState for stateful widgets or by passing data as arguments to the new screen.
By turning navigation routes into asynchronous functions in Flutter, you can create
responsive and data-driven applications that fetch and display information as needed when
users move between screens.
This enhances the user experience and ensures that data is available when it is required.
In Flutter, you can retrieve results from a dialog by using the Navigator and
Navigator.pop() method to close the dialog and pass data back to the calling widget.
This process is often referred to as popping the dialog.
1. OPEN A DIALOG
This method displays a dialog and returns a Future that resolves when the dialog is
dismissed.
The showDialog method allows you to define the content of the dialog, including buttons and
other interactive elements.
return AlertDialog(
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context, 'Cancelled');
},
),
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.pop(context, ‘Confirmed’);
},
),
],
);
},
);
When the user interacts with the dialog and you want to close it while passing back a
result, use Navigator.pop(context, result) where ‘context’ is the context of the dialog and
‘result’ is the data you want to return.
After the dialog is dismissed, the await showDialog line will resolve with the result that
was passed to ‘Navigator.pop’. You can handle this result in the calling widget.
4. ERROR HANDLING
Handle exceptions that may occur when working with dialogs, such as if the user
closes the dialog without taking any action.
Always check if the result is null to handle such cases.
context: context,
// ...
},
);
if (result != null) {
} else {
// Handle the case where the dialog was dismissed without making a choice
5. USING ‘ASYNC/AWAIT’
To make the code more readable, use ‘async/await’ to open and handle the dialog.
This allows you to write code in a sequential and clear manner.
Create custom dialog widgets that contain specific form fields or widgets can be
created,
And then retrieve the data entered by the user when the dialog is closed.
class CustomDialogResult {
CustomDialogResult(this.userInput);
context: context,
return AlertDialog(
title: Text(‘Custom Dialog'),
content: TextField(
onChanged: (value) {
userInput = value;
},
),
actions: <Widget>[
TextButton(
child: Text(‘Cancel’),
onPressed: () {
Navigator.pop(context, null);
},
),
TextButton(
child: Text(‘OK’),
onPressed: () {
Navigator.pop(context, CustomDialogResult(userInput));
},
),
],
);
},
);
if (result != null) {
print(‘User entered: ${result.userInput}’);
By following these steps, you can create dialogs in Flutter that allow users to interact with
your app and return results, making your app more dynamic and user-friendly.