Mastering Asynchronous Flutter
Explore the depths of asynchronous programming in Flutter with our detailed guide.
Whether you're a beginner or an experienced developer, discover best practices and code samples to elevate your Flutter projects to the next level.
Don't miss out on essential insights into async programming with Flutter and enhance your skills for large-scale project success! 🚀
Synchronous: Code is executed sequentially, and each operation must wait for the previous one to complete.
Asynchronous: Code doesn't wait for the completion of each operation and continues with the next one. Callbacks, Promises, or Futures are often used.
..
async and await:
- async: Used to define a function that performs asynchronous operations. It allows the function to use the await keyword.
- await: Used inside an async function to pause execution until a Future completes and returns its result.
Future<int> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 42;
}
..
Future and Future<void>:
- Future: Represents a value that might be available in the future. It's used for asynchronous computations that yield a single result.
- Future<void>: Represents an asynchronous operation that doesn't return a value.
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 2), () => 'Data fetched');
}
..
Using try, catch, and finally:
you can handle exceptions in asynchronous code using try, catch, and finally blocks
Future<void> fetchData() async {
try {
// Asynchronous operation that might throw an exception
await Future.delayed(Duration(seconds: 2));
throw Exception('Simulated error');
} catch (error) {
// Handle the exception
print('Error: $error');
} finally {
// Code that runs whether an exception occurred or not
print('Cleanup or additional logic');
}
}
void main() {
fetchData();
}
..
Using catchError: method is used to register a callback that will be invoked if the Future completes with an error.
It allows you to handle errors in a way that doesn't break the entire program
Future<void> fetchData() async {
// Asynchronous operation that might throw an exception
await Future.delayed(Duration(seconds: 2));
throw Exception('Simulated error');
}
void main() {
fetchData().catchError((error) {
// Handle the exception
print('Error: $error');
});
}
..
Future.then: method is used to register a callback that will be invoked when the Future completes successfully.
It allows you to chain multiple asynchronous operations together.
Future<int> fetchData() async {
// ... asynchronous operation ...
return 42;
}
fetchData().then((result) {
print('Data fetched successfully: $result');
});
It's common to use both then and catchError together for comprehensive error handling..
..
Stream:
Represents a sequence of asynchronous events. It's used for continuous data flow, allowing you to react to data as it arrives.
- async*: This indicates that the function is asynchronous and returns a stream.
- yield i: This statement adds the current value of i to the stream, allowing consumers of the stream to receive these values asynchronously.
// Using Stream
Stream<int> countNumbers() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
void main() {
// Subscribe to the stream
countNumbers().listen((int number) {
// This callback will be invoked for each value emitted by the stream
print(number.toString());
// You can perform any action you need with the emitted values
});
}
..
await for:
await for is used to asynchronously iterate over stream events.
It allows awaiting each event in a stream sequentially, making it suitable for asynchronous stream processing.
// Using Stream
Stream<int> countNumbers() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
Future<void> main() async {
// Using await for to iterate over the stream
await for (int number in countNumbers()) {
// This block will be executed for each value emitted by the stream
print(number.toString());
}
print('Stream completed.');
}
..
FutureBuilder is a Flutter widget that rebuilds when a Future completes. It is used for handling asynchronous operations in the context of building UI.
..
Completer
- A Completer is used to produce a Future and control when it completes.
- It is handy when working with non-Future-based asynchronous APIs.
import 'dart:async';
void main() {
// Create a Completer
Completer<String> completer = Completer<String>();
// Create a Future using the Completer
Future<String> futureResult = completer.future;
// Simulate an asynchronous operation
simulateAsyncOperation(completer);
// Attach a callback to the Future
futureResult.then((result) {
print('Async operation completed: $result');
});
}
void simulateAsyncOperation(Completer<String> completer) {
// Simulate an asynchronous operation
Future.delayed(Duration(seconds: 2), () {
// Complete the Future with a result
completer.complete('Operation successful');
});
}
- An Isolate is a separate Dart process that runs concurrently with the main program.
- It is used for parallelizing computation-intensive tasks without affecting the main program's performance.
// Isolates with run function
Future<String> startDownloadUsingRunMethod() async {
final imageData = await Isolate.run(_readAndParseJsonWithoutIsolateLogic);
return imageData;
}
Future<String> _readAndParseJsonWithoutIsolateLogic() async {
await Future.delayed(const Duration(seconds: 2));
return 'this is downloaded data';
}
- Single Subscription Streams: Ordered sequences with one listener, crucial when event order matters.
import 'dart:async';
void main() {
// Create a StreamController of integers
var controller = StreamController<int>();
// Listen to the stream
var subscription = controller.stream.listen(
(data) => print('Data: $data'),
onError: (error) => print('Error: $error'),
onDone: () => print('Stream closed'),
cancelOnError: true, // Cancel the subscription on error
);
// Add data to the stream
controller.add(1);
controller.add(2);
controller.add(3);
// Simulate an error
controller.addError('Error message');
// Close the stream
controller.close();
// Try adding more data after closing (will not be received)
controller.add(4);
// Cancel the subscription manually (not necessary if using onDone)
// subscription.cancel();
}
- Broadcast Streams: Flexible streams allowing multiple listeners, with instant event access and the ability to rejoin after canceling.
import 'dart:async';
void main() {
// Create a BroadcastStreamController of integers
var controller = StreamController<int>.broadcast();
// Listen to the broadcast stream with multiple listeners
var subscription1 = controller.stream.listen(
(data) => print('Listener 1: $data'),
onError: (error) => print('Listener 1 Error: $error'),
onDone: () => print('Listener 1 Stream closed'),
cancelOnError: true,
);
var subscription2 = controller.stream.listen(
(data) => print('Listener 2: $data'),
onError: (error) => print('Listener 2 Error: $error'),
onDone: () => print('Listener 2 Stream closed'),
cancelOnError: true,
);
// Add data to the broadcast stream
controller.add(1);
controller.add(2);
controller.add(3);
// Simulate an error
controller.addError('Error message');
// Close the broadcast stream
controller.close();
// Try adding more data after closing (will not be received)
controller.add(4);
// Cancel the subscriptions manually (not necessary if using onDone)
subscription1.cancel();
subscription2.cancel();
}
void main() {
// Generate numbers using the generateMoreNumbers function
var numbers = generateMoreNumbers();
// Print each generated number
for (var number in numbers) {
print('Generated Number: $number');
}
}
// Generator function that yields three numbers: 1, 2, and 3
Iterable<int> generateNumbers() sync* {
yield 1;
yield 2;
yield 3;
}
// Generator function that includes numbers from generateNumbers and adds 4 and 5
Iterable<int> generateMoreNumbers() sync* {
// Yield numbers from generateNumbers
yield* generateNumbers();
// Add two more numbers: 4 and 5
yield 4;
yield 5;
}
Comments
Post a Comment