getActivity() / context in a ViewHolder in Kotlin Android

user3338275 picture user3338275 · Jul 28, 2017 · Viewed 22.3k times · Source

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
    }
}

Answer

rafal picture rafal · Jul 28, 2017

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