Skip to main content
Android Development with Kotlin
CHAPTER 20 Beginner

Working with APIs and JSON (Retrofit)

Updated: May 16, 2026
35 min read

# CHAPTER 20

Working with APIs and JSON (Retrofit)

1. Introduction

A mobile application isolated from the internet is rarely useful. Weather apps download forecasts, Instagram downloads images, and banking apps download account balances. This global communication is facilitated by RESTful APIs transmitting data in JSON (JavaScript Object Notation) format. Downloading and translating this raw text into Kotlin Objects manually is tedious and error-prone. In this chapter, we will master Retrofit, the undisputed industry-standard networking library for Android. We will define API interfaces, automate JSON parsing utilizing converter factories, and execute seamless network requests within our Coroutine background threads.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand the structure of a REST API and JSON payload.
  • Configure internet permissions within the Android Manifest.
  • Integrate the Retrofit and Gson/Moshi libraries via Gradle.
  • Define a Retrofit HTTP Interface (GET, POST).
  • Execute network calls securely utilizing Coroutines and viewModelScope.

3. Understanding JSON

When you ask an API for user data, it doesn't send Kotlin code. It sends raw text formatted as JSON.
json
12345
{
  "id": 1,
  "name": "Leanne Graham",
  "email": "Sincere@april.biz"
}

Our goal is to automatically convert that JSON text into a Kotlin Data Class:

kotlin
1
data class User(val id: Int, val name: String, val email: String)

4. Step 1: Internet Permissions and Gradle

Before writing code, you MUST tell the Android OS that your app needs the internet. Open AndroidManifest.xml and add this line *above* the <application> tag:
xml
1
<uses-permission android:name="android.permission.INTERNET" />

Next, add Retrofit and Gson (the JSON parser) to your build.gradle.kts (Module :app) dependencies:

kotlin
12
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

*Click "Sync Now" in the top right corner!*

5. Step 2: The Data Class and The API Interface

We will use a free, public testing API: https://jsonplaceholder.typicode.com/users

1. Create the Data Class: The variable names MUST perfectly match the keys in the JSON payload!

User.kt
12345
data class User(
    val id: Int,
    val name: String,
    val email: String
)

2. Create the API Interface: Retrofit generates the complex networking code for us. We just need to define an Interface that outlines the "Endpoints" we want to hit. Notice we use the suspend keyword because networking takes time!

ApiService.kt
123456789
import retrofit2.http.GET

interface ApiService {
    
    // The @GET annotation defines the endpoint URL!
    // It will append "/users" to the base URL we define later.
    @GET("/users")
    suspend fun getUsers(): List<User> // Automatically parses the JSON into a List of User objects!
}

6. Step 3: Building the Retrofit Instance

We need to create the actual Retrofit "Machine" and give it the Base URL of the server. This is usually done in a dedicated object (Singleton) so we don't recreate the heavy network client 50 times.
RetrofitClient.kt
1234567891011121314151617
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {

    private const val BASE_URL = "https://jsonplaceholder.typicode.com"

    // The 'by lazy' block ensures this is only built ONCE when first accessed
    val apiService: ApiService by lazy {
        val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create()) // Tells it to use Gson for JSON!
            .build()

        retrofit.create(ApiService::class.java)
    }
}

7. Step 4: Executing the Call in the ViewModel

Now we combine Chapter 19 (Coroutines) with our Retrofit client!
UserViewModel.kt
123456789101112131415161718192021222324252627282930313233
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class UserViewModel : ViewModel() {

    private val _users = MutableLiveData<List<User>>()
    val users: LiveData<List<User>> get() = _users

    // For error handling
    private val _error = MutableLiveData<String>()
    val error: LiveData<String> get() = _error

    fun fetchUsersFromNetwork() {
        // Launch on IO thread because networking is heavy!
        viewModelScope.launch(Dispatchers.IO) {
            try {
                // This ONE line does the HTTP request, waits for the response, and parses the JSON!
                val userList = RetrofitClient.apiService.getUsers()
                
                // Switch to Main thread to update LiveData!
                _users.postValue(userList) // .postValue() is a shortcut for updating LiveData from a background thread!
                
            } catch (e: Exception) {
                // Catch timeouts, missing internet, or 404 errors!
                _error.postValue("Network Error: ${e.message}")
            }
        }
    }
}

