Home > Blockchain >  RecyclerView in Fragment not populating
RecyclerView in Fragment not populating

Time:09-17

So I've been wrestling with this for days and I need some help. I've made this code work in an activity, but then I move it to a fragment it doesn't work. Everything else is the same between the two.

Using the debugger with the working Activity, the line

apiService = retrofit.create<HomeJsonApiService>(HomeJsonApiService::class.java)

goes to getItemCount(). However in the fragment it goes directly to onCreateView in the Fragment. I've attached my code below. Thanks in advance for the help! And be gentle. I'm still new to this :)

First is my fragment:

class TabHomeActivity : Fragment() {

    val itemList = ArrayList<HomeCards>()
    lateinit var adapter: HomeCardsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
        adapter = HomeCardsAdapter()
        var rv = binding.rvHomeCards
        rv.adapter = adapter
        loadData()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.cards_home, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }

    private fun loadData() {
        ApiManager.getInstance().service.listHeroes()
            .enqueue(object : Callback<ResponseData<List<HomeCards>>> {

                override fun onResponse(
                    call: Call<ResponseData<List<HomeCards>>>,
                    response: Response<ResponseData<List<HomeCards>>>
                ) {
                    val listData: List<HomeCards> = response.body()!!.data

                    // updating data from network to adapter
                    itemList.clear()
                    itemList.addAll(listData)
                    adapter.updateData(itemList)
                    adapter.notifyDataSetChanged()
                }

                override fun onFailure(call: Call<ResponseData<List<HomeCards>>>, t: Throwable) {
                }

            })
    }

}

The HTTP request:

data class ResponseData<T> (
    val code: Int,
    val data: T
)
interface HomeJsonApiService {
    @GET("marvel-heroes.asp?h=2")
    fun listHeroes(): retrofit2.Call<ResponseData<List<HomeCards>>>
}

class ApiManager {

    private var apiService: HomeJsonApiService? = null

    init {
        createService()
    }

    val service: HomeJsonApiService get() = apiService!!

    private fun createService() {
        val loggingInterceptor =
            HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
                override fun log(message: String) {
                    Log.i("Retrofit", message)
                }
            })

        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

        val client = OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
            .connectTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(loggingInterceptor)
            .build()

        val retrofit: Retrofit = Retrofit.Builder()
            .client(client)
            .baseUrl("https://www.mywebsite.com/jsonfolder/JSON/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        apiService = retrofit.create(HomeJsonApiService::class.java)
    }

    companion object {

        private var instance: ApiManager? = null

        fun getInstance(): ApiManager {
            return instance ?: synchronized(this) {
                ApiManager().also { instance = it }
            }
        }
    }

}

And my adapter:

class HomeCardsAdapter() : RecyclerView.Adapter<HomeCardsAdapter.ViewHolder>() {

    private lateinit var itemList: List<HomeCards>
    lateinit var context: Context

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        context = parent.context
        val view = LayoutInflater.from(context).inflate(R.layout.cards_home, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return if (::itemList.isInitialized) itemList.size else 0
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind()
    }

    fun updateData(list: List<HomeCards>) {
        itemList = list;
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        //var binding = ActivityMainBinding(layoutInflater(inf))

        fun bind() {
            val item = itemList.get(adapterPosition)

            ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.name
            ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.superheroName

            Glide.with(context)
                .load(item.photo)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .circleCrop()
                .into(ViewHolder(itemView).itemView.findViewById<ImageView>(R.id.cardHomeIcon))
        }
    }
}

class HomeCards {
    @SerializedName("superhero_name")
    var superheroName: String = ""
    var name: String = ""
    var photo: String = ""
}

CodePudding user response:

If onResponse() gets called and provides response, verify that code updating UI is running on main/ui thread. Common source of issue when working with network (other threads).

activity?.runOnUiThread {
   itemList.clear()
   itemList.addAll(listData)
   adapter.updateData(itemList)
   adapter.notifyDataSetChanged()
}

CodePudding user response:

The main problem is:

var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)

That is inside on onCreate but onCreateView is returning another view inflater.inflate(R.layout.cards_home, container, false)

So you are applying the adapter to a recycler that is on the binding, but the view on the screen is inflated from the layout. Change it to this:

private lateinit var binding: FragmentTabHomeActivityBinding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
         binding = FragmentTabHomeActivityBinding.inflate(layoutInflater, container, false)
        return binding.root
    }

And move the code from from onCreate to onViewCreated but make sure to use the lateinit binding

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        adapter = HomeCardsAdapter()
        var rv = binding.rvHomeCards
        rv.adapter = adapter
        loadData()
    }

After that there is a problem in your adapter: private lateinit var itemList: List<HomeCards> very specifically List<HomeCards>. The method notifyDataSetChanged doesn't work by changing or updating the reference of the data structure but when the collection is modified. Change it to this:

private val list = mutableListOf<HomeCards>()


    override fun getItemCount(): Int {
        return list.size()
    }

    fun updateData(list: List<HomeCards>) {
        this.itemList.clear()
        this.itemList.addAll(list)
        notifyDataSetChanged()
    }
  • Related