Dart Error Handling: Try-Catch and Asynchronous Operations
Error Handling and Asynchronous Programming
Today I learned two important topics: Error handling (Try-Catch) and asynchronous operations (Async-Await). Both are very critical topics in real applications.
Live Demo: Try-Catch and Async-Await
Learn error handling and asynchronous programming interactively:
Try-Catch - Catching Errors
Our programs don't always run flawlessly. Users can enter something wrong, internet connection can drop, files might not be found. Try-Catch is exactly for these situations.
The First Problem I Encountered
I'm asking the user for a number but the user enters a letter. The program crashes:
import 'dart:io';
void main() {
print("Enter a number:");
String input = stdin.readLineSync()!;
int number = int.parse(input); // ERROR if letters are entered!
print("Your number: $number");
}If the user writes "abc", the program explodes. Solution? Try-Catch!
Making It Safe with Try-Catch
import 'dart:io';
void main() {
print("Enter a number:");
try {
String input = stdin.readLineSync()!;
int number = int.parse(input);
print("Your number: $number");
} catch (e) {
print("Error! Please enter a valid number.");
print("Error detail: $e");
}
}Now no matter what the user writes, the program doesn't crash. If there's an error, the catch block runs.
Program That Keeps Asking for Number
I took this one step further. Keep asking until the user enters the correct number:
import 'dart:io';
void main() {
int number;
while (true) {
try {
print("Enter a number:");
number = int.parse(stdin.readLineSync()!);
break; // Exit loop if successful
} catch (e) {
print("Please enter a valid number!");
}
}
print("Congratulations! Your number: $number");
}This code continues until the user enters the correct number. Very useful!
Finally Block
Sometimes you want code to run whether there's an error or not:
try {
// Risky operation
int result = 10 ~/ 0; // Division by zero error
} catch (e) {
print("Error occurred: $e");
} finally {
print("This always runs");
}The finally block runs no matter what. Generally used for things like closing files, disconnecting connections.
Asynchronous Operations - Async and Await
The asynchronous programming topic challenged me a lot at first. But here's how I understood it: Some operations take time (fetching data from internet, reading files, etc.) and we don't want the program to freeze while waiting for these operations.
Synchronous vs Asynchronous
Synchronous (Normal):
Operation 1 start → Operation 1 finish → Operation 2 start → Operation 2 finishAsynchronous:
Operation 1 start → Operation 2 start → Operation 1 finish → Operation 2 finishSo you can do other things while waiting for something.
Future, Async, Await - Triple Concept
These three words are always used together:
- Future: An operation that will complete in the future
- async: Indicates the function is asynchronous
- await: Wait until the operation finishes
My First Async Example
Future<void> main() async {
print("Waiting for data...");
String data = await getDataFromDatabase();
print("Data received: $data");
}
Future<String> getDataFromDatabase() async {
// Wait 3 seconds (simulating database)
await Future.delayed(Duration(seconds: 3));
return "User data";
}This code works like this:
- Print "Waiting for data..."
- Call
getDataFromDatabase()function and wait (3 seconds) - When data comes, print "Data received: ..."
What Happens If We Don't Use Await?
Future<void> main() async {
print("Waiting for data...");
String data = getDataFromDatabase(); // ERROR!
print("Data received: $data");
}This won't work because getDataFromDatabase() doesn't return a value immediately, it returns a Future. You can't convert a Future to String without using await.
A More Realistic Example
Future<void> main() async {
print("1. Waiting for data...");
var data = await getDataFromDatabase();
print("2. Getting data: $data");
}
Future<String> getDataFromDatabase() async {
// Show loading status every second
for (var i = 1; i <= 5; i++) {
await Future.delayed(
Duration(seconds: 1),
() => print("Loading... %${i * 20}")
);
}
return "Database cluster";
}Output:
1. Waiting for data...
Loading... %20
Loading... %40
Loading... %60
Loading... %80
Loading... %100
2. Getting data: Database clusterUsing Try-Catch with Async
Errors can occur in asynchronous operations too (internet dropped, timeout, etc.):
Future<void> main() async {
try {
print("Fetching data from API...");
String data = await fetchDataFromAPI();
print("Data: $data");
} catch (e) {
print("Error occurred: $e");
print("Check your internet connection");
}
}
Future<String> fetchDataFromAPI() async {
await Future.delayed(Duration(seconds: 2));
// Error simulation
throw Exception("Could not connect to server!");
// return "API Data"; // This would normally work
}Usage in Flutter
We constantly use async in Flutter. Because:
- We fetch data from APIs
- We do database operations
- We read files
- We wait for user input
API Call Example in Flutter
class DataPage extends StatefulWidget {
@override
_DataPageState createState() => _DataPageState();
}
class _DataPageState extends State<DataPage> {
String data = "Loading...";
@override
void initState() {
super.initState();
loadData();
}
Future<void> loadData() async {
try {
// Make API call (example)
await Future.delayed(Duration(seconds: 2));
setState(() {
data = "Data loaded successfully!";
});
} catch (e) {
setState(() {
data = "Error: $e";
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(data),
),
);
}
}FutureBuilder Widget
There's a special widget in Flutter to show Futures:
FutureBuilder<String>(
future: getDataFromDatabase(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // Show loading
}
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}"); // Show error
}
return Text("Data: ${snapshot.data}"); // Show data
},
)This widget automatically manages loading, error and success states. Very useful!
Things to Watch Out for When Using Async-Await
Some important rules I learned:
- await is only used in async functions
// WRONG
void function() {
await something(); // ERROR!
}
// CORRECT
Future<void> function() async {
await something(); // OK
}- async function must return Future
Future<String> getData() async {
return "Data";
}- You can use multiple awaits
Future<void> getAllData() async {
String data1 = await getDataFromAPI1();
String data2 = await getDataFromAPI2();
String data3 = await getDataFromAPI3();
print("$data1, $data2, $data3");
}- Use Future.wait for parallel execution
Future<void> parallelData() async {
// All three start at the same time
var results = await Future.wait([
getDataFromAPI1(),
getDataFromAPI2(),
getDataFromAPI3(),
]);
print(results); // [data1, data2, data3]
}What Did I Learn Today?
- Try-Catch safely catches errors
- Finally block runs in every case
- Async-Await manages asynchronous operations
- Future is for operations that will complete in the future
- FutureBuilder is very useful in Flutter
Tomorrow I'll look at Flutter's UI structure (Widget tree, Row, Column, Stack). Now that I've learned the code side well, it's time for the interface!
Have questions?
Keep coding! 🚀