*Your Activity can now observe the users LiveData and instantly display the downloaded data in a RecyclerView!*

8. @SerializedName (Fixing JSON Mismatches)

What if the server sends JSON with ugly variable names like first_name_str, but you want your Kotlin variable to be cleanly named firstName? You use the @SerializedName annotation.
kotlin
1234567
import com.google.gson.annotations.SerializedName

data class User(
    val id: Int,
    @SerializedName("first_name_str") // The ugly JSON key
    val firstName: String             // The beautiful Kotlin variable
)

9. Common Mistakes

  • Forgetting the Manifest Permission: If you write perfect Retrofit code, but forget to add <uses-permission android:name="android.permission.INTERNET" /> to the Manifest, the app will instantly crash with a SecurityException: Permission denied (missing INTERNET permission?).
  • Trailing Slashes in URLs: Retrofit is very strict about URLs. If your Base URL ends with a slash (https://api.com/) and your endpoint also starts with a slash (@GET("/users")), Retrofit will crash because it combines them into https://api.com//users. Standardize your slash placement.

10. Best Practices

  • Dependency Injection: Building RetrofitClient as an object (Singleton) is great for beginners. However, in enterprise applications, networking clients are injected using tools like Hilt or Dagger to allow for robust unit testing and mocking.

11. Exercises

  1. 1. Ensure your device/emulator has internet access. Add the Internet permission to your AndroidManifest.xml.
  1. 2. Review the public API at https://jsonplaceholder.typicode.com/posts. Create a Kotlin Data Class named Post that mirrors the JSON structure (userId, id, title, body).

12. Coding Challenges

Challenge: Extend the ApiService interface. Add a new endpoint to fetch a *single* user by ID. You will need to research Retrofit Path variables. (Hint: The endpoint will look like @GET("/users/{id}") and the function signature will require @Path("id") userId: Int).

13. MCQ Quiz with Answers

Question 1

Before a developer can execute any HTTP networking request via Retrofit, what critical configuration step MUST be explicitly declared within the Android project?

Question 2

When utilizing Retrofit with the GsonConverterFactory, what specific programmatic purpose does the @SerializedName annotation serve?

14. Interview Questions

  • Q: Explain the architectural role of GsonConverterFactory within the Retrofit initialization pipeline. How does it eliminate boilerplate XML/JSON parsing logic?
  • Q: Detail the structural advantage of declaring Retrofit endpoints as suspend functions. How does this integrate seamlessly with the MVVM viewModelScope architecture?
  • Q: Contrast LiveData.value with LiveData.postValue(). Why must a developer strictly utilize .postValue() when updating state directly from an asynchronous Retrofit IO thread?

15. FAQs

Q: I heard Gson is old and I should use Moshi or Kotlinx.serialization. Is that true? A: Gson is older, but it is still the most widely taught and utilized parser in introductory tutorials and legacy enterprise codebases. Moshi and Kotlinx.serialization are the modern, faster, Kotlin-native standards. Switching Retrofit to use MoshiConverterFactory is incredibly easy once you understand the core concepts taught here!

16. Summary

In Chapter 20, we broke the boundaries of local execution and connected our application to the global internet. We mastered the Retrofit networking architecture, abstracting complex HTTP transactions into elegant Kotlin Interfaces. We automated the translation of raw JSON payloads into robust Kotlin Data Classes utilizing the Gson Converter. Crucially, we executed these network operations flawlessly within the strict confines of asynchronous Coroutines, utilizing .postValue() to bridge background thread downloads safely back to the reactive UI architecture.

17. Next Chapter Recommendation

Downloading data from the internet is great, but what if the user opens the app on an airplane? They will see a blank screen. We need to save the downloaded data permanently to the phone's hard drive so it works offline. Proceed to Chapter 21: Local Databases with Room (SQLite).

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·