Asynchronous Programming di Dart

September 10, 2025 (1mo ago)

Pemrograman asynchronous adalah konsep fundamental dalam Dart yang memungkinkan program melakukan operasi yang memakan waktu tanpa memblokir eksekusi kode lainnya. Konsep ini sangat penting dalam pengembangan aplikasi Flutter dan server-side Dart.

Asynchronous programming memungkinkan kita melakukan beberapa hal secara bersamaan. Sebagai contoh, saat aplikasi melakukan request API untuk mengambil data resep masakan, user tetap bisa berinteraksi dengan text field dan elemen UI lainnya. Program bisa berinteraksi dengan user sambil melakukan background tasks secara terpisah.


Mengapa Asynchronous Programming Penting?

Operasi asynchronous memungkinkan program menyelesaikan pekerjaan sambil menunggu operasi lain selesai. Beberapa operasi asynchronous yang umum:

  • Fetching data dari network/internet
  • Writing/Reading ke database
  • Reading data dari file
  • Timer operations dan delays
  • HTTP requests dan API calls
  • File I/O operations

Tanpa asynchronous programming, aplikasi akan "freeze" saat melakukan operasi yang memakan waktu. Dart menyediakan dua cara utama untuk menangani asynchronous programming:

  • Future - untuk satu event asynchronous
  • Stream - untuk sequence dari asynchronous events

Apa itu Future?

Future adalah objek yang merepresentasikan hasil dari operasi asynchronous. Future memiliki dua state:

1. Uncompleted (Belum Selesai)

Future masih menunggu operasi asynchronous selesai atau error.

2. Completed (Selesai)

Future sudah selesai dengan dua kemungkinan:

  • Completed with value: Operasi berhasil dan menghasilkan nilai
  • Completed with error: Operasi gagal dan menghasilkan error
// Contoh Future type
Future<String> fetchUserName() {
  // Simulasi operasi yang memakan waktu
  return Future.delayed(Duration(seconds: 2), () => 'John Doe');
}
 
Future<int> fetchUserAge() {
  return Future.delayed(Duration(seconds: 1), () => 25);
}
 
Future<void> processData() {
  // Future yang tidak mengembalikan nilai berguna
  return Future.delayed(Duration(seconds: 3), () => print('Processing completed'));
}

Async dan Await Keywords

async Keyword

  • Menandai function sebagai asynchronous
  • Function yang diberi async otomatis mengembalikan Future
  • Memungkinkan penggunaan await di dalam function

await Keyword

  • Digunakan untuk menunggu hasil dari Future
  • Hanya bisa digunakan di dalam async function
  • Membuat kode asynchronous terlihat seperti synchronous
// ❌ SALAH: Synchronous function
String createOrderMessage() {
  var order = fetchUserOrder(); // Ini akan return Future<String>, bukan String
  return 'Your order is: $order'; // Output: "Your order is: Instance of 'Future<String>'"
}
 
// ✅ BENAR: Asynchronous function
Future<String> createOrderMessage() async {
  var order = await fetchUserOrder(); // Menunggu Future selesai
  return 'Your order is: $order'; // Output: "Your order is: Large Latte"
}
 
Future<String> fetchUserOrder() {
  return Future.delayed(Duration(seconds: 2), () => 'Large Latte');
}

Execution Flow dengan Async/Await

Async function berjalan synchronous sampai bertemu keyword await pertama:

Future<void> printOrderMessage() async {
  print('Fetching user order...'); // Dieksekusi immediately
  print('Before await'); // Dieksekusi immediately
  
  var order = await fetchUserOrder(); // Eksekusi pause di sini
  
  print('After await'); // Dieksekusi setelah await selesai
  print('Your order is: $order');
}
 
Future<String> fetchUserOrder() {
  return Future.delayed(Duration(seconds: 2), () => 'Large Latte');
}
 
void main() async {
  await printOrderMessage();
}
 
/* Output:
Fetching user order...
Before await
(2 detik kemudian...)
After await
Your order is: Large Latte
*/

Menggunakan Multiple Await

