Home > front end >  How to get data from RecyclerView adapter into the fragment after clicking on it?
How to get data from RecyclerView adapter into the fragment after clicking on it?

Time:09-29

I am trying to get data from RecylerView adapter into the fragment, after clicking on that data. I have tried solving this question using interface. But my app is crashing after clicking on it.

Here is the code of the adapter:-

class SearchPlaceAdapter(
    private var mContext: Context,
    private var mPlaces: List<String>,
    private var isFragment: Boolean = false,
): RecyclerView.Adapter<SearchPlaceAdapter.ViewHolder>(){
    private val onPlaceClickListener: MainActivity.OnPlaceClickListener? = null

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val place = mPlaces[position]

        holder.place.text = place

        holder.place.setOnClickListener {
            onPlaceClickListener!!.onPlaceClick(place)
        }
    }

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

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var place: TextView =itemView.findViewById(R.id.searchPlaceTV)
    }

}

here is MainActivity code:-

interface OnPlaceClickListener {
    fun onPlaceClick(place: String?)
}

here is my fragment code to get the data:-

 override fun onPlaceClick(place: String?) {
        newPlace.text = place
    }

I am getting this as an error. The error is in the adapter:-

java.lang.NullPointerException
    at com.ehie.recyclerview.adapter.SearchPlaceAdapter.onBindViewHolder$lambda-0(SearchPlaceAdapter.kt:31)
    at com.ehi.recyclerview.adapter.SearchPlaceAdapter.$r8$lambda$KIVoR28fNIxsomM1sHTPNEhSuXQ(Unknown Source:0)
    at com.ehie.recyclerview.adapter.SearchPlaceAdapter$$ExternalSyntheticLambda0.onClick(Unknown Source:4)
at android.view.View.performClick(View.java:7792)
        at android.widget.TextView.performClick(TextView.java:16112)
        at android.view.View.performClickInternal(View.java:7769)
        at android.view.View.access$3800(View.java:910)
        at android.view.View$PerformClick.run(View.java:30218)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:226)
        at android.os.Looper.loop(Looper.java:313)
        at android.app.ActivityThread.main(ActivityThread.java:8751)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)

CodePudding user response:

The problem is that you are not initializing your onPlaceClickListener anywhere in the adapter, that's why it is throwing NullPointerException when you do onPlaceClickListener!!.onPlaceClick(place). You should pass it as an argument to the adapter.

class SearchPlaceAdapter(
    private val onPlaceClickListener: OnPlaceClickListener,
    private val mPlaces: List<String>,
    private val isFragment: Boolean = false,
): RecyclerView.Adapter<SearchPlaceAdapter.ViewHolder>() {

While creating the adapter from the fragment, you can pass this for the onPlaceClickListener parameter (it will work because your fragment implements the OnPlaceClickListener interface).

Also, you don't need to pass mContext to the adapter. In onCreateViewHolder you can use parent.context in place of mContext.

An alternate approach to sending data from adapter to fragment is to use a lambda function instead of an interface.

class SearchPlaceAdapter(
    private val mPlaces: List<String>,
    private val isFragment: Boolean = false,
    private val onPlaceClick: (String?) -> Unit,
): RecyclerView.Adapter<SearchPlaceAdapter.ViewHolder>() {

    ...
    holder.place.setOnClickListener {
        onPlaceClick(place)
    }
    ...
}

// And in your fragment
val adapter = SearchPlaceAdapter(mPlaces = <yourList>) { place ->
    newPlace.text = place
}

This approach is much more concise than using an interface.

CodePudding user response:

I was going to say you forgot to set your onPlaceClickListener property, which you did, put also you marked it as private, making it impossible to set it.

Then you use !!. to call it. !! means "crash the app if I happen to put a null value in this property". You should (almost) never use !!. In this case, it would make sense to use a null-safe ?. call, which would silently do nothing if the onPlaceClickListener is not set.

But you need to make onPlaceClickListener public (remove the private keyword in front of it) and set its value in the Activity or Fragment. Or alternatively, you could move it into your constructor to require it.

You should mark your interface as a fun interface to make it easier to work with. It will allow you to define it using a lambda (way less boilerplate).

Also, in my opinion, it is preferable not to make your Fragment directly implement the listener. It is cleaner (better encapsulation) to define your listener as a separate class instance, for example:

// In fragment:

mySearchPlaceAdapter.onPlaceClickListener = MainActivity.OnPlaceClickListener { place ->
    newPlace.text = place
}
  • Related