1
votes

I am trying to retrieve several rows from an API using Retrofit and Moshi, but am facing this error:

Retrofit error:- Expected BEGIN_ARRAY but was BEGIN_OBJECT at path$

The API endpoint I am requesting the data from is: https://thecodecafe.in/gogrocer-ver2.0/api/top_selling

This is the setup code for Retrofit and Moshi that I am using to request the data from the API:

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET

private const val BASE_URL = "https://thecodecafe.in/gogrocer-ver2.0/api/"

val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .build()

interface GroceryApiServices {
    @GET("top_selling")
    fun getProperties():
            Call<List<GroceryProperty>>
}

object GroceryApi {
    val retrofitServices: GroceryApiServices by lazy { retrofit.create(GroceryApiServices::class.java)}
}

This is the logic of my view model class, showing how I want to retrieve the data:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kotlin_developer.grocerysell.network.GroceryApi
import com.kotlin_developer.grocerysell.network.GroceryProperty
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response


class OverviewViewModel: ViewModel() {

    private val _response = MutableLiveData<String>()
    val response: LiveData<String>
        get() = _response

    init {
        getGroceryProperties()
    }

    private fun getGroceryProperties(){
        GroceryApi.retrofitServices.getProperties().enqueue(object : Callback<List<GroceryProperty>>{
            override fun onFailure(call: Call<List<GroceryProperty>>, t: Throwable) {
                _response.value = t.message
            }

            override fun onResponse(
                call: Call<List<GroceryProperty>>,
                response: Response<List<GroceryProperty>>
            ) {
                _response.value="Success ${response.body()?.size} Grocery Property arrived"
            }

        })
    }

    override fun onCleared() {
        super.onCleared()
    }
}
2

2 Answers

1
votes

This is how I want to retrieve data in my view model class

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kotlin_developer.grocerysell.network.GroceryApi
import com.kotlin_developer.grocerysell.network.GroceryProperty
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response


class OverviewViewModel: ViewModel() {

    private val _response = MutableLiveData<String>()
    val response: LiveData<String>
        get() = _response

    init {
        getGroceryProperties()
    }

    private fun getGroceryProperties(){
        GroceryApi.retrofitServices.getProperties().enqueue(object : Callback<List<GroceryProperty>>{
            override fun onFailure(call: Call<List<GroceryProperty>>, t: Throwable) {
                _response.value = t.message
            }

            override fun onResponse(
                call: Call<List<GroceryProperty>>,
                response: Response<List<GroceryProperty>>
            ) {
                _response.value="Success ${response.body()?.size} Grocery Property arrived"
            }

        })
    }

    override fun onCleared() {
        super.onCleared()
    }
}
1
votes

The error you getting is Moshi telling you that it expects a JSON array, but it got an object. Your Retrofit endpoint method looks like this:

@GET("top_selling")
fun getProperties():
        Call<List<GroceryProperty>>

Here, you are telling Retrofit that you expect a List. In JSON, this would be the array Moshi is expecting. However, upon clicking on the link to the endpoint you provided, the JSON you are receiving looks like this:

{
  "status": "1",
  "message": "top selling products",
  "data": [
    ...
  ]
}

As you can see, this JSON is not an array but an object that contains an array, and that's where Moshi's error originates. To deserialize it into a List, it expected the begin of an array ([), but what it found was actually the begin of an object ({)

To sum it up, you are not expecting a List, but an object, which in turn contains that List (the data array in the JSON).

You would need to define another class that encapsulates this List, something the likes of this:

data class TopSellingResponse(
    val status: String,
    val message: String,
    val data: List<GroceryProperty>
)

If you then change your method signature to

@GET("top_selling")
fun getProperties():
        Call<TopSellingResponse>

Moshi should be able to deserialize the JSON object into your class and the contained data array into the List you initially expected.