Anda bisa menggunakan await beberapa kali dalam satu function:

Future<void> processUserData() async {
  print('Starting process...');
  
  var userName = await fetchUserName(); // Tunggu 2 detik
  print('Name: $userName');
  
  var userAge = await fetchUserAge(); // Tunggu 1 detik lagi
  print('Age: $userAge');
  
  var userEmail = await fetchUserEmail(); // Tunggu 1 detik lagi
  print('Email: $userEmail');
  
  print('All data processed!');
}
 
Future<String> fetchUserName() async {
  await Future.delayed(Duration(seconds: 2));
  return 'John Doe';
}
 
Future<int> fetchUserAge() async {
  await Future.delayed(Duration(seconds: 1));
  return 25;
}
 
Future<String> fetchUserEmail() async {
  await Future.delayed(Duration(seconds: 1));
  return 'john@email.com';
}

Error Handling dengan Try-Catch

Gunakan try-catch untuk menangani error dalam async function:

Future<void> fetchUserData() async {
  try {
    print('Fetching user data...');
    var userData = await fetchUserFromServer();
    print('User data: $userData');
  } catch (e) {
    print('Error occurred: $e');
  } finally {
    print('Cleanup completed');
  }
}
 
Future<String> fetchUserFromServer() async {
  await Future.delayed(Duration(seconds: 2));
  
  // Simulasi error
  throw Exception('Server connection failed');
}
 
void main() async {
  await fetchUserData();
}
 
/* Output:
Fetching user data...
Error occurred: Exception: Server connection failed
Cleanup completed
*/

Contoh Praktis: Login System

class LoginService {
  Future<String> authenticate(String username, String password) async {
    try {
      print('Authenticating user...');
      
      // Simulasi validasi credentials
      await Future.delayed(Duration(seconds: 2));
      
      if (username == 'admin' && password == '123456') {
        return 'Authentication successful';
      } else {
        throw Exception('Invalid credentials');
      }
    } catch (e) {
      throw Exception('Authentication failed: ${e.toString()}');
    }
  }
  
  Future<Map<String, dynamic>> getUserProfile(String userId) async {
    await Future.delayed(Duration(seconds: 1));
    
    return {
      'id': userId,
      'name': 'John Doe',
      'email': 'john@example.com',
      'role': 'admin'
    };
  }
}
 
Future<void> loginUser(String username, String password) async {
  final loginService = LoginService();
  
  try {
    // Step 1: Authenticate
    var authResult = await loginService.authenticate(username, password);
    print(authResult);
    
    // Step 2: Get user profile
    var userProfile = await loginService.getUserProfile('user123');
    print('Welcome, ${userProfile['name']}!');
    print('Role: ${userProfile['role']}');
    
  } catch (e) {
    print('Login failed: $e');
  }
}
 
void main() async {
  await loginUser('admin', '123456');
}

Future.wait() - Menjalankan Multiple Futures Bersamaan

Jika Anda memiliki beberapa operasi async yang tidak bergantung satu sama lain, gunakan Future.wait():

Future<void> fetchAllData() async {
  try {
    // Jalankan 3 operasi bersamaan (parallel)
    var results = await Future.wait([
      fetchUserName(),
      fetchUserAge(),
      fetchUserEmail(),
    ]);
    
    print('Name: ${results[0]}');
    print('Age: ${results[1]}');
    print('Email: ${results[2]}');
    
  } catch (e) {
    print('Error: $e');
  }
}
 
// Sequential (4 detik total)
Future<void> fetchDataSequential() async {
  var name = await fetchUserName(); // 2 detik
  var age = await fetchUserAge(); // 1 detik
  var email = await fetchUserEmail(); // 1 detik
  // Total: 4 detik
}
 
// Parallel (2 detik total - waktu operasi terlama)
Future<void> fetchDataParallel() async {
  var results = await Future.wait([
    fetchUserName(), // 2 detik
    fetchUserAge(), // 1 detik
    fetchUserEmail(), // 1 detik
  ]);
  // Total: 2 detik (semua jalan bersamaan)
}

Handling Timeout

