DEV Community

Cover image for Work flow for dealing with Retrofit in Android
Tristan Elliott
Tristan Elliott

Posted on

Work flow for dealing with Retrofit in Android

Table of contents

  1. Read First
  2. My workflow
  3. Use Postman to query endpoint
  4. Model response object
  5. Create method in interface
  6. Create method in Repository

The code

YouTube version

Introduction

  • I have embarked on my next app, a Twitch client app. This series will be all my notes and problems faced when creating this app.

READ FIRST

  • This tutorial already assumes you are familiar with Retrofit and have retrofit up and running in your app. This tutorial is meant to be a more abstract guide and give individuals a broad understanding of my work flow. Don't focus too much on the details, it is the series of steps within this workflow that are most helpful

Workflow when using Retrofit

  • As I have been working with the Twitch API, I have been dealing a lot with Retrofit. So much so that I have developed a little workflow for when I need to call a new endpoint of the API. The workflow consists of 4 steps:

1) Use Postman to query endpoint
2) Model the response object
3) Create method in Retrofit interface
4) Create method in the Repository layer

1) Use Postman to query the endpoint

  • Documentation can only tell us so much and sometimes it can be out of date or incorrect. That is why before I start coding I like to query the endpoint with Postman. This has two main benefits:

1) Verifies that we are hitting the proper endpoint with all the proper headers and parameters.

2) Gives us a proper example of what the response object will be. It is also important that we verify that the response object is in JSON. JSON is the proper format that Retrofit will use to convert to our Kotlin object

2) Model the response object

  • So lets assume we are using the get-streams API endpoint. Upon a successful response, we will get a JSON object that looks something like this:
"data": [
        {
            "id": "40457151671",
            "user_id": "582765062",
            "user_login": "premiertwo",
            "user_name": "PremierTwo",
            "game_id": "33214",
            "game_name": "Fortnite",
            "type": "live",
            "title": "fork knife @rubberross @ironmouse @aicandii | Tokyo, Japan | !discord",
            "viewer_count": 3506,
            "started_at": "2023-07-17T08:14:13Z",
            "language": "en",
            "thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_premiertwo-{width}x{height}.jpg",
            "tag_ids": [],
            "tags": [
                "Improv",
                "Forehead",
                "AMA",
                "VoiceActing",
                "Japan",
                "Travel",
                "English",
                "NoSpoilers"
            ],
            "is_mature": false
        },
]


Enter fullscreen mode Exit fullscreen mode
  • Our modeled response object will then look like this:
data class FollowedLiveStreams(
    val data:List<StreamData>
)

Enter fullscreen mode Exit fullscreen mode
data class StreamData(
    val id:String,
    @SerializedName("user_id")
    val userId:String,
    @SerializedName("user_login")
    val userLogin:String,
    @SerializedName("user_name")
    val userName:String,
    @SerializedName("game_id")
    val gameId:String,
    @SerializedName("game_name")
    val gameName:String,
    val type:String,
    val title:String,
    @SerializedName("viewer_count")
    val viewerCount:Int,
    @SerializedName("started_at")
    val startedAt:String,
    val language:String,
    @SerializedName("thumbnail_url")
    val thumbNailUrl:String,
    @SerializedName("tag_ids")
    val tagIds:List<String>,
    val tags:List<String>,
    @SerializedName("is_mature")
    val isMature:Boolean

)
Enter fullscreen mode Exit fullscreen mode
  • There are 2 things you should look out for when trying to model a JSON response object:

1) [] square brackets : indicate a list. That is why the Kotlin data class version of "tags": ["Improv"] is val tags:List<String>

2) {} curly brackets : indicates a new object. Which is why we are modeling the JSON response object as two classes. Notice how its "data": [ {, the square brackets followed by the curly brackets are telling us that data is a list of objects. Which results in our data:List<StreamData> value

3) Create method in Retrofit interface

  • Now is the part where we modify the interface which will represent out Http call. Assuming we are still using the get-streams API, the call would look like this:
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query

@GET("streams/followed")
    suspend fun getFollowedStreams(
        @Header("Authorization") authorization:String,
        @Header("Client-Id") clientId:String,
        @Query("user_id") userId:String
    ): Response<FollowedLiveStreams>

Enter fullscreen mode Exit fullscreen mode
  • Notice how we can use @Header, @Query to define dynamic values for the query parameter and the header. Also, look how the return object is Response<FollowedLiveStreams>, we are using the FollowedLiveStreams we have created previously. Retrofit will automatically map the JSON object to our FollowedLiveStreams object

4) Create method in the Repository layer

  • Now inside of the repository layer we can make the actual call the the api, like so:
class TwitchRepoImpl(
    private val twitchClient: TwitchClient = TwitchRetrofitInstance.api
) {
suspend fun getFollowedLiveStreams(
        authorizationToken: String,
        clientId: String,
        userId: String
    ): Flow<Response<FollowedLiveStreams>> = flow{
        emit(Response.Loading)
        val response = twitchClient.getFollowedStreams(
            authorization = authorizationToken,
            clientId = clientId,
            userId = userId
        )
        if (response.isSuccessful){
           emit(response.body())
        }else{
          emit(Response.Failure(Exception("Error!, code {${response.code()}}")))

        }
    }

}

Enter fullscreen mode Exit fullscreen mode
  • The code above might seem a little confusing but to clarify things, the Response object is of my own creation:
sealed class Response<out T> {


    object Loading: Response<Nothing>()


    data class Success<out T>(
        val data:T
    ):Response<T>()


    data class Failure(
        val e:Exception
    ):Response<Nothing>()

}
Enter fullscreen mode Exit fullscreen mode
  • I am returning Kotlin Flows from getFollowedLiveStreams() so my downstream classes (ViewModels) are able to call collect{} on this method and react differently depending if the Response is Loading, Success or Failure

  • After we have defined the method inside of the repository layer, we are free to use the method as we see fit.

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)