In today’s article we are going to learn how we can use livedata with retrofit. Retrofit allows us to make HTTP calls but it returns Call object. If you are not familiar with retrofit please head over here and here.
Java:
@GET
Call<Model> getData();
Kotlin:
@GET
fun getData() : Call<Model>
But the good thing is retrofit allows us to add custom Call Adapters and Converter factories if we want to get data other than Call objects.
Converter Factory: The converter factory automatically converts the HTTP JSON response into the model (POJO classes) which is why while using retrofit you’ll have to use atleast one converter factory.
The most common is GsonConverterFactory.
Important Note
When using custom converter factories it is important to add a Gson converter factory as the last converter.
Why it is important?
Suppose at any point or for any particular API you do not need to get a response as livedata, in that case, it will look for other converters and if there isn’t any other converter your app will crash.
Let’s Start
We are going to use a third-party library livedata-call-adapter for this article.
- Add Dependencies in our app level gradle file (app/gradle). Add following lines.
1 2 3 4 5 6 7 8 9 10 11 12 |
def retrofit = '2.9.0' // retrofit implementation "com.squareup.retrofit2:retrofit:$retrofit" // gson converter implementation "com.squareup.retrofit2:converter-gson:$retrofit" // livedata call adapter and converter implementation "com.github.leonardoxh:retrofit2-livedata-adapter:1.1.2" |
For demo purposes, we are going to use reqres.in fake rest API that allows us to do fake API calls.
We will get the following responses
Users list
Single User
2. Create retrofit client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class RetrofitClient { companion object { var retrofit: Retrofit? = null fun getClient(): Retrofit { if (retrofit == null) { retrofit = Retrofit.Builder() .baseUrl("https://reqres.in/api") // Call factory for livedata .addCallAdapterFactory(LiveDataCallAdapterFactory.create()) // converter factory for livedata .addConverterFactory(LiveDataResponseBodyConverterFactory.create()) // fallback converter factory .addConverterFactory(GsonConverterFactory.create()) .build() } return retrofit!! } } } |
3. Create Interface
1 2 3 4 5 6 7 8 9 10 11 |
interface ApiInterface { // Get response as livedata // For this it will use livedata response body converter @GET(API.GET_USERS_LIST) fun getUsers(): LiveData<Resource<UserResponse>> // Get single user (to demonstrate default case) // it will use GsonConverter to convert this response @GET(API.GET_USER) fun getSingleUser(@Path("user_id") userId: Int): Call<SingleUserResponse> } |
API Class
1 2 3 4 5 6 |
class API { companion object { const val GET_USERS_LIST = "users" const val GET_USER = "users/{user_id}" } } |
4. Create Models
UserResponse Model
1 2 3 4 5 6 7 8 9 10 11 12 13 |
data class UserResponse( val page: Long, @SerializedName("per_page") val perPage: Long, val total: Long, @SerializedName("total_pages") val totalPages: Long, val data: List<User> ) |
User Model
1 2 3 4 5 6 7 8 9 10 11 12 |
data class User( val id: Long, val email: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, val avatar: String ) |
5. Create APiClient
1 2 3 4 5 6 7 8 9 10 11 |
class ApiClient { companion object { fun getUsersList(): LiveData<Resource<UserResponse>> { return RetrofitClient.getClient().create(ApiInterface::class.java).getUsers() } fun getSingleUser(userId:Int): Call<SingleUserResponse> { return RetrofitClient.getClient().create(ApiInterface::class.java).getSingleUser(userId) } } } |
How to use:
- Use Live Data Response
1 2 3 4 |
// Live data response (observe for changes) ApiClient.getUsersList().observe(/*Lifecycle owner*/ owner, Observer<ResponseModel> {response -> // do something with response }) |
Complete MainActivity Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Live data response (observe for changes) ApiClient.getUsersList().observe(this, { response -> if (response.isSuccess) { val data = response.resource!! Log.i(TAG, "onCreate: ${data.data[0]}") } else { // handle error case response.error?.printStackTrace() } }) // Normal retrofit response (Call object) val call = ApiClient.getSingleUser(/* Demo User Id */1) call.enqueue(object : Callback<SingleUserResponse> { override fun onResponse( call: Call<SingleUserResponse>, response: Response<SingleUserResponse> ) { if (response.isSuccessful) { Log.i(TAG, "onResponse: ${response.body()?.data}") } } override fun onFailure(call: Call<SingleUserResponse>, t: Throwable) { t.printStackTrace() } }) } companion object { private const val TAG = "MainActivity" } } |
Custom Call Adapter and Converter
In the above part, we have used a third-party library for livedata call adapter and live data response converter, but what if we want to use our own classes.
Here, we will create our own call adapter and response converter and will see how we can use them as well.
- Custom API Response Object (Just like Resource Object in case of library)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
sealed class ApiResponse<T> { companion object { fun <T> create(response: Response<T>): ApiResponse<T> { return if (response.isSuccessful) { val body = response.body() if (body == null || response.code() == 204) { ApiEmptyResponse() } else { ApiSuccessResponse(body) } } else { ApiErrorResponse( response.code(), response.errorBody()?.string() ?: response.message() ) } } fun <T> create(errorCode: Int, error: Throwable): ApiErrorResponse<T> { return ApiErrorResponse(errorCode, error.message ?: "Unknown Error!") } } } class ApiEmptyResponse<T> : ApiResponse<T>() data class ApiErrorResponse<T>(val errorCode: Int, val errorMessage: String) : ApiResponse<T>() data class ApiSuccessResponse<T>(val body: T) : ApiResponse<T>() |
2. Live Data Call Adapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class LiveDataCallAdapter<R>(private val responseType: Type) : CallAdapter<R, LiveData<ApiResponse<R>>> { override fun adapt(call: Call<R>): LiveData<ApiResponse<R>> { return object : LiveData<ApiResponse<R>>() { private var isSuccess = false override fun onActive() { super.onActive() if (!isSuccess) enqueue() } override fun onInactive() { super.onInactive() dequeue() } private fun dequeue() { if (call.isExecuted) call.cancel() } private fun enqueue() { call.enqueue(object : Callback<R> { override fun onFailure(call: Call<R>, t: Throwable) { postValue(ApiResponse.create(-1, t)) } override fun onResponse(call: Call<R>, response: Response<R>) { postValue(ApiResponse.create(response)) isSuccess = true } }) } } } override fun responseType(): Type = responseType } |
3. Live Data Call Adapter Factory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
sealed class LiveDataCallAdapterFactory : CallAdapter.Factory() { override fun get( returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit ): CallAdapter<*, *>? { val observableType = getParameterUpperBound( 0, returnType as ParameterizedType ) as? ParameterizedType ?: throw IllegalArgumentException("resource must be parameterized") return LiveDataCallAdapter<Any>( getParameterUpperBound( 0, observableType ) ) } } |
How to use:
Retrofit Client
1 2 3 4 5 6 7 8 9 10 11 12 |
if (retrofit == null) { retrofit = Retrofit.Builder() .baseUrl("https://reqres.in/api/") // Note this line // Custom Live Data Call Adapter Factory .addCallAdapterFactory(LiveDataCallAdapterFactory()) // fallback converter factory .addConverterFactory(GsonConverterFactory.create()) .build() } |
ApiInterface
1 2 3 4 |
// Get response as livedata // Using our own Live call adapter and ApiResponse @GET(API.GET_USERS_LIST) fun getUsers(): LiveData<ApiResponse<UserResponse>> |
Handle Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ApiClient.getUsersList().observe(this, { response -> // Checks if the response is success // then fetch data if (response is ApiSuccessResponse) { val data = response.body Log.i(TAG, "onCreate: ${data.data[0]}") } // response not success handle error case else if (response is ApiErrorResponse) { Log.i(TAG, "onCreate: ${response.errorMessage}") } }) |
Summary
In this article, we learned how we can use livedata with retrofit to make HTTP calls.
I hope this blog will help you in getting some basic knowledge about retrofit and how to make HTTP calls with retrofit and how you can add custom call adapter and converters.
Thanks for reading!!