Untuk mencegah operasi async berjalan terlalu lama:

Future<String> fetchDataWithTimeout() async {
  try {
    var result = await fetchUserData().timeout(Duration(seconds: 5));
    return result;
  } on TimeoutException {
    throw Exception('Operation timed out');
  } catch (e) {
    throw Exception('Error: $e');
  }
}
 
Future<String> fetchUserData() async {
  // Simulasi operasi lama
  await Future.delayed(Duration(seconds: 10));
  return 'User data';
}

Stream dan Async For Loop

Untuk data yang datang secara berkelanjutan, gunakan Stream:

Stream<int> numberStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; // Emit nilai satu per satu
  }
}
 
Future<void> listenToStream() async {
  print('Starting stream...');
  
  await for (int number in numberStream()) {
    print('Received: $number');
  }
  
  print('Stream completed');
}
 
/* Output:
Starting stream...
(1 detik)
Received: 1
(1 detik)
Received: 2
(1 detik)
Received: 3
(1 detik)
Received: 4
(1 detik)
Received: 5
Stream completed
*/

Kapan Menggunakan then vs async/await

Menggunakan then (Callback Style)

then bekerja dengan konsep callbacks - memanggil kembali (executing) blok kode di kemudian hari. Gunakan then ketika:

  • Anda ingin kode lanjutan tidak menunggu operasi asynchronous selesai
  • Melakukan fire-and-forget operations
  • Chains operasi yang kompleks
void main() {
  print('start fetching recipes');
  
  Future.delayed(Duration(seconds: 1), () {
    print('recipes fetched');
  }).then((_) {
    print('after fetching recipes');
  });
  
  String computation = 'a random computation';
  print(computation);
}
 
/* Output:
start fetching recipes
a random computation      <- Ini dieksekusi dulu (tidak menunggu)
recipes fetched
after fetching recipes
*/

Menggunakan async/await (Sequential Style)

async/await menyediakan syntax yang lebih mudah dibaca dan sequential. Gunakan ketika:

  • Anda ingin menunggu operasi selesai sebelum lanjut ke baris berikutnya
  • Code yang mudah dibaca dan mirip synchronous code
  • Error handling yang lebih sederhana dengan try-catch
void main() async {
  print('start fetching recipes');
  
  await Future.delayed(Duration(seconds: 1), () {
    print('recipes fetched');
  });
  
  print('after fetching recipes');
  
  String computation = 'a random computation';
  print(computation);
}
 
/* Output:
start fetching recipes
recipes fetched
after fetching recipes
a random computation      <- Ini dieksekusi setelah await selesai
*/

Kombinasi Both (Hybrid Approach)

Anda bisa menggunakan async/await di dalam then callback:

void main() {
  Future.delayed(Duration(seconds: 1), () {
    print('inside delayed 1');
  }).then((_) async {
    print('inside then 1');
    
    await Future.delayed(Duration(seconds: 1), () {
      print('inside delayed 2');
    });
    
    print('inside then 2');
  });
  
  print('after delayed');
}

Future Class Methods dan Constructors

Dart menyediakan berbagai constructor dan method untuk bekerja dengan Future:

Future Constructors

// 1. Future.value() - Membuat completed Future dengan value
Future<String> immediateResult = Future.value('Hello World');
 
// 2. Future.error() - Membuat completed Future dengan error
Future<String> errorResult = Future.error('Something went wrong');
 
// 3. Future.delayed() - Future yang complete setelah delay
Future<String> delayedResult = Future.delayed(
  Duration(seconds: 2), 
  () => 'Delayed result'
);
 
// 4. Future.sync() - Menjalankan function langsung secara synchronous
Future<int> syncResult = Future.sync(() => 42);
 
// 5. Future.microtask() - Menjalankan dengan scheduleMicrotask
Future<String> microtaskResult = Future.microtask(() => 'Microtask');

Static Methods

// Future.wait() - Menunggu multiple futures
Future<List<String>> waitMultiple() async {
  var results = await Future.wait([
    fetchData1(),
    fetchData2(),
    fetchData3(),
  ]);
  return results;
}
 
