← Back to Tutorials Chapter 12

Networking & APIs

HTTP Clients

Retrofit (Recommended)

// build.gradle.kts
// implementation("com.squareup.retrofit2:retrofit:2.9.0")
// implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")

data class Post(
    val userId: Int,
    val id: Int,
    val title: String,
    val body: String
)

interface ApiService {
    @GET("posts")
    suspend fun getPosts(): List<Post>

    @GET("posts/{id}")
    suspend fun getPost(@Path("id") id: Int): Post

    @POST("posts")
    suspend fun createPost(@Body post: Post): Post

    @PUT("posts/{id}")
    suspend fun updatePost(@Path("id") id: Int, @Body post: Post): Post

    @DELETE("posts/{id}")
    suspend fun deletePost(@Path("id") id: Int)
}

// Retrofit instance
val logging = HttpLoggingInterceptor().apply {
    level = HttpLoggingInterceptor.Level.BODY
}

val client = OkHttpClient.Builder()
    .addInterceptor(logging)
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl("https://jsonplaceholder.typicode.com/")
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val api = retrofit.create(ApiService::class.java)

// Usage in ViewModel
viewModelScope.launch {
    try {
        val posts = api.getPosts()
        _posts.value = posts
    } catch (e: Exception) {
        _error.value = "Network error: ${e.message}"
    }
}

OkHttp (Lower Level)

val client = OkHttpClient()

val request = Request.Builder()
    .url("https://api.example.com/users")
    .get()
    .addHeader("Authorization", "Bearer $token")
    .build()

viewModelScope.launch(Dispatchers.IO) {
    val response = client.newCall(request).execute()
    if (response.isSuccessful) {
        val body = response.body?.string()
        // Parse JSON
    }
}

WebSocket

val client = OkHttpClient()
val ws = client.newWebSocket(
    Request.Builder().url("wss://echo.websocket.org").build(),
    object : WebSocketListener() {
        override fun onOpen(ws: WebSocket, response: Response) {
            ws.send("Hello from Android!")
        }

        override fun onMessage(ws: WebSocket, text: String) {
            println("Received: $text")
        }

        override fun onFailure(ws: WebSocket, t: Throwable, response: Response?) {
            println("Error: ${t.message}")
        }
    }
)

Image Loading

// build.gradle.kts
// implementation("com.github.bumptech.glide:glide:4.16.0")

// Load image into ImageView
Glide.with(this)
    .load("https://example.com/image.jpg")
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .circleCrop()
    .into(imageView)

// Coil (Kotlin coroutines-based alternative)
// implementation("io.coil-kt:coil:2.5.0")

imageView.load("https://example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.placeholder)
    error(R.drawable.error)
}

Firebase Realtime Database

// build.gradle.kts
// implementation("com.google.firebase:firebase-database")

val database = FirebaseDatabase.getInstance()
val usersRef = database.getReference("users")

// Write
usersRef.child("alice").setValue(User("Alice", 30))

// Read (one-time)
usersRef.child("alice").get().addOnSuccessListener { snapshot ->
    val user = snapshot.getValue(User::class.java)
}

// Read (realtime)
usersRef.child("alice").addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val user = snapshot.getValue(User::class.java)
        // Update UI
    }

    override fun onCancelled(error: DatabaseError) {
        println("Database error: ${error.message}")
    }
})

Connectivity Monitoring

val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

// Check current state
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)
val isOnline = capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true

// Observe changes
val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        println("Network available")
    }
    override fun onLost(network: Network) {
        println("Network lost")
    }
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
Exercise: Create an app that fetches and displays a list of posts from JSONPlaceholder API using Retrofit. Each item shows the post title. Tapping an item shows the full post in a detail screen. Add pull-to-refresh, error handling with retry, and offline caching using Room as a cache layer.