[FIXED] Can't launch Fragment from RecyclerView adapter

Issue

There is a code for the NameListHolder class located in the RecyclerView adapter

class NameListHolder(val binding: NameListItemBinding) : RecyclerView.ViewHolder(binding.root) {

    fun bind(paginationLocalModel: PaginationLocalModel, position: Int, context: NameListFragment){

        binding.name.text = paginationLocalModel.name
        itemView.setOnClickListener{

            val fragment = DescriptionFragment()
            val bundle = Bundle()
            bundle.putInt("position", position)
            fragment.setArguments(bundle)

            val activity=context.context as AppCompatActivity
            activity.supportFragmentManager
                .beginTransaction()
                .replace(R.id.rd_fragment, fragment)
                .commitNow()

            Log.d("OnClick", "произошло нажатие по позиции $position")
        }

    }

}

Inside itemView.setOnClickListener the fragment should fire, but when I click on the list item, a NullPointerException is thrown:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.sem.receivedata, PID: 5484
    java.lang.NullPointerException
        at com.sem.receivedata.presentation.DescriptionFragment.<init>(DescriptionFragment.kt:22)
        at com.sem.receivedata.presentation.adapters.NameListAdapter$NameListHolder.bind$lambda-0(NameListAdapter.kt:49)
        at com.sem.receivedata.presentation.adapters.NameListAdapter$NameListHolder.$r8$lambda$FuXGFWDKhm2ZD3YhtF7Xh98aUvc(Unknown Source:0)
        at com.sem.receivedata.presentation.adapters.NameListAdapter$NameListHolder$$ExternalSyntheticLambda0.onClick(Unknown Source:4)

DescriptionFragment.kt:22 points to the string val position: Int = bundle!!.getInt("position", 0) inside the DescriptionFragment fragment

NameListAdapter.kt:49 points to the line val fragment = DescriptionFragment() inside the NameListHolder

Unknown Source:0 and Unknown Source:4 always point to line 0 and 4, no matter what is there (before there were newInstance() imports)

The whole data chain:

NameListFragment:

class NameListFragment : Fragment() {

...

    private fun initRecyclerExchangeRate(){
    
        binding?.listNameRV?.layoutManager =
            LinearLayoutManager(context)
        nameListAdapter = NameListAdapter(this)
    
        binding?.listNameRV?.adapter = nameListAdapter
    }

...

}

NameListAdapter:

class NameListAdapter(var context: NameListFragment
) : RecyclerView.Adapter<NameListAdapter.NameListHolder>() {

...

    override fun onBindViewHolder(holder: NameListHolder, position: Int) {
        holder.bind(pagination[position], position, context)
    }

...


    class NameListHolder(val binding: NameListItemBinding) : RecyclerView.ViewHolder(binding.root) {
    
        fun bind(paginationLocalModel: PaginationLocalModel, position: Int, context: NameListFragment){
    
            binding.name.text = paginationLocalModel.name
            itemView.setOnClickListener{
    
                val fragment = DescriptionFragment()
                val bundle = Bundle()
                bundle.putInt("position", position)
                fragment.setArguments(bundle)
    
                val activity=context.context as AppCompatActivity
                activity.supportFragmentManager
                    .beginTransaction()
                    .replace(R.id.rd_fragment, fragment)
                    .commitNow()
    
                Log.d("OnClick", "произошло нажатие по позиции $position")
            }
    
        }
    
    }

...

}

DescriptionFragment:

class DescriptionFragment : Fragment() {

...

    val bundle: Bundle? = this.getArguments();
    val position: Int = bundle!!.getInt("position", 0)

...

}

The launch of the first fragment from the Activity is done inside the markup:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".presentation.MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:id="@+id/framelayout"
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <fragment
                android:id="@+id/rd_fragment"
                android:name="com.sem.receivedata.presentation.NameListFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />



        </FrameLayout>

    </androidx.appcompat.widget.LinearLayoutCompat>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Solution

you are creating new fragment in this line

val fragment = DescriptionFragment()

so it gets created and at start it tries to parse bundle

val bundle: Bundle? = this.getArguments();
val position: Int = bundle!!.getInt("position", 0)

which isn’t set yet, you are doing this in next lines after Fragment creation. but you are forcing to read from bundle (!!) at very beginning, it’s null at this moment, so NullPointerException occurs

your aproach is just wrong, you should read arguments in some lifecycle method like onCreate or onCreateView, when you are shure that Fragment is created, is set up (e.g. Bundle arguments set), is added to layout and running (as it is calling lifecycle methods)

Answered By – snachmsm

Answer Checked By – Robin (Easybugfix Admin)

Leave a Reply

(*) Required, Your email will not be published