// Future.any() - Return first completed future
Future<String> getFirstResult() async {
  return await Future.any([
    fetchFromServer1(),
    fetchFromServer2(),
    fetchFromCache(),
  ]);
}
 
// Future.doWhile() - Loop dengan async condition
Future<void> processUntilDone() async {
  await Future.doWhile(() async {
    bool hasMoreWork = await checkForWork();
    if (hasMoreWork) {
      await processWork();
      return true; // Continue loop
    }
    return false; // Stop loop
  });
}
 
// Future.forEach() - Async iteration
Future<void> processItems() async {
  List<String> items = ['item1', 'item2', 'item3'];
  await Future.forEach(items, (String item) async {
    await processItem(item);
  });
}

Flutter Integration: FutureBuilder dan Networking

FutureBuilder Widget

FutureBuilder adalah widget Flutter yang memudahkan menampilkan hasil dari Future:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
 
class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;
  
  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum(); // Panggil di initState, bukan di build()
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fetch Data Example')),
      body: Center(
        child: FutureBuilder<Album>(
          future: futureAlbum,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Text(snapshot.data!.title);
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            }
            
            // Loading spinner while waiting
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

HTTP Requests dengan Future

import 'dart:convert';
import 'package:http/http.dart' as http;
 
class Album {
  final int userId;
  final int id;
  final String title;
  
  const Album({required this.userId, required this.id, required this.title});
  
  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}
 
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );
  
  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to load album');
  }
}

Mengapa fetchAlbum() Dipanggil di initState()?

Jangan di build():

@override
Widget build(BuildContext context) {
  // SALAH! Function dipanggil setiap rebuild
  var future = fetchAlbum(); 
  return FutureBuilder<Album>(
    future: future, // Future baru setiap rebuild = inefficient
    builder: (context, snapshot) => ...,
  );
}

Gunakan di initState():

late Future<Album> futureAlbum;
 
@override
void initState() {
  super.initState();
  futureAlbum = fetchAlbum(); // Dipanggil sekali saja
}
 
@override
Widget build(BuildContext context) {
  return FutureBuilder<Album>(
    future: futureAlbum, // Future yang sama di-reuse
    builder: (context, snapshot) => ...,
  );
}

Advanced Future Programming Patterns

Chaining Futures dengan then()

Future<String> processUserData() {
  return fetchUserId()
    .then((userId) => fetchUserProfile(userId))
    .then((profile) => processProfile(profile))
    .then((result) => formatResult(result))
    .catchError((error) => handleError(error));
}
 
// Equivalent dengan async/await:
Future<String> processUserDataAsync() async {
  try {
    var userId = await fetchUserId();
    var profile = await fetchUserProfile(userId);
    var result = await processProfile(profile);
    return formatResult(result);
  } catch (error) {
    return handleError(error);
  }
}

Error Handling yang Lebih Advanced

Future<bool> fileContains(String path, String needle) async {
  try {
    var haystack = await File(path).readAsString();
    return haystack.contains(needle);
  } on FileSystemException catch (exception, stack) {
    _myLog.logError(exception, stack);
    return false;
  } on FormatException catch (exception) {
    print('File format error: $exception');
    return false;
  } catch (exception) {
    // Handle any other exceptions
    print('Unexpected error: $exception');
    return false;
  }
}

Timeout Handling

Future<String> fetchWithTimeout() async {
  try {
    return await fetchData().timeout(Duration(seconds: 5));
  } on TimeoutException catch (_) {
    return 'Request timed out';
  }
}
 
// Atau dengan custom onTimeout callback:
Future<String> fetchWithTimeoutCallback() async {
  return await fetchData().timeout(
    Duration(seconds: 5),
    onTimeout: () => 'Default timeout response',
  );
}

Best Practices

1. Selalu gunakan async/await untuk Future

// ❌ Hindari
fetchData().then((result) {
  print(result);
}).catchError((error) {
  print('Error: $error');
});
 
// ✅ Gunakan
try {
  var result = await fetchData();
  print(result);
} catch (error) {
  print('Error: $error');
}

