Belajar Mobile Developer - Networking & API Integration
Episode 10 of 28

Belajar Mobile Developer - Networking & API Integration

Menghubungkan aplikasi mobile ke backend: REST & GraphQL client di tiap stack, serialization, error handling & retry dengan exponential backoff, serta caching — sambil mengintegrasikan API workout Fitku secara nyata

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Aplikasi mobile yang berguna hampir selalu butuh data dari server: katalog workout, profil pengguna, riwayat langkah. Di episode 10 ini kita belajar networking & API integration — menghubungkan Fitku ke backend lewat REST/GraphQL dengan benar, menangani error dengan anggun, melakukan retry yang cerdas, dan caching agar aplikasi terasa cepat.

Mengapa topik ini sering jadi sumber bug? Karena di jaringan seluler, koneksi tidak bisa diandalkan: sinyal lemah, latency tinggi, timeout, dan server yang sedang turun. Aplikasi yang hanya "memanggil API" tanpa menangani kegagalan akan crash atau menampilkan layar kosong — reputasi langsung hancur.

REST vs GraphQL

REST adalah standar paling umum: resource sebagai URL + method HTTP. GraphQL memakai satu endpoint dengan query yang meminta field spesifik.

REST: resource-based
GET  /api/v1/workouts          → daftar workout
GET  /api/v1/workouts/42       → detail workout
POST /api/v1/workouts          → buat workout baru
PUT  /api/v1/workouts/42       → update workout
DELETE /api/v1/workouts/42     → hapus workout
GraphQL: satu endpoint, query field
query TodaySummary {
  todaySteps { total }
  workouts(date: TODAY) { id name durationMinutes }
}

Pilih REST untuk kesederhanaan dan kompatibilitas; GraphQL jika data bertingkat dan over-fetching jadi masalah nyata. Untuk Fitku, REST cukup — kita bisa tambah GraphQL nanti.

Networking di Tiap Stack

Android: Retrofit + kotlinx.serialization

KotlinRetrofit API Fitku
interface FitkuApi {
    @GET("workouts")
    suspend fun fetchWorkouts(@Query("userId") userId: String): List<WorkoutDto>
 
    @POST("workouts")
    suspend fun uploadWorkout(@Body workout: WorkoutDto): WorkoutDto
}
 
// Setup
val api = Retrofit.Builder()
    .baseUrl("https://api.fitku.dev/")
    .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
    .client(OkHttpClient.Builder()
        .addInterceptor(AuthInterceptor(token))
        .build())
    .build()
    .create(FitkuApi::class.java)

Flutter: dio atau http

dio di Flutter
import 'package:dio/dio.dart';
 
final dio = Dio(BaseOptions(baseUrl: 'https://api.fitku.dev/'))
  ..interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) {
      options.headers['Authorization'] = 'Bearer $token';
      handler.next(options);
    },
  ));
 
Future<List<Workout>> fetchWorkouts(String userId) async {
  final res = await dio.get<List<dynamic>>('/workouts',
      queryParameters: {'userId': userId});
  return res.data!
      .map((e) => Workout.fromJson(e as Map<String, dynamic>))
      .toList();
}

React Native: fetch + TanStack Query

fetch di React Native
const API = "https://api.fitku.dev";
 
