You are on page 1of 37

UNIT 3

ASYNCHRONOUS PROGRAMMING

Asynchronous programming is a crucial aspect of modern software development, allowing to


write code that does not block the main thread and enables the application to handle
tasks concurrently.
This means that you can perform tasks that might take time to complete, such as network
requests, file I/O, or other operations, without freezing or blocking the user interface (UI).
This is especially important in environments like Flutter, where user interfaces should remain
responsive.

Flutter provides a robust framework for working with asynchronous operations.


Asynchronous programming is primarily achieved through the use of Dart’s built-in
asynchronous features.

Async and Await:


Flutter uses the async and await keywords to perform asynchronous operations.
Future and FutureBuilder:
The Future class represents a potential value or error that will be available at some time in
the future. It is often used to handle one-time asynchronous operations.
The FutureBuilder widget helps you manage and present the result of a Future within your
UI. It rebuilds the widget when the Future completes.

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.

The fetchUserData function simulates a network request by using ‘await


Future.delayed(Duration(seconds: 2))’

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.

void fetchData() async {


print(“Fetching data from source 1”);
String data1 = await fetchFromSource1();
print(“Data from source 1: $data1”);

print(“Fetching data from source 2”);


String data2 = await fetchFromSource2();
print(“Data from source 2: $data2”);

// Continue processing the data


}

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 with then


The then method is often used with Futures to schedule a callback function to be executed
when the Future completes.

Using ‘Future’ with ‘then’ in asynchronous programming in Flutter:


1. Creating Futures:
Create a Future using the Future constructor or by using factory functions like
‘Future.value’ or ‘Future.error’.

Future<int> fetchNumber() {
return Future.value(42);
}

2. Chaining with ‘then’:


The then method allows to specify a callback function that will be executed when the
‘Future’ completes.
fetchNumber().then((value) {
print('Fetched number: $value');
});
3. Chaining Multiple then Blocks
You can chain multiple ‘then’ blocks to perform a sequence of asynchronous operations.
fetchNumber()
.then((value) => performOperation(value))
.then((result) => handleResult(result))
.catchError((error) => handleError(error));

4. Handling Errors with catchError:


Use the ‘catchError’ method to handle errors that occur during the execution of the Future.

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.

Example: Fetching data from a server