2. Jangan lupa await di main function

// ❌ SALAH
void main() {
  processData(); // Tidak menunggu selesai
  print('Main completed'); // Bisa print dulu sebelum processData selesai
}
 
// ✅ BENAR
void main() async {
  await processData(); // Tunggu sampai selesai
  print('Main completed'); // Print setelah processData selesai
}

3. Handle Errors dengan Proper Exception Types

Future<T> safeApiCall<T>(Future<T> Function() apiCall) async {
  try {
    return await apiCall();
  } on SocketException {
    throw NetworkException('No internet connection');
  } on HttpException {
    throw ApiException('API server error');
  } on FormatException {
    throw DataException('Invalid response format');
  } catch (e) {
    throw UnknownException('Unexpected error: $e');
  }
}

4. Gunakan Future.wait() untuk Parallel Operations

// ❌ Sequential (lambat - total 6 detik)
Future<void> fetchAllDataSequential() async {
  var user = await fetchUser();        // 2 detik
  var posts = await fetchPosts();      // 2 detik  
  var comments = await fetchComments(); // 2 detik
}
 
// ✅ Parallel (cepat - total 2 detik)
Future<void> fetchAllDataParallel() async {
  var results = await Future.wait([
    fetchUser(),        // Semua berjalan bersamaan
    fetchPosts(),       // 2 detik saja total
    fetchComments(),
  ]);
}

5. Avoid Memory Leaks dengan Proper Cleanup

class DataService {
  Timer? _periodicTimer;
  late StreamSubscription _subscription;
  
  void startPeriodicFetch() {
    _periodicTimer = Timer.periodic(Duration(seconds: 30), (_) {
      fetchDataPeriodically();
    });
  }
  
  void dispose() {
    _periodicTimer?.cancel(); // Cleanup timer
    _subscription.cancel();   // Cleanup stream subscription
  }
}
  print('Main completed'); // Print setelah processData selesai
}

3. Use Future.wait() untuk operasi parallel

// ❌ Sequential (lambat)
var result1 = await operation1();
var result2 = await operation2();
var result3 = await operation3();
 
// ✅ Parallel (cepat)
var results = await Future.wait([
  operation1(),
  operation2(),
  operation3(),
]);

Latihan Soal

Soal 1: Implementasi Async Function

// Lengkapi function berikut:
Future<String> reportUserRole() async {
  // TODO: Panggil fetchRole() dan return "User role: <role>"
  // Contoh output: "User role: admin"
}
 
Future<String> fetchRole() async {
  await Future.delayed(Duration(seconds: 1));
  return 'admin';
}

Jawaban:

Future<String> reportUserRole() async {
  var role = await fetchRole();
  return 'User role: $role';
}

Soal 2: Error Handling

// Implementasi function yang handle error:
Future<String> changeUsername() async {
  // TODO: Panggil fetchNewUsername()
  // Jika berhasil, return hasilnya
  // Jika error, return string error message
}
 
Future<String> fetchNewUsername() async {
  await Future.delayed(Duration(seconds: 1));
  throw Exception('Username not available');
}

Jawaban:

Future<String> changeUsername() async {
  try {
    return await fetchNewUsername();
  } catch (e) {
    return e.toString();
  }
}

Soal 3: Multiple Async Operations

// Implementasi function yang menggabungkan hasil dari 2 async function:
Future<String> getUserInfo() async {
  // TODO: Panggil fetchName() dan fetchAge()
  // Return format: "Name: <name>, Age: <age>"
}
 
Future<String> fetchName() async {
  await Future.delayed(Duration(seconds: 1));
  return 'John';
}
 
Future<int> fetchAge() async {
  await Future.delayed(Duration(seconds: 1));
  return 25;
}

Jawaban:

Future<String> getUserInfo() async {
  var name = await fetchName();
  var age = await fetchAge();
  return 'Name: $name, Age: $age';
}
 
