Flutter & REST API dengan Dio dan Interceptors
Pengembangan aplikasi modern seringkali melibatkan interaksi dengan layanan backend melalui RESTful API. Flutter, sebagai framework UI yang populer, memerlukan pustaka HTTP yang efisien dan fleksibel untuk menangani komunikasi ini. Dio menonjol sebagai pilihan utama di ekosistem Flutter, menawarkan fitur-fitur canggih seperti interceptor yang sangat berguna untuk mengelola permintaan dan respons.
Pendahuluan: Integrasi REST API di Flutter
RESTful API adalah arsitektur standar untuk membangun layanan web, memungkinkan komunikasi antara klien (aplikasi Flutter) dan server menggunakan metode HTTP standar (GET, POST, PUT, DELETE). Mengelola permintaan, respons, otentikasi, dan penanganan kesalahan secara konsisten bisa menjadi tantangan. Di sinilah pustaka HTTP yang kuat seperti Dio, bersama dengan kemampuan interceptor-nya, memainkan peran krusial.
Mengapa Memilih Dio?
Dio adalah HTTP client yang kuat untuk Dart, mendukung RequestOptions, Interceptors, FormData, Request Cancellation, File downloading, Timeout, dan banyak lagi. Beberapa alasan utama memilih Dio:
- Interceptors: Memungkinkan Anda untuk mencegat dan memodifikasi permintaan atau respons sebelum diproses atau dikirim.
- API Fleksibel: Mudah digunakan untuk berbagai jenis permintaan HTTP.
- Penanganan Kesalahan Komprehensif: Memudahkan penanganan berbagai jenis kesalahan jaringan dan server.
- Pembatalan Permintaan: Kemampuan untuk membatalkan permintaan yang sedang berlangsung.
- Dukungan FormData: Unggah file dengan mudah.
Persiapan: Menambahkan Dio ke Proyek Flutter
Pertama, tambahkan Dio sebagai dependensi di file pubspec.yaml Anda:
dependencies:
flutter:
sdk: flutter
dio: ^5.x.x # Gunakan versi terbaru yang stabil
Kemudian jalankan flutter pub get di terminal Anda.
Dasar Penggunaan Dio
Berikut adalah contoh dasar inisialisasi dan penggunaan Dio untuk melakukan permintaan GET dan POST.
Inisialisasi Dio
import 'package:dio/dio.dart';
final dio = Dio();
// Atau dengan BaseOptions
final dioWithBaseOptions = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
Permintaan GET
Future fetchData() async {
try {
final response = await dioWithBaseOptions.get('/users');
if (response.statusCode == 200) {
print('Data: ${response.data}');
} else {
print('Gagal mengambil data: ${response.statusCode}');
}
} on DioException catch (e) {
print('Error Dio: ${e.message}');
if (e.response != null) {
print('Response Error: ${e.response?.data}');
}
} catch (e) {
print('Error umum: $e');
}
}
Permintaan POST
Future postData(Map data) async {
try {
final response = await dioWithBaseOptions.post('/users', data: data);
if (response.statusCode == 201) {
print('Data berhasil dikirim: ${response.data}');
} else {
print('Gagal mengirim data: ${response.statusCode}');
}
} on DioException catch (e) {
print('Error Dio: ${e.message}');
if (e.response != null) {
print('Response Error: ${e.response?.data}');
}
} catch (e) {
print('Error umum: $e');
}
}
Memahami Interceptors
Interceptor adalah mekanisme yang memungkinkan Anda untuk menyadap (intercept) dan memodifikasi permintaan atau respons HTTP sebelum mereka mencapai tujuan akhir atau sebelum dikembalikan ke pemanggil. Ini sangat powerful untuk tugas-tugas cross-cutting seperti:
- Logging: Mencatat detail permintaan dan respons untuk debugging.
- Otentikasi: Menambahkan token otentikasi ke header permintaan secara otomatis.
- Penanganan Kesalahan Global: Mengelola error secara terpusat, seperti menampilkan pesan error generik atau me-refresh token.
- Cache: Mengimplementasikan strategi caching.
- Retry Mechanism: Otomatis mencoba ulang permintaan yang gagal.
Menerapkan Interceptors Kustom
Anda dapat menambahkan interceptor ke instans Dio menggunakan metode interceptors.add(). Ada tiga jenis interceptor utama:
RequestInterceptorResponseInterceptorErrorInterceptor
Atau Anda bisa menggunakan kelas Interceptor yang mengimplementasikan ketiganya.
Contoh 1: Logging Interceptor
Interceptor ini mencatat detail permintaan dan respons ke konsol.
class LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('REQUEST[${options.method}] => PATH: ${options.path}');
print('HEADERS: ${options.headers}');
print('DATA: ${options.data}');
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
print('DATA: ${response.data}');
super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
print('MESSAGE: ${err.message}');
if (err.response != null) {
print('RESPONSE DATA: ${err.response?.data}');
}
super.onError(err, handler);
}
}
// Menambahkan interceptor ke Dio
final dioWithInterceptors = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
),
);
dioWithInterceptors.interceptors.add(LoggingInterceptor());
Contoh 2: Authentication Interceptor
Interceptor ini menambahkan token otentikasi ke setiap permintaan.
class AuthInterceptor extends Interceptor {
final String? _authToken;
AuthInterceptor(this._authToken);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (_authToken != null) {
options.headers['Authorization'] = 'Bearer $_authToken';
}
super.onRequest(options, handler);
}
}
// Contoh penggunaan dengan token dummy
String? userToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Ambil dari SharedPreferences atau provider
dioWithInterceptors.interceptors.add(AuthInterceptor(userToken));
Contoh 3: Refresh Token Interceptor (Konseptual)
Untuk skenario token otentikasi yang kedaluwarsa, Anda bisa membuat interceptor yang mendeteksi status 401, mencoba me-refresh token, lalu mengulang permintaan asli.
class RefreshTokenInterceptor extends Interceptor {
final Dio _dio;
final Function() _refreshTokenCallback; // Callback untuk memanggil logika refresh token
RefreshTokenInterceptor(this._dio, this._refreshTokenCallback);
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
// Coba refresh token
try {
await _refreshTokenCallback(); // Memanggil fungsi refresh token
// Setelah token di-refresh, coba lagi permintaan asli dengan token baru
final RequestOptions requestOptions = err.requestOptions;
// Asumsi token baru sudah disimpan dan AuthInterceptor akan mengambilnya
final Response response = await _dio.fetch(requestOptions);
return handler.resolve(response);
} catch (e) {
// Gagal refresh token, lanjutkan error asli
super.onError(err, handler);
}
} else {
super.onError(err, handler);
}
}
}
// Integrasi dengan Dio
// final dio = Dio(...);
// Function() refreshTokenFunction = () async { /* logic untuk refresh token */ };
// dio.interceptors.add(AuthInterceptor(sharedPreferences.getString('accessToken'))); // Harus bisa diupdate
// dio.interceptors.add(RefreshTokenInterceptor(dio, refreshTokenFunction));
Struktur Proyek untuk API Service
Untuk menjaga kode tetap bersih dan mudah dikelola, disarankan untuk mengabstraksi logika API ke dalam kelas-kelas service terpisah.
// lib/services/api_service.dart
import 'package:dio/dio.dart';
import '../interceptors/logging_interceptor.dart';
import '../interceptors/auth_interceptor.dart';
// ... import interceptor lainnya
class ApiService {
final Dio _dio;
ApiService() : _dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
) {
_dio.interceptors.add(LoggingInterceptor());
// _dio.interceptors.add(AuthInterceptor(null)); // Token bisa diupdate kemudian
// _dio.interceptors.add(RefreshTokenInterceptor(_dio, () => _refreshToken()));
}
// Metode untuk memperbarui token otentikasi secara dinamis
void updateAuthToken(String? token) {
// Cari AuthInterceptor dan perbarui tokennya, atau buat ulang Dio jika perlu
// Ini mungkin memerlukan pendekatan yang lebih canggih, misalnya menggunakan Provider/Bloc
// Atau memastikan AuthInterceptor mengambil token dari sumber yang dapat diupdate (misal SharedPrefs)
}
Future getUsers() async {
return await _dio.get('/users');
}
Future createUser(Map userData) async {
return await _dio.post('/users', data: userData);
}
// Future _refreshToken() async {
// // Logika untuk memanggil API refresh token
// // Simpan token baru dan perbarui AuthInterceptor
// }
}
// Penggunaan di UI atau Business Logic
// final apiService = ApiService();
// try {
// final response = await apiService.getUsers();
// print(response.data);
// } catch (e) {
// print(e);
// }
Kesimpulan
Dio adalah pilihan yang sangat baik untuk mengelola permintaan REST API di aplikasi Flutter berkat fitur-fitur canggihnya, terutama Interceptors. Dengan Interceptors, Anda dapat dengan mudah mengimplementasikan logika global untuk logging, otentikasi, penanganan kesalahan, dan banyak lagi, menghasilkan kode yang lebih bersih, mudah dipelihara, dan skalabel. Menggabungkan Dio dengan struktur proyek yang terorganisir akan meningkatkan efisiensi pengembangan secara signifikan.