import ‘package:flutter/material.dart’;
void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {


@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {


Future<String> fetchData() {
// Simulate a network request that takes 2 seconds
return Future.delayed(Duration(seconds: 2), () {
return “Hello”;
});
}

@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.

The ElevatedButton triggers the asynchronous operation when pressed. It calls


fetchData().then(...) to handle the successful completion of the Future and catchError(...) to
handle any errors that may occur.
When the data is successfully fetched, an AlertDialog is displayed with the fetched data. If an
error occurs, an AlertDialog with an error message is shown.

USING ASYNC/AWAIT TO REMOVE CALLBACKS

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;

Future<void> fetchData() async {


final response = await http.get(‘https://jsonplaceholder.typicode.com/todos/1’);
if (response.statusCode == 200) {
// Data retrieval was successful
print(‘Data: ${response.body}’);
} else {
// Handle errors
print(‘Error: ${response.statusCode}’);
}
}
1. Importing the http package:
import ‘package:http/http.dart’ as http;

2. Defining an asynchronous function named ‘fetchData’:


Future<void> fetchData() async {

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.

3. Making an HTTP GET request:


final response = await http.get(‘https://jsonplaceholder.typicode.com/todos/1’);

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.

4. Checking the response status code:

if (response.statusCode == 200) {

The code checks if the HTTP response status code is 200, which indicates a successful
request.

5. Handling a successful response:


print(‘Data: ${response.body}’);

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:

Use try/catch block to handle errors in asynchronous code.

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.

import ‘package:http/http.dart’ as http;


void fetchData() async {
try {
final response = await http.get(‘https://example.com/api/data’);
if (response.statusCode == 200) {
// Data retrieval was successful
print('Data: ${response.body}');
} else {
// Handle errors (e.g., server returned an error response)
print(‘Error: ${response.statusCode}’);
}
} catch (e) {
// Handle exceptions (e.g., network request failed)
print(‘Exception: $e’);
}
}

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.

4. HTTP GET Request:


Inside the try block, the code makes an HTTP GET request using
‘http.get(‘https://example.com/api/data’)’.
The result of this request is stored in the response variable, and the await keyword is used to
pause the execution until the request is complete.

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.

Parallel asynchronous operations:

In order to perform multiple asynchronous operations in parallel, use Future.wait.

Future<void> fetchData() async {

var data1 = fetchDataFromServer1();

var data2 = fetchDataFromServer2();

// Wait for both operations to complete

await Future.wait([data1, data2]);

// Continue with the data

COMPLETING FUTURES IN FLUTTER

 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.

To complete Futures in Flutter,

COMPLETER CLASS:

The Completer class in Flutter is a common way to complete Futures manually.

It allows you to create a Future and manually control when it is completed or resolved with a
value.

How to use ‘Completer’:

1. CREATING A COMPLETER

Completer<String> completer = Completer<String>();

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.

2. COMPLETING THE FUTURE

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:

completer.completeError(Error(“Something went wrong”));

4. ACCESSING THE FUTURE


Once you have completed the ‘Completer’, you can obtain the Future using the future
property:

Future<String> myFuture = completer.future;

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.

1. MARKING A FUNCTION AS ‘ASYNC’

To use async/await, you need to mark a function as ‘async’:

Future<void> fetchData() async {

// Asynchronous code here

2. Awaiting a Future:

Within an ‘async’ function use the ‘await’ keyword to wait for the completion of a Future:

String data = await fetchSomeData();

3. CALLBACKS AND ‘.then()’:

You can also use callbacks and the ‘.then()’ method to handle the completion of a Future.

fetchSomeData().then((result) {

// Handle the result

}).catchError((error) {

// Handle the 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(),

builder: (BuildContext context, AsyncSnapshot<String> snapshot) {

if (snapshot.connectionState == ConnectionState.done) {

// Handle the data

return Text(snapshot.data);

} else if (snapshot.hasError) {

// Handle the error

return Text(‘Error: ${snapshot.error}’);

} else {

// Loading state

return CircularProgressIndicator();

},

);

Completing with a Value:


You can complete a Future with a specific value using the Future.value() constructor.
Example:
Future<int> fetchValue() {
return Future.value(42);
}
Completing with an Error:
To complete a Future with an error, you can use the Future.error() constructor.

Future<int> fetchError() {
return Future.error(Exception(‘An error occurred’));
}

Completing with Delay:


The Future.delayed() constructor allows to complete a Future after a specified duration.
This is useful for simulating delayed operations:
Future<int> fetchDelayedValue() {
return Future.delayed(Duration(seconds: 2), () => 42);
}

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.

FIRING MULTIPLE FUTURES AT THE SAME TIME

In Flutter, handling multiple asynchronous operations concurrently is essential for optimizing


app performance.
FutureGroup in Flutter allows you to efficiently fire multiple futures simultaneously,
improving the responsiveness and performance of your application.

This is particularly useful when dealing with independent asynchronous tasks that can be
executed concurrently.

FutureGroup is a class that facilitates firing multiple futures simultaneously.

Using FutureGroup:

FutureGroup is part of the async package, which provides utilities for asynchronous
programming.

To use it, add the async package to pubspec.yaml file:

dependencies:

async: ^2.8.2

Then, run flutter pub get to fetch the package.

import ‘package:async/async.dart’;

Future<void> main() async {

print(“Start”);

FutureGroup<String> futureGroup = FutureGroup<String>();

// Add futures to the FutureGroup

for (int i = 1; i <= 3; i++) {

futureGroup.add(() => fetchData(“Task $i”));

// Wait for all futures to complete

List<String> results = await futureGroup.close();

results.forEach((result) => print(result));


print(“End”);

Future<String> fetchData(String task) async {

// Simulate a network request

await Future.delayed(Duration(seconds: 2));

return “$task: Data loaded”;

Benefits of FutureGroup:

 Concurrent Execution: Futures added to ‘FutureGroup’ are executed concurrently,


potentially improving performance.
 Cleaner Code: FutureGroup simplifies the code for firing multiple futures by
managing the concurrency for you.
 Ordered Results: The order of results in the final list corresponds to the order in which
the futures were added to the group.

Firing Single Future:

Future<String> fetchData() async {

// Simulate a network request

await Future.delayed(Duration(seconds: 2));

return “Data loaded”;

void main() async {

print(“Start”);

String result = await fetchData();

print(result);
print(“End”);

Firing Multiple Futures Concurrently:

Future.wait function is used to execute multiple futures concurrently.

This is useful when there are independent asynchronous tasks that can be performed
simultaneously.

Future<void> main() async {

print(“Start”);

List<Future<String>> futures = [

fetchData(“Task 1”),

fetchData(“Task 2”),

fetchData(“Task 3”),

];

List<String> results = await Future.wait(futures);

results.forEach((result) => print(result));

print(“End”);

Future<String> fetchData(String task) async {

// Simulate a network request

await Future.delayed(Duration(seconds: 2));

return “$task: Data loaded successfully”;

}
Using async and await with Loops:

You can use async and await with loops to fire multiple futures dynamically.

Future<void> main() async {

print(“Start”);

List<String> tasks = [“Task 1”, “Task 2”, “Task 3”];

List<Future<String>> futures = [];

for (String task in tasks) {

futures.add(fetchData(task));

List<String> results = await Future.wait(futures);

results.forEach((result) => print(result));

print(“End”);

Future<String> fetchData(String task) async {

// Simulate a network request

await Future.delayed(Duration(seconds: 2));

return “$task: Data loaded successfully”;

RESOLVING ERRORS IN ASYNCHRONOUS CODE

Efficient error handling is crucial in asynchronous programming to ensure the stability and
reliability of Flutter applications.

Two primary approaches:

1. Dealing with errors using then() callback


2. Dealing with errors using async/await
1.Dealing with errors using then() callback:

The ‘then()’ method is employed to handle the result of a Future and manage errors.

fetchData().then(

(result) {

// Handle successful completion

print(‘Data loaded: $result’);

},

onError: (error) {

// Handle errors

print(‘Error: $error’);

},

);

Chaining then() Callbacks:

Chaining then() callbacks allows for sequencing asynchronous operations.

fetchData()

.then((result) => processResult(result))

.then((processedResult) => displayResult(processedResult))

.catchError((error) => handleError(error));

2.Handling Errors with async/await:

The try-catch block effectively captures errors in asynchronous functions.

Future<void> fetchDataAndHandleError() async {

try {
String result = await fetchData();

print(“Data: $result”);

} catch (error) {

print(“Error: $error”);

then() and async/await:

then() Callback:

 Useful for handling multiple asynchronous operations concurrently.


 Allows chaining of operations in a smooth manner.

async/await:

 Provides a more synchronous and straightforward syntax.


 Often preferred for sequential and more readable code.

Combining the then() callback and async/await provides a versatile and robust approach to
error handling in Flutter.

USING FUTURES WITH STATEFULWIDGETS

 Using Futures with StatefulWidgets in Flutter is a common practice for handling


asynchronous operations while managing the state of a widget.
 StatefulWidgets are typically used for UI components that need to respond to
changes in their state, and Futures are used for managing asynchronous operations
such as network requests, database queries, or file I/O.

How to use Futures with StatefulWidgets in Flutter:


1. Import Necessary Packages:
Make sure to import the necessary packages:
import ‘package:flutter/material.dart’;

2. Declare a Future in State:


In the StatefulWidget class, declare a Future object to represent the asynchronous operation.

class MyWidget extends StatefulWidget {


@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {


Future<Data> fetchData() async {
// Your asynchronous operation here
}
// ...
}

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.

class _MyWidgetState extends State<MyWidget> {


Future<Data> fetchData() async {
// Your asynchronous operation here
}

@override
void initState() {
super.initState();
fetchData().then((data) {
setState(() {
// Update the widget state with the fetched data
});
});
}
// ...
}

4. Show Loading Indicators:


While waiting for the Future to complete, you might want to show a loading indicator to
provide feedback to the user.

class _MyWidgetState extends State<MyWidget> {


bool isLoading = true;

Future<Data> fetchData() async {


// Your asynchronous operation here
}

@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.

Triggering a refresh in a Flutter application:

 Allowing the user to manually initiate an asynchronous operation, such as fetching


new data or refreshing the content displayed on the screen.
 Typically, this is done by tapping a “Refresh” button or taking some other user-
initiated action.
 When you trigger a refresh, you want to reset the state of the data to indicate that a
new asynchronous operation is in progress.
 To achieve this, you set the ‘_data’ variable to null and then call the ‘setState’ method
to rebuild the widget.

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.

fetchData(): It is a function responsible for initiating the asynchronous operation, such as


fetching new data.
The exact implementation of this function depends on your specific use case, but it should
return a Future that represents the result of the operation.
Therefore,
When the user calls refreshData(), the following happens:
_data is set to null, indicating that the data is no longer available or valid. setState() is called,
which marks the widget as dirty and schedules a rebuild of the widget. Inside the build
method of the widget, you can check if ‘_data’ is ‘null’, and if so, display a loading indicator.
As the asynchronous operation (e.g., ‘fetchData()’) progresses, it will update ‘_data’ with the
new data when it is available. When the Future completes, Flutter will automatically rebuild
the widget, displaying the new data, and you can handle any errors or display the data
accordingly.
This ensures a responsive and user-friendly experience when dealing with asynchronous
tasks.

TURNING NAVIGATION ROUTES INTO ASYNCHRONOUS FUNCTIONS

 Turning navigation routes into asynchronous functions in Flutter can be a useful


technique for performing tasks such as data loading, API calls, or other asynchronous
operations when navigating between different screens or routes in your app.
 To achieve this, you can use Flutter’s built-in routing system and integrate
asynchronous code appropriately.

Understanding Asynchronous Navigation


In Flutter, navigation is typically managed by a Navigator widget. It pushes and pops routes
onto a navigation stack.
Asynchronous navigation means you want to perform asynchronous tasks, like fetching
data from a server, when navigating to a new route.

Using async and await


You can use the async and await keywords in Flutter to work with asynchronous code.
Mark the function that handles your route as async. For example:

Future<void> _navigateToNewScreen(BuildContext context) async {


// Asynchronous code can go here
await fetchDataFromAPI();
Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewScreen()));
}

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:

Future<void> fetchDataFromAPI() async {


var response = await http.get(‘https://api.example.com/data’);
if (response.statusCode == 200) {
// Data retrieved successfully
var data = json.decode(response.body);
// Do something with the data
} else {
// Handle errors
}
}
Error Handling
Be sure to handle errors gracefully, especially when dealing with network requests.
So, you can use `try` and `catch` blocks to capture exceptions and provide feedback to the
user.
try {
var response = await http.get(url);
// Process response
} catch (e) {
// Handle network or other errors
}

Executing Asynchronous Code Before Navigation


You can use asynchronous functions before pushing a new route, ensuring that the code
completes before the navigation occurs.

Showing Loading Indicators


While data is being fetched asynchronously, it is a good practice to show loading indicators
to provide feedback to the user.

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.

Using Flutter Routes


Define the routes using `MaterialApp` and `Navigator`:
MaterialApp(
routes: {
‘/’: (context) => HomeScreen(),
‘/newScreen’: (context) => NewScreen(),
},
)
Then, you can navigate asynchronously to the new screen using a function:

_navigateToNewScreen(BuildContext context) async {


await fetchDataFromAPI();
Navigator.of(context).pushNamed('/newScreen');
}

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.

GETTING THE RESULTS FROM A DIALOG

 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.

How to get results from a dialog in Flutter:

1. OPEN A DIALOG

To open a dialog, use ‘showDialog’ method.

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.

Future<void> _openDialog(BuildContext context) async {

final result = await showDialog(


context: context,

builder: (BuildContext context) {

return AlertDialog(

title: Text('Dialog Title'),

content: Text('Dialog content'),

actions: <Widget>[

TextButton(

child: Text('Cancel'),

onPressed: () {

Navigator.pop(context, 'Cancelled');

},

),

TextButton(

child: Text('OK'),

onPressed: () {

Navigator.pop(context, ‘Confirmed’);

},

),

],

);

},

);

// Handle the result from the dialog

print('Dialog Result: $result');


}

2. PASS DATA WHEN POPPING

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.

3. HANDLE THE RESULT

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.

Future<void> _openDialog(BuildContext context) async {

final result = await showDialog(

context: context,

builder: (BuildContext context) {

// ...

},

);

if (result != null) {

// Handle the result

print(‘Dialog Result: $result’);

} else {
// Handle the case where the dialog was dismissed without making a choice

print(‘Dialog was dismissed’);

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.

6. USING A CUSTOM DIALOG

 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 {

final String userInput;

CustomDialogResult(this.userInput);

Future<void> _openCustomDialog(BuildContext context) async {

final result = await showDialog<CustomDialogResult>(

context: context,

builder: (BuildContext context) {

String userInput = '';

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.

You might also like