Retrofit API Call in Android Explained for Beginners

Points to understand :

If you are learning Android development, Retrofit code can look confusing at first:

private fun getPost() {

    RetrofitClient.apiService
        .getPost()
        .enqueue(object : Callback<Post> {

            override fun onResponse(
                call: Call<Post>,
                response: Response<Post>
            ) {

                if (response.isSuccessful) {

                    val post = response.body()

                    tvTitle.text = post?.title
                    tvBody.text = post?.body

                } else {

                    Toast.makeText(
                        this@day2,
                        "Something went wrong",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }

            override fun onFailure(
                call: Call<Post>,
                t: Throwable
            ) {

                Toast.makeText(
                    this@day2,
                    "Error: ${t.message}",
                    Toast.LENGTH_LONG
                ).show()
            }
        })
}

There are functions, callbacks, objects, Retrofit, Response, Call, Throwable, null safety and UI updates—all inside a few lines of code.

Instead of memorizing this code, let’s understand what Android is actually doing.


1. First Understand the Goal

Our Android application wants data from a server.

Imagine the server contains this JSON:

{
  "userId": 1,
  "id": 1,
  "title": "Hello Android",
  "body": "I am learning Retrofit"
}

We want to download this data and display:

Title → Hello Android

Body → I am learning Retrofit

The basic flow is:

Android App
     ↓
Send API Request
     ↓
Internet
     ↓
Server
     ↓
JSON Response
     ↓
Android App
     ↓
Convert JSON into Kotlin Object
     ↓
Display Data on Screen

Retrofit helps us perform this communication.


2. What is Retrofit?

Retrofit is a popular HTTP client library for Android.

In simple terms:

Retrofit helps our Android application communicate with a web server or REST API.

Without Retrofit, we would have to manually handle many details related to HTTP requests, URLs, response handling and data conversion.

With Retrofit, we can describe an API like this:

interface ApiService {

    @GET("posts/1")
    fun getPost(): Call<Post>
}

Now Retrofit knows that getPost() should make a GET request to:

posts/1

3. Understanding the Post Data Class

Suppose the server returns:

{
  "userId": 1,
  "id": 1,
  "title": "Hello Android",
  "body": "I am learning Retrofit"
}

We create a Kotlin data class:

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

Retrofit’s converter can transform the JSON response into this Kotlin object.

Conceptually:

SERVER JSON

{
  "userId": 1,
  "id": 1,
  "title": "Hello Android",
  "body": "I am learning Retrofit"
}

              ↓

        JSON Converter

              ↓

KOTLIN OBJECT

Post(
    userId = 1,
    id = 1,
    title = "Hello Android",
    body = "I am learning Retrofit"
)

This concept is extremely important.

The server sends JSON.

Our Android code works with a Kotlin object.


4. Understanding getPost()

Now let’s examine our function.

private fun getPost() {

}

This is simply a Kotlin function.

Its responsibility is:

Get a Post from the server and display it on the screen.

You can think about functions based on their responsibility:

fun loginUser()

fun registerUser()

fun getProducts()

fun getStudents()

fun getPost()

In our example:

getPost()

means:

Start the process of getting a post.


5. Understanding RetrofitClient.apiService

Next we have:

RetrofitClient.apiService

Break this into two parts:

RetrofitClient
      ↓
apiService

RetrofitClient normally contains our Retrofit configuration.

For example:

object RetrofitClient {

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

    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val apiService: ApiService =
        retrofit.create(ApiService::class.java)
}

Here:

BASE_URL

tells Retrofit where the server is.

And:

apiService

gives us access to functions declared inside:

ApiService

6. Understanding .getPost()

Now look at:

RetrofitClient.apiService.getPost()

Our ApiService contains:

@GET("posts/1")
fun getPost(): Call<Post>

So calling:

.getPost()

creates a request for that API.

But there is an important concept here.

getPost() does not immediately return the Post data.

It returns:

Call<Post>

Think about Call<Post> as:

A request capable of fetching a Post

It is similar to placing an order.

getPost()

does NOT mean:

"Here is your Post."

Instead it means:

"Here is the request for getting your Post."

7. Understanding enqueue()

Next comes:

.enqueue(...)

This is one of the most important parts.

RetrofitClient.apiService
    .getPost()
    .enqueue(...)

enqueue() tells Retrofit to execute the network request asynchronously.

In beginner-friendly language:

Send the request without freezing the Android application, and inform me when the result arrives.

Think about ordering food.

You place an order
       ↓
Restaurant starts preparing food
       ↓
You don't stand frozen at the counter
       ↓
You can do other things
       ↓
Restaurant informs you when order is ready

Network requests work similarly.

The server may take:

100 ms
500 ms
2 seconds
5 seconds

to respond.

Android should not freeze the UI while waiting.


8. Why Do We Need a Callback?

Here comes the scary-looking part:

object : Callback<Post>

Don’t worry about the syntax yet.

First understand the requirement.

Retrofit needs to know:

What should I do when the network request finishes?

There are two important possibilities:

Request
   |
   ├──── HTTP response received
   |
   └──── Network call failed

Retrofit gives us two callback functions:

onResponse()

and:

onFailure()

Conceptually:

Retrofit:

"If I receive an HTTP response,
I will call onResponse()."

"If the network request itself fails,
I will call onFailure()."

9. Understanding object : Callback

Now we can understand:

object : Callback<Post>

We are creating an anonymous object that implements Retrofit’s:

Callback<Post>

The callback requires us to provide implementations for:

onResponse()

and:

onFailure()

So our structure becomes:

.enqueue(object : Callback<Post> {

    override fun onResponse(...) {

    }

    override fun onFailure(...) {

    }
})

For beginners, remember it as:

enqueue

   ↓

Wait for Retrofit

   ↓

┌─────────────────┐
│                 │
onResponse    onFailure

10. Understanding onResponse()

Now look at:

override fun onResponse(
    call: Call<Post>,
    response: Response<Post>
) {

}

Retrofit calls this function when an HTTP response is received.

The most important thing here is:

response

The response contains information returned from the server.

Conceptually:

Android
   |
   | Request
   ↓
Server
   |
   | HTTP Response
   ↓
response

11. Understanding response.isSuccessful

Inside onResponse() we have:

if (response.isSuccessful) {

}

Why do we need this?

Because receiving an HTTP response doesn’t necessarily mean the request succeeded.

For example:

200 OK
201 Created
400 Bad Request
401 Unauthorized
404 Not Found
500 Internal Server Error

Retrofit can receive all of these as HTTP responses.

Therefore:

response.isSuccessful

checks whether the HTTP status code represents a successful response.

Generally:

2xx → Successful
4xx → Client error
5xx → Server error

So:

if (response.isSuccessful)

essentially means:

Did the server successfully process our request?


12. Understanding response.body()

If the response is successful:

val post = response.body()

body() contains the actual data returned by the server after conversion.

For example, the server returns:

{
  "userId": 1,
  "id": 1,
  "title": "Hello Android",
  "body": "Learning Retrofit"
}

Retrofit converts it into:

Post(
    userId = 1,
    id = 1,
    title = "Hello Android",
    body = "Learning Retrofit"
)

Therefore:

val post = response.body()

means:

Take the converted Post object from the server response and store it in post.

Now we can access:

post?.id

post?.userId

post?.title

post?.body

13. Why Do We Use ?.

You will notice:

post?.title

instead of:

post.title

This is Kotlin’s null-safety system.

response.body() can potentially return null.

Therefore:

val post = response.body()

may contain either:

Post object

or:

null

The safe-call operator:

?.

means:

If post is not null, access its property. Otherwise return null safely.

So:

post?.title

means:

If post exists
     ↓
get title

If post is null
     ↓
don't access title

14. Displaying the Data

Now we have:

tvTitle.text = post?.title
tvBody.text = post?.body

Suppose:

post?.title

contains:

Hello Android

Then:

tvTitle.text = post?.title

displays:

Hello Android

inside the TextView.

The complete data flow is:

Server
   ↓
JSON
   ↓
Retrofit
   ↓
Post Object
   ↓
post.title
   ↓
tvTitle
   ↓
Android Screen

15. What Happens in the else Block?

We have:

if (response.isSuccessful) {

    // display data

} else {

    Toast.makeText(
        this@day2,
        "Something went wrong",
        Toast.LENGTH_SHORT
    ).show()
}

The else block means:

The server sent us an HTTP response, but the HTTP status indicates that the request wasn’t successful.

Examples could include:

400 Bad Request

401 Unauthorized

404 Not Found

500 Internal Server Error

We therefore display:

Something went wrong

16. Understanding onFailure()

Now look at:

override fun onFailure(
    call: Call<Post>,
    t: Throwable
) {

}

onFailure() is different from an unsuccessful HTTP response.

It generally means the network call itself failed before Retrofit received a usable HTTP response.

Possible causes include:

No internet connection

Connection timeout

DNS problem

Server unreachable

Connection interrupted

t represents the failure.

We can access:

t.message

to obtain information about what happened.

Therefore:

Toast.makeText(
    this@day2,
    "Error: ${t.message}",
    Toast.LENGTH_LONG
).show()

displays the error to the user.


17. onResponse vs onFailure — Very Important

A common beginner mistake is thinking:

onResponse = Success

onFailure = Error

That isn’t completely correct.

The better mental model is:

                    API REQUEST
                         |
             ┌───────────┴───────────┐
             ↓                       ↓
      HTTP response              Network call
        received                    failed
             |                       |
        onResponse()             onFailure()
             |
       ┌─────┴─────┐
       ↓           ↓
      2xx        4xx/5xx
       |           |
    Success      HTTP Error

For example:

200 → onResponse()
404 → onResponse()
500 → onResponse()

No Internet → onFailure()
Timeout → onFailure()

Inside onResponse() we therefore check:

response.isSuccessful

18. Complete Flow of Our Code

Now let’s connect everything.

getPost()
    ↓
RetrofitClient
    ↓
apiService
    ↓
getPost()
    ↓
Call<Post>
    ↓
enqueue()
    ↓
Network Request
    ↓
SERVER
    ↓
What happened?
    |
    ├───────────────┐
    ↓               ↓
HTTP Response    Network Failure
    ↓               ↓
onResponse()     onFailure()
    ↓
isSuccessful?
    |
 ┌──┴──┐
 ↓     ↓
YES    NO
 ↓      ↓
body()  Show Error
 ↓
Post Object
 ↓
title + body
 ↓
TextViews
 ↓
SCREEN

If you understand this diagram, you understand the basic Retrofit callback flow.


19. Read the Complete Code Like English

Now let’s translate the original Kotlin code into normal English.

private fun getPost()

Create a function whose responsibility is getting a post.

RetrofitClient.apiService

Get access to our configured API service.

.getPost()

Create the API request for getting the post.

.enqueue()

Execute the request asynchronously.

object : Callback<Post>

Provide instructions for what Retrofit should do when the network operation finishes.

onResponse()

An HTTP response was received.

response.isSuccessful

Check whether the HTTP response represents success.

response.body()

Extract the converted Post object.

post?.title

Get the title safely.

post?.body

Get the body safely.

tvTitle.text

Display the title on the screen.

tvBody.text

Display the body on the screen.

else

The server responded, but the HTTP response wasn’t successful.

onFailure()

The network request itself failed.


20. Don’t Learn Retrofit by Memorizing Code

Beginners often try to memorize this:

RetrofitClient.apiService
    .getPost()
    .enqueue(object : Callback<Post> {

That is the wrong approach.

Instead, remember the architecture:

ANDROID
   ↓
API REQUEST
   ↓
RETROFIT
   ↓
INTERNET
   ↓
SERVER
   ↓
JSON
   ↓
RETROFIT
   ↓
KOTLIN OBJECT
   ↓
UI

Once this flow is clear, the Kotlin code starts making sense.


21. Beginner Exercise

Before moving to another API, try modifying the current example.

Add another TextView:

tvId.text = post?.id.toString()

Now display:

Post ID
Title
Body

Then try changing:

@GET("posts/1")

to:

@GET("posts/2")

Run the application again and observe how the server returns a different post.

This is a simple way to understand that Android isn’t creating this data—the data is coming from the server.


22. Final Mental Model

Whenever you see Retrofit callback code, think:

1. Which API am I calling?

2. What type of data am I expecting?

3. Send the request.

4. Wait without blocking the UI.

5. Did I receive an HTTP response?

6. Was that response successful?

7. Extract the response body.

8. Convert/use the Kotlin object.

9. Update the UI.

10. Handle failures.

The most important concept is:

Android sends a request → server sends JSON → Retrofit converts it into a Kotlin object → our application uses that object to update the UI.

Once this flow is clear, Retrofit stops looking like complicated syntax and starts looking like a predictable sequence of operations.


What’s Next?

After understanding this callback-based example, the next topics to learn are:

Retrofit + Coroutines → Repository → ViewModel → StateFlow/LiveData → UI

That is the point where we move from a simple beginner Retrofit example toward production-style Android architecture.

SoftSence Academy — Android Development Learning Series

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top