// Atau menggunakan Future.wait (lebih efisien):
Future<String> getUserInfo() async {
  var results = await Future.wait([
    fetchName(),
    fetchAge(),
  ]);
  return 'Name: ${results[0]}, Age: ${results[1]}';
}

Flash Cards

Card 1: Future States

Q: Apa saja state dari Future?
A:

  • Uncompleted: Future masih dalam proses
  • Completed with value: Future selesai dengan hasil sukses
  • Completed with error: Future selesai dengan error

Card 2: async vs await

Q: Apa perbedaan async dan await?
A:

  • async: Keyword yang menandai function bisa melakukan operasi asynchronous
  • await: Keyword untuk menunggu Future selesai (hanya bisa digunakan dalam async function)

Card 3: Future.wait() vs Sequential await

Q: Kapan menggunakan Future.wait()?
A: Gunakan Future.wait() ketika ingin menjalankan multiple futures secara parallel untuk menghemat waktu. Sequential await menjalankan satu per satu.

Card 4: then() vs async/await

Q: Kapan menggunakan then() dan kapan async/await?
A:

  • then(): Ketika tidak ingin menunggu operasi selesai (non-blocking)
  • async/await: Ketika ingin menunggu operasi selesai sebelum lanjut (blocking, lebih mudah dibaca)

Card 5: Error Handling

Q: Bagaimana handle error di async function?
A: Gunakan try-catch block seperti synchronous code:

try {
  var result = await riskyOperation();
} catch (e) {
  print('Error: $e');
}

Card 6: FutureBuilder

Q: Apa kegunaan FutureBuilder di Flutter?
A: Widget yang membantu menampilkan UI berdasarkan state Future (loading, success, error) tanpa perlu setState manual.

Card 7: Stream vs Future

Q: Apa perbedaan Stream dan Future?
A:

  • Future: Single asynchronous operation (1 result)
  • Stream: Multiple asynchronous events over time (0, 1, or many results)

Real-World Examples untuk Latihan

Example 1: Authentication Flow

class AuthService {
  Future<User?> loginUser(String email, String password) async {
    try {
      // 1. Validate input
      if (email.isEmpty || password.isEmpty) {
        throw ValidationException('Email and password required');
      }
      
      // 2. Send login request
      var response = await http.post(
        Uri.parse('$baseUrl/login'),
        body: {'email': email, 'password': password},
      );
      
      // 3. Handle response
      if (response.statusCode == 200) {
        var data = jsonDecode(response.body);
        return User.fromJson(data);
      } else if (response.statusCode == 401) {
        throw AuthException('Invalid credentials');
      } else {
        throw ServerException('Server error occurred');
      }
    } catch (e) {
      rethrow; // Propagate error to caller
    }
  }
}

Example 2: Data Synchronization

class DataSyncService {
  Future<void> syncAllData() async {
    try {
      // Sync multiple data sources in parallel
      await Future.wait([
        syncUserProfile(),
        syncUserPosts(),
        syncUserSettings(),
        syncNotifications(),
      ]);
      
      print('All data synced successfully');
    } catch (e) {
      print('Sync failed: $e');
      // Implement retry logic here
    }
  }
  
  Future<void> syncWithRetry() async {
    int maxRetries = 3;
    int attempts = 0;
    
    while (attempts < maxRetries) {
      try {
        await syncAllData();
        return; // Success, exit loop
      } catch (e) {
        attempts++;
        if (attempts >= maxRetries) {
          throw Exception('Sync failed after $maxRetries attempts');
        }
        
        // Wait before retry (exponential backoff)
        await Future.delayed(Duration(seconds: pow(2, attempts) as int));
      }
    }
  }
}

Example 3: Progressive Loading

class ProgressiveLoader extends StatefulWidget {
  @override
  _ProgressiveLoaderState createState() => _ProgressiveLoaderState();
}
 
class _ProgressiveLoaderState extends State<ProgressiveLoader> {
  String status = 'Initializing...';
  double progress = 0.0;
  
  @override
  void initState() {
    super.initState();
    loadDataProgressively();
  }
  
