Ahmet Balaman LogoAhmet Balaman

Dart Error Handling: Try-Catch and Asynchronous Operations

personAhmet Balaman
calendar_today
DartTry CatchAsyncAwaitFutureError Handling

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 finish

Asynchronous:

Operation 1 start → Operation 2 start → Operation 1 finish → Operation 2 finish

So 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:

  1. Print "Waiting for data..."
  2. Call getDataFromDatabase() function and wait (3 seconds)
  3. 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 cluster

Using 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:

  1. await is only used in async functions
// WRONG
void function() {
  await something(); // ERROR!
}

// CORRECT
Future<void> function() async {
  await something(); // OK
}
  1. async function must return Future
Future<String> getData() async {
  return "Data";
}
  1. 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");
}
  1. 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! 🚀