Home > Software design >  Error parsing fragment to recyclerview in Kotlin
Error parsing fragment to recyclerview in Kotlin

Time:04-08

As part of one of my courses, I am using an API in Android Studio for the first time. For this I use OkHttp. However, I have the following error when I launch my application. I guess it's because the JSON is not parsed well but I can't find a solution.

If anyone could help me that would be great!

Thank you

The error screenshot

MyMarqueRecyclerViewAdapter.kt


import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.fragment_marque_list.*
import okhttp3.*
import java.io.IOException

/**
 * A fragment representing a list of Items.
 */
class MarqueFragment : Fragment(), OnMarqueClickListener {

    private var columnCount = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        arguments?.let {
            columnCount = it.getInt(ARG_COLUMN_COUNT)
        }

        fetchJson()
    }

    fun fetchJson() {
        val url = "https://tp3.infomobile.app/api/v1/brand"

        val request = Request.Builder().url(url).build()

        val lesmarques = OkHttpClient()
        lesmarques.newCall(request).enqueue(object: Callback {
            override fun onResponse(call: Call, response: Response) {
                val body = response.body?.string()
                println(body)

                val gson = GsonBuilder().create()

                val homeFeed = gson.fromJson(body, HomeFeed::class.java)
                println(homeFeed)
                activity?.runOnUiThread {
                    recyclerView_main.adapter = MyMarqueRecyclerViewAdapter(homeFeed)
                }
            }

            override fun onFailure(call: Call, e: IOException) {
                println("Failed to execute request")
            }
        })
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_marque_list, container, false)

        // Set the adapter
        if (view is RecyclerView) {
            with(view) {
                layoutManager = when {
                    columnCount <= 1 -> LinearLayoutManager(context)
                    else -> GridLayoutManager(context, columnCount)
                }
                //adapter = MyMarqueRecyclerViewAdapter(homeFeed)
            }
        }
        return view
    }

    override fun onMarqueItemClicked(position: Int) {
        Toast.makeText(this.context, "ça marche", Toast.LENGTH_LONG).show()
        val intent = Intent([email protected](),MainActivity2::class.java)
        startActivity(intent)
    }

    companion object {

        // TODO: Customize parameter argument names
        const val ARG_COLUMN_COUNT = "column-count"

        // TODO: Customize parameter initialization
        @JvmStatic
        fun newInstance(columnCount: Int) =
            MarqueFragment().apply {
                arguments = Bundle().apply {
                    putInt(ARG_COLUMN_COUNT, columnCount)
                }
            }
    }
}

class HomeFeed(val marques: List<Marque>)

class Marque(val id: Int, val name: String)

MarqueFragment.kt


import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView

import ca.ulaval.ima.tp3.placeholder.PlaceholderContent.PlaceholderItem
import ca.ulaval.ima.tp3.databinding.FragmentMarqueBinding

/**
 * [RecyclerView.Adapter] that can display a [PlaceholderItem].
 * TODO: Replace the implementation with code for your data type.
 */
class MyMarqueRecyclerViewAdapter(val homeFeed: HomeFeed?) : RecyclerView.Adapter<MyMarqueRecyclerViewAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

        return ViewHolder(
            FragmentMarqueBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )

    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = homeFeed?.marques?.get(position)
        holder.contentView.text = item.toString()
        holder.contentView.setOnClickListener{
            val context=holder.contentView.context
            val intent = Intent( context, MainActivity2::class.java)
            intent.putExtra("marque", item.toString())
            context.startActivity(intent)
        }
    }

    override fun getItemCount(): Int {
        return homeFeed?.marques!!?.count()
    }

    inner class ViewHolder(binding: FragmentMarqueBinding) : RecyclerView.ViewHolder(binding.root) {
        val contentView: TextView = binding.content

        override fun toString(): String {
            return super.toString()   " '"   contentView.text   "'"
        }
    }

}

CodePudding user response:

First of all, you can trace the crash using the log which clearly states that the crash happened at line 42 in MyMarqueRecyclerViewAdapter.kt, in the getItemCount function, which means that this functions is causing it:

override fun getItemCount(): Int {
    return homeFeed?.marques!!?.count()
}

What's happening here is that you're forcing kotlin to treat a property that might be null as a non null property, by using the "!!" operator, this tells kotlin that this propery would never be null in any case, and that isn't true in your case since its throwing a NullPointerException, what you actually need to do is allow it to be null and provide an alternative value in case it was null, using the elvis operator, like this:

override fun getItemCount(): Int {
    return homeFeed?.marques?.count() ?: 0
}

Another thing is that you're using the count() function, i think you meant to use the size property instead:

override fun getItemCount(): Int {
    return homeFeed?.marques?.size ?: 0
}

Second, i noticed your class declaration and compared the content to the json's response content, it seems that the json response conatins a parameter called "content", while the propery in the HomeFeed class is called "marques", which results in GSON not knowing where to get "marques" from, it only knows that there is a parameter called "content" in the json and it doesn't know what to do with it, the best solution would be to annotate the HomeFeed.marques property with a @SerializedName annotation, and provide the corresponding json parameter that should be mapped to this property, like so:

class HomeFeed(
    @SerializedName("content")
    val marques: List<Marque>
)

class Marque(
    @SerializedName("id")
    val id: Int,
    @SerializedName("name")
    val name: String
)

Hope this helps!

Some resources:

Null Safety | Kotlin

Difference between list.count() and list.size

Parsing between Kotlin classes and Json objects using GSON

  • Related