  Future<void> loadDataProgressively() async {
    try {
      setState(() {
        status = 'Loading user data...';
        progress = 0.2;
      });
      await loadUserData();
      
      setState(() {
        status = 'Loading posts...';
        progress = 0.5;
      });
      await loadPosts();
      
      setState(() {
        status = 'Loading images...';
        progress = 0.8;
      });
      await loadImages();
      
      setState(() {
        status = 'Completed!';
        progress = 1.0;
      });
    } catch (e) {
      setState(() {
        status = 'Error: $e';
        progress = 0.0;
      });
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(status),
        LinearProgressIndicator(value: progress),
      ],
    );
  }
}

Soal Tambahan dengan Pembahasan

Soal 4: Complex Error Handling

Future<String> complexApiCall() async {
  // TODO: Implement error handling untuk scenario:
  // 1. NetworkException -> return "No internet connection"
  // 2. TimeoutException -> return "Request timed out"
  // 3. FormatException -> return "Invalid data format"
  // 4. Any other error -> return "Unknown error occurred"
}
 
Future<String> riskyApiCall() async {
  await Future.delayed(Duration(seconds: 2));
  throw FormatException('Invalid JSON format');
}

Jawaban & Pembahasan:

Future<String> complexApiCall() async {
  try {
    return await riskyApiCall().timeout(Duration(seconds: 5));
  } on NetworkException {
    return "No internet connection";
  } on TimeoutException {
    return "Request timed out";
  } on FormatException {
    return "Invalid data format";
  } catch (e) {
    return "Unknown error occurred";
  }
}

Pembahasan: Order dari catch blocks penting! Specific exceptions dulu, baru general catch di akhir.

Soal 5: Stream Processing

Stream<int> numberStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}
 
Future<int> calculateSum() async {
  // TODO: Hitung total sum dari numberStream()
  // Expected result: 1 + 2 + 3 + 4 + 5 = 15
}

Jawaban & Pembahasan:

Future<int> calculateSum() async {
  int sum = 0;
  await for (int number in numberStream()) {
    sum += number;
  }
  return sum;
}
 
// Alternative menggunakan Stream methods:
Future<int> calculateSumAlt() async {
  return await numberStream().reduce((a, b) => a + b);
}

Pembahasan: await for digunakan untuk iterate over Stream elements. Stream.reduce() lebih concise untuk operasi matematis.


Tips & Common Mistakes

❌ Common Mistakes:

  1. Lupa await di main()

    void main() {
      processData(); // SALAH - function async tidak ditunggu
    }
  2. Mencampur then() dengan async/await

    Future<void> badExample() async {
      fetchData().then((result) => print(result)); // Inconsistent style
      await processResult(); // Mixed approaches
    }
  3. Tidak handle errors

    Future<void> riskyCode() async {
      var data = await fetchRiskyData(); // Bisa throw exception
      // Tidak ada error handling!
    }
  4. Memanggil async function di build()

    Widget build(BuildContext context) {
      var future = fetchData(); // SALAH - dipanggil setiap rebuild
      return FutureBuilder(future: future, ...);
    }

✅ Best Practices:

  1. Consistent async/await style
  2. Always handle errors dengan try-catch
  3. Use Future.wait() untuk parallel operations
  4. Call async functions di initState(), bukan build()
  5. Implement timeout untuk network operations

Kesimpulan

Key Points :

  1. Future = objek yang merepresentasikan hasil operasi asynchronous
  2. async = keyword untuk menandai function asynchronous
  3. await = keyword untuk menunggu Future selesai (hanya di dalam async function)
  4. Error handling menggunakan try-catch seperti synchronous code
  5. Future.wait() untuk menjalankan multiple futures secara parallel
  6. Stream untuk data yang datang berkelanjutan dengan await for
  7. FutureBuilder untuk menampilkan async data di Flutter UI
  8. then() untuk non-blocking operations, async/await untuk sequential code

Remember: Async function berjalan synchronous sampai bertemu await pertama, lalu pause sampai Future selesai!

Pro Tip: Practice dengan real-world examples seperti HTTP requests, file operations, dan database queries untuk memahami konsep dengan baik.