Search code examples
kotlinretrofitandroid-mvvm

Error displaying data from retrofit: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $


I am trying to get data to my app using retrofit, the data is being logged, it however doesn't display. I keep getting this error

Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

Not sure what am doing wrong.

The response looks something similar to this

   {
        info: {
        count: 493,
        pages: 25
        },
        results: [
          {
            id: 1,
            name: "Rick Sanchez",
            status: "Alive",
            species: "Human",
            gender: "Male",
            image: "https://rickandmortyapi.com/api/character/avatar/1.jpeg",
          },
             // more characters
        ]
    }

My data class looks like this

    data class Character(
    @SerializedName("results")
    val results: List<Results>,

    @SerializedName("info")
    val info: List<Info>

)
data class Results(
    @SerializedName("id")
    val id: Int,

    @SerializedName("name")
    val name: String,

    //
)

This is how my adapter looks like

    class CharacterAdapter(var characters: ArrayList<Character>): RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {

    fun updateCharacters(newCharacters: List<Character>) {
        characters.clear()
        characters.addAll(newCharacters)
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = CharacterViewHolder(
        LayoutInflater.from(parent.context).inflate(R.layout.character_item, parent, false)
    )

    override fun getItemCount() = characters.size

    override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
        holder.bind(characters[position])
    }

    class CharacterViewHolder(view: View): RecyclerView.ViewHolder(view){
        private val characterName = view.characterName
        private val progressDrawable = getProgressDrawable(view.context)

        fun bind(character:Character){
            characterName.text = character.results[adapterPosition].name
        }
    }
}

My ViewModel

    class CharacterViewModel: ViewModel() {

    @Inject
    lateinit var charactersService: CharactersService

    init {
        DaggerApiComponent.create().inject(this)
    }

    private val disposable = CompositeDisposable()

    val characters = MutableLiveData<List<Character>>()
    val charactersLoadError = MutableLiveData<Boolean>()
    val loading = MutableLiveData<Boolean>()

    fun refresh(){
        fetchCharacters()
    }


    private fun fetchCharacters(){
        loading.value = true
        disposable.add(
            charactersService.getCharacters()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(object : DisposableSingleObserver<List<Character>>(){
                    override fun onSuccess(value: List<Character>?) {
                        characters.value = value
                        charactersLoadError.value = false
                        loading.value = false
                    }

                    override fun onError(e: Throwable?) {
                        //
                        Log.e(TAG, "${e?.message}")
                    }

                })
        )
    }

    override fun onCleared() {
       //
    }
}

My Activity

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: CharacterViewModel
    private val characterAdapter = CharacterAdapter(arrayListOf())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProviders.of(this).get(CharacterViewModel::class.java)
        viewModel.refresh()

        charactersList.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = characterAdapter
        }

        swipeRefresh.setOnRefreshListener {
            swipeRefresh.isRefreshing = false
            viewModel.refresh()
        }

        observeViewModel()
    }

    private fun observeViewModel(){
        viewModel.characters.observe(this, Observer { characters ->
            characters?.let{
                charactersList.visibility = View.VISIBLE
                characterAdapter.updateCharacters(it)
            }
        })

        viewModel.charactersLoadError.observe(this, Observer { isError ->

        })

        viewModel.loading.observe(this, Observer { isLoading ->
            isLoading?.let{ loadingView.visibility = if(it) View.VISIBLE else View.GONE
                if(it) {

                    charactersList.visibility = View.GONE
                }
            }

        })
    }
}

Solution

  • You specify info as a list but the object is expected. You can fix your crash by changing type of the property:

    @SerializedName("info")
    val info: Info
    

    And I suppose you return List from your service, but there is an object in Json:

     CharactersService.getCharacters(): Character