6

I'm building a ViewHolder and Adapter for a Fragment and when I try to make an OnClick for the ViewHolder, none of the contexts I pass in work. There is no activity from getActivity() that I can use, and p0!!.context nor itemView.context work either. Where should I be getting my context from, and how do I reference it?

package com._________.criminalintent

import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast

class CrimeListFragment: Fragment() {
    private var mCrimeRecyclerView: RecyclerView? = null
    private var mAdapter: CrimeAdapter? = null

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    // fragment_crime_list.xml has a RecyclerView element = crime_recycler_view
    // inflate the fragment into the activity
        val view = inflater!!.inflate(R.layout.fragment_crime_list, container, false)

        // grab the recyclerView and give it a required layoutManager
        mCrimeRecyclerView = view.findViewById(R.id.crime_recycler_view)
        mCrimeRecyclerView!!.layoutManager = LinearLayoutManager(activity)
        updateUI()
        return view
    }

    private fun updateUI() {
        val crimeLab = CrimeLab.get(activity)
        val crimes = crimeLab.getCrimes()
        mAdapter = CrimeAdapter(crimes)
        // Connect the adapter to the recyclerView
        mCrimeRecyclerView!!.adapter = mAdapter
    }

    /**
     * in Kotlin, we must give the view passed into the constructor directly
     * as a substitute for a super() call
     *
     * create a ViewHolder that holders the crime list item's view
     *
     * super(itemView) = super(inflater!!.inflate(R.layout.list_item_crime, parent, false))
     * MUST give it the direct value in Kotlin
     */
    private class CrimeHolder(inflater: LayoutInflater?, parent: ViewGroup):
        RecyclerView.ViewHolder(inflater!!.inflate(R.layout.list_item_crime, parent, false)),
        View.OnClickListener {

        private var mCrime: Crime? = null

        /**
         * When given a crime, this CrimeHolder will update the title and date for this Crime
         */
        fun bind(crime: Crime) {
           mCrime = crime
            val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
            val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
            titleTextView.text = mCrime!!.mTitle
            dateTextView.text = mCrime!!.mDate.toString()
        }

        override fun onClick(p0: View?) {

            Toast.makeText(WHAT_TO_PUT_HERE, "${mCrime!!.mTitle} clicked!", Toast.LENGTH_SHORT / 2)
                .show()
    }
}


    private class CrimeAdapter(private var mCrimes: MutableList<Crime>):
        RecyclerView.Adapter<CrimeHolder>() {

        /**
         * - Calls our CrimeHolder to make our custom ViewHolders
         * - Called by RecyclerView when it needs a new view to display
         * - Gets the layoutInflater from the ViewGroup and returns a CrimeHolder of it
         */
        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): CrimeHolder
        = CrimeHolder(LayoutInflater.from(parent!!.context), parent)


        /**
         * Bind the crime (data) to the CrimeHolder
         */
        override fun onBindViewHolder(holder: CrimeHolder?, position: Int) {
            holder!!.bind(mCrimes[position])
        }

        /**
         * Sees how many items are in the RecyclerView that need to be shown
         */
        override fun getItemCount(): Int = mCrimes.size
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
user3338275
  • 240
  • 2
  • 3
  • 13
  • 4
    I know this is not a code review, but here's my 2 cents. By setting your views as `lateinit var` you don't need the `!!` everywhere. – Edson Menegatti Jul 28 '17 at 07:48
  • Holy crap. How have I never known this... Thank you! – user3338275 Jul 28 '17 at 07:51
  • Talking about 2 cents, always use the closest context. You need context for a layout manager? Use `recyclerView.context`. /// `inflater` in `onCreateView` is never null. `container` may be null. /// Don't put context in `CrimeHolder`! View holders can access their `itemView` and therefore `itemView.context`. – Eugen Pechanec Jul 28 '17 at 08:07
  • 1
    In Kotlin, nested classes are static by default. If you need inner class, just write `inner class`. Be careful with memory leaks! – Miha_x64 Jul 28 '17 at 08:19
  • @EugenPechanec never knew that you could change the null safety on overrides. Thank you! – user3338275 Jul 28 '17 at 16:32
  • @Miha_x64, can you tell me where I might have memory leaks? – user3338275 Jul 28 '17 at 16:32
  • @user3338275, for example, if you are going to create an inner Loader, after Fragment recreation loader will leak the original Fragment. – Miha_x64 Jul 28 '17 at 18:28
  • @Miha_x64 Tbh I have no idea what you mean by loader, or pretty much anything you said. – user3338275 Jul 28 '17 at 18:36
  • @user3338275 consider googling what things are Loader in Android, inner class in Java/Kotlin, and memory leak in Java/Kotlin. – Miha_x64 Jul 28 '17 at 19:20
  • if i have an inner class in my adapter is it likely to leak. ive passed it the view created in onViewCreated and im using this context. – filthy_wizard May 15 '19 at 08:28

3 Answers3

6

In your implementation you can safely use Context from View provided to your OnClickListener

override fun onClick(p0: View) {
          Toast.makeText(p0.context, "${mCrime!!.mTitle} clicked!", Toast.LENGTH_SHORT / 2)
                .show()
    }

Just remember to set onclick:

fun bind(crime: Crime) {
           mCrime = crime
            val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
            val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
            titleTextView.text = mCrime!!.mTitle
            dateTextView.text = mCrime!!.mDate.toString()
            itemView.setOnClickListener(this)
}

Moreover all Kotlin classes are nested (static) by default. So your private class CrimeHolder is equivalent to private static class CrimeHolder in Java. That's why you don't have access to getActivity() from within CrimeHolder

rafal
  • 3,120
  • 1
  • 17
  • 12
  • The `setOnClickListener` worked! So regardless of having an onClick, I need to set the onClick for the view. Why is `this` being passed in? And I thought static is the Kotlin equivalent of companion object. – user3338275 Jul 28 '17 at 07:56
  • `val titleTextView = itemView.findViewById(R.id.crime_title)` and such should be defined directly on the view holder *not locally every time the view holder is bound*. That defeats the whole purpose of view holder - caching found views. – Eugen Pechanec Jul 28 '17 at 08:09
  • @EugenPechanec thank you, that makes a lot of sense. – user3338275 Jul 28 '17 at 16:26
1

Use itemView.context property inside your holder.

Edit: The reason why your onClick doesn't "work" (is not called) is because you haven't registered the onClickListener, for example:

itemView.setOnClickListener(this)

inside your holder init or bind.

maciekjanusz
  • 4,702
  • 25
  • 36
0
 itemView.setOnClickListener {
                Toast.makeText(itemView.context, "Item is clicked $position", Toast.LENGTH_SHORT).show() }
        }
HandyPawan
  • 1,018
  • 1
  • 11
  • 16