✦ Selamat Idul Fitri 1447 H πŸŒ™ Taqabbalallahu minna wa minkum. Mohon maaf lahir dan batin. ✦
ALFAkwt

Menangani Error dengan Elegan di Flutter: Jangan Biarkan Aplikasi Diam Saja

Β· 0 komentar Β· Β± 4 menit baca Β· πŸ‘ 90 dilihat

Salah satu hal yang paling membedakan aplikasi yang terasa profesional dari yang terasa "buatan sendiri" adalah cara ia menangani kondisi error. Aplikasi yang bagus tidak membiarkan pengguna bingung ketika ada yang tidak berjalan β€” ia memberitahu, dan kalau bisa, menawarkan jalan keluar.

Saya belajar ini dengan cara yang agak menyakitkan: dari review negatif pertama aplikasi saya di Play Store. Pengguna menulis: "aplikasi ngeblank, gak ada keterangan apa-apa." Waktu saya cek, ternyata ada error yang saya tangkap dengan try-catch tapi tidak saya tampilkan ke pengguna β€” hanya ditelan tanpa feedback apapun.

Sejak itu, error handling jadi salah satu hal yang saya pikirkan dengan serius sejak awal pengembangan.

Jangan Pernah Tangkap Error Diam-Diam

Ini yang paling sering saya lihat di kode Flutter pemula β€” dan kadang kode saya sendiri di masa lalu:

// SALAH β€” error ditangkap tapi tidak ada feedback ke pengguna
Future<void> ambilData() async {
try {
final data = await ApiService.getData();
setState(() => _data = data);
} catch (e) {
// Tidak ada apa-apa di sini
}
}

Dari perspektif pengguna: tombol ditekan, tidak terjadi apa-apa. Mereka tidak tahu apakah requestnya berhasil, gagal, atau masih loading. Pengalaman yang sangat buruk.

Yang lebih baik:

Future<void> ambilData() async {
setState(() => _isLoading = true);
try {
final data = await ApiService.getData();
setState(() {
_data = data;
_isLoading = false;
});
} on SocketException {
setState(() => _isLoading = false);
_tampilkanPesan('Tidak ada koneksi internet. Periksa jaringan Anda.');
} on TimeoutException {
setState(() => _isLoading = false);
_tampilkanPesan('Koneksi terlalu lambat. Coba lagi nanti.');
} catch (e) {
setState(() => _isLoading = false);
_tampilkanPesan('Terjadi kesalahan. Silakan coba lagi.');
debugPrint('Error: $e'); // log untuk developer
}
}

Buat Custom Exception yang Informatif

Daripada hanya catch Exception atau Object, mendefinisikan custom exception memungkinkan penanganan yang lebih spesifik dan pesan error yang lebih bermakna:

class ApiException implements Exception {
final int statusCode;
final String message;
const ApiException({required this.statusCode, required this.message});
@override
String toString() => 'ApiException($statusCode): $message';
// Pesan yang ramah untuk ditampilkan ke pengguna
String get pesanPengguna {
switch (statusCode) {
case 400: return 'Data yang dikirim tidak valid.';
case 401: return 'Sesi habis. Silakan login ulang.';
case 403: return 'Anda tidak punya akses ke fitur ini.';
case 404: return 'Data tidak ditemukan.';
case 500: return 'Server sedang bermasalah. Coba beberapa saat lagi.';
default: return 'Terjadi kesalahan (kode: $statusCode).';
}
}
}
// Di API service:
if (response.statusCode != 200) {
throw ApiException(
statusCode: response.statusCode,
message: jsonDecode(response.body)['error'] ?? 'Unknown error',
);
}
// Di widget:
} on ApiException catch (e) {
_tampilkanPesan(e.pesanPengguna);
}

Widget ErrorState yang Konsisten

Daripada membuat UI error yang berbeda-beda di setiap layar, saya buat widget ErrorState yang bisa dipakai di mana saja:

class ErrorState extends StatelessWidget {
final String pesan;
final String? labelTombol;
final VoidCallback? onRetry;
const ErrorState({
super.key,
required this.pesan,
this.labelTombol = 'Coba Lagi',
this.onRetry,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 48, color: Colors.red.shade300),
const SizedBox(height: 16),
Text(
pesan,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
if (onRetry != null) ...[
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh),
label: Text(labelTombol!),
),
],
],
),
),
);
}
}
// Penggunaan:
if (_hasError) {
return ErrorState(
pesan: _errorMessage,
onRetry: _ambilData,
);
}

Global Error Handler untuk Error yang Tidak Tertangkap

Meskipun sudah berusaha menangkap semua error, selalu ada kemungkinan error yang lolos. Flutter menyediakan hook untuk menangkap ini sebelum aplikasi crash:

void main() {
// Tangkap Flutter framework errors
FlutterError.onError = (details) {
FlutterError.presentError(details);
// Kirim ke error reporting service (Firebase Crashlytics, Sentry, dll)
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
};
// Tangkap Dart errors di luar Flutter framework
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const MyApp());
}

Firebase Crashlytics gratis untuk proyek personal dan sangat berguna β€” Anda bisa lihat stack trace dari crash yang dilaporkan pengguna nyata, bahkan crash yang tidak pernah Anda bisa reproduksi di mesin sendiri.

Penutup

Error handling yang baik adalah bentuk respek ke pengguna. Mereka tidak tahu β€” dan tidak perlu tahu β€” detail teknis di balik masalah. Yang mereka butuhkan adalah informasi yang cukup untuk tahu apa yang terjadi dan apa yang bisa mereka lakukan.

Investasi waktu untuk membangun error handling yang konsisten di awal proyek jauh lebih murah dari menambalnya satu per satu setelah ada review negatif.

Kalau ada pendekatan error handling Flutter yang Anda temukan efektif dan belum saya sebut di sini β€” share di komentar. Saya selalu cari cara yang lebih baik.


Komentar

Belum ada komentar. Jadilah yang pertama menulis.

Tulis Komentar

↑