async function fetchWorkouts(userId: string): Promise<Workout[]> {
  const res = await fetch(`${API}/workouts?userId=${userId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new ApiError(res.status);
  return res.json();
}

Untuk RN, kombinasikan dengan TanStack Query: ia memberi caching, retry, dan stale-while-revalidate secara gratis.

Note

Serialization wajib dipisahkan dari model domain: WorkoutDto (JSON) vs Workout (domain). Di Android pakai kotlinx.serialization, di Flutter json_serializable, di RN cukup zod untuk validasi runtime. Mencegah error akibat field yang berubah dari server.

Error Handling & Retry

Error API perlu dikategorikan:

  • 4xx — kesalahan klien (validasi, auth) → tampilkan pesan jelas, jangan retry otomatis.
  • 5xx / timeout / offline — kesalahan sementara → retry dengan exponential backoff.
KotlinRetry dengan exponential backoff (KMP)
suspend fun <T> retryWithBackoff(
    maxAttempts: Int = 3,
    block: suspend () -> T,
): T {
    var attempt = 0
    while (true) {
        try {
            return block()
        } catch (e: RetryableException) {
            attempt++
            if (attempt >= maxAttempts) throw e
            delay(200L * (1L shl attempt)) // 200ms, 400ms, 800ms
        }
    }
}

Di TanStack Query, retry dan retryDelay dikonfigurasi langsung; di Flutter, Dio punya dio.httpClientAdapter dan kita bisa memakai retry_dio package.

Timeout

Selalu set timeout — tanpa timeout, request yang menggantung membuat UI "beku" terasa:

Timeout yang direkomendasikan
connectTimeout: 10s    → koneksi ke server
receiveTimeout: 15s    → menerima respons (besar bisa lebih lama)

Caching

Caching adalah kunci UX mobile: data yang pernah dimuat tidak perlu dimuat ulang. Strategi:

  1. In-memory cache (TanStack Query, Riverpod) — cepat, hilang saat proses mati.
  2. Disk cache (Room/DataStore, shared_preferences) — bertahan antar sesi.
  3. HTTP cache headersCache-Control/ETag dari server untuk respons yang immutable.
Strategi cache Fitku
First open   → muat dari network → simpan ke cache
Second open  → tampilkan cache dulu (instan) → refresh di background
Offline      → tampilkan cache (episode 23)

Tip

Pola stale-while-revalidate adalah standar UX modern: tampilkan data lama seketika, lalu update di latar belakang. TanStack Query, Riverpod, dan banyak library 2026 sudah menerapkannya. Aplikasi terasa instan tanpa menunggu network.

Praktik: Integrasi API Fitku

Mari lengkapi repository dari episode 8 dengan error handling nyata:

KotlinRepository dengan error handling
class WorkoutRepositoryImpl(
    private val api: FitkuApi,
    private val localDb: WorkoutDao,
) : WorkoutRepository {
    override suspend fun fetchWorkouts(userId: String): List<Workout> {
        return try {
            retryWithBackoff { api.fetchWorkouts(userId) }
                .map(WorkoutDto::toDomain)
                .also { localDb.insertAll(it) }
        } catch (e: RetryableException) {
            localDb.fetchCached()     // fallback ke cache saat offline/server down
                ?: throw ApiUnavailable("Server sedang bermasalah, coba lagi nanti")
        } catch (e: ApiError) {
            throw UserFacingError(e.message ?: "Terjadi kesalahan")
        }
    }
}

ViewModel menangkap error, memetakannya ke UiState.error, dan UI menampilkan pesan + tombol coba lagi.

Common Pitfalls Networking

  • Tidak ada timeout: request menggantung selamanya → UX buruk.
  • Retry tanpa backoff: retry serentak membebani server saat sudah down.
  • Token expired tidak di-refresh: satu interceptor auth untuk refresh otomatis.
  • Menyimpan token di tempat salah: security penuh dibahas episode 13.
  • Tidak memakai cache: setiap buka layar request ulang → boros data & lambat.

Warning

Jangan pernah menampilkan error mentah dari server ke pengguna ("500 Internal Server Error"). Map dulu ke pesan yang manusiawi, dan jangan log data sensitif (token, PII) ke konsol produksi.

Penutup

Pada episode 10 ini, kalian telah menghubungkan Fitku ke dunia luar:

  • REST vs GraphQL dan kapan memilih masing-masing.
  • Client networking di Android (Retrofit), Flutter (dio), dan RN (fetch + TanStack Query).
  • Error handling dengan kategori 4xx/5xx dan retry exponential backoff.
  • Caching multi-layer dengan pola stale-while-revalidate.

Di episode 11 selanjutnya kita simpan data secara permanen: Local Storage & Database — SQLite/Room, Realm, dan preferences, dengan strategi offline-first untuk Fitku. Sampai jumpa!

Belajar Mobile Developer - Networking & API Integration | Belajar Mobile Developer