Don Robin
Don Robin

Reputation: 131

Kotlin: Passing data to fragment

I have a fragment which holds a RecyclerView. Through this RecyclerView, I want a click to any of the views to open another fragment (which holds a different recycler view). Each view of the RecyclerView holds a picture URL and some text. I want both of these to be passed on to the second fragment so that I can use it in a ImageView and TextView.

I've managed to open the second fragment with the RecyclerView adapter but I'm having difficulty passing on the data from the RecyclerView to my second fragment.

Here is my code:

RecyclerView Adapter

class ManuAdapter(private val manu: List<Manufacturers>) :
    RecyclerView.Adapter<ManuAdapter.ViewHolder>() {


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val mpos = manu[position]
        Picasso.get().load(mpos.bgphoto).into(holder.image)
        holder.manufacturer.text = mpos.manufacturer

        holder.itemView.setOnClickListener {

            val activity = it.context as AppCompatActivity

            CarListFragment.newInstance(mpos.manufacturer)
            Timber.e("The string is: ${mpos.manufacturer}")

            activity.supportFragmentManager.beginTransaction()
                .replace(R.id.frameLayout, CarListFragment())
                .addToBackStack(null)
                .commit()
        }

    }

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

        return ViewHolder(view)
    }


    override fun getItemCount() = manu.size

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val image: ImageView = itemView.manu_photo
        val manufacturer: TextView = itemView.manufacturer
    }
}

The Fragment I am trying to send information to

class CarListFragment : Fragment() {

    companion object {
        private const val ARG_TEXT = "myFragment_text"

        fun newInstance(text: String): CarListFragment {
            val args = Bundle()
            args.putString(ARG_TEXT, text)
            val fragment = CarListFragment()
            fragment.arguments = args
            return fragment
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val root = inflater.inflate(R.layout.fragment_car_list, container, false)


        //RecyclerView in the second fragment
        val carsRepository = CarsRepository().getAllCars()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({
            root.recycler_view.apply {
                layoutManager = LinearLayoutManager(activity)
                adapter = CarsAdapter(it)
                root.loadingicon.visibility = View.GONE
                root.loadingtext.visibility = View.GONE
            }
        }, {
        })


        val index = arguments?.getString(ARG_TEXT,"")
        Timber.e("index is: $index")
        root?.manu_name?.text = index

        return root
    }
}

After clicking on the view from RecyclerView, the log statement in my Adapter class shows me the correct String is being placed into the newInstance constructor. However, in my second fragment, my log statement for index shows that it is null.

Upvotes: 0

Views: 3828

Answers (1)

Don Robin
Don Robin

Reputation: 131

Okay I managed to figure out how to get my data to my second fragment. The solution I found was not too complicated so I think this would help someone just coming into coding/kotlin like I am. It might not be the most efficient way but it's good enough for me.

Basically, I added an interface and a listener to communicate information from a click on the recyclerview to fragment #1 that holds the recyclerview to fragment #2. The three pieces of data I communicated were the position of the list, and two strings (one of which is an image URL).

The two sources I used to develop the code is a video that walks through how to get data through a onClickListener from the RecyclerView to my fragment #1 (MainFragment) and a tutorial that showed how to communicate that data to fragment #2 (CarListFragment).

First, I created the interface: I put this in it's own file just to keep things a bit tidier

package com.myname.mypackage
    
interface Communicator {
    fun passData(position: Int, name: String, image: String)
}

In my RecyclerView Adapter:

  • removed the OnClickListener from my onBindViewHolder as this slows down the app (which is what I was originally doing in the question)
  • added private val listener: Communicator to the adapter constructor at the top
  • in my ViewHolder class
    • changed the class to an inner class
    • added View.OnClickListener after the extends ":"
    • added the init block for the setOnClickListener
    • added the override fun onClick(v: View?)

The steps for this were followed from the video I linked.

class ManuAdapter(
    private val manu: List<Manufacturers>,
    private val listener: Communicator
    ) :
    RecyclerView.Adapter<ManuAdapter.ViewHolder>() {


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val currentItem = manu[position]
        Picasso.get().load(currentItem.bgphoto).into(holder.image)
        holder.manu_name.text = currentItem.manufacturer
    }

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


    override fun getItemCount() = manu.size

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
        val image: ImageView = itemView.manu_photo
        val manu_name: TextView = itemView.manufacturer

        init {
            itemView.setOnClickListener (this)
        }

        override fun onClick(v: View?) {
            val position = adapterPosition
            val image = manu[adapterPosition].bgphoto
            val name =  manu[adapterPosition].manufacturer
            if (position != RecyclerView.NO_POSITION) {
                listener.passData(position,name, image)
            }
        }
    }
}

In my fragment #1 (which holds the RecyclerView):

  • implement Communicator in the fragment by adding it after the ":"
  • added context for the listener in the adapter line adapter = ManuAdapter(it, this@MainFragment)
  • added the passData function inside the fragment #1 class.

Everything up to the creation of the onItemClick function is covered in the video, from here I followed the tutorial the rest of the way.

class MainFragment : Fragment(R.layout.fragment_main), Communicator {


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val root =  inflater.inflate(R.layout.fragment_main, container, false)

        val carsRepository = CarsRepository().getAllManufacturers()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                root.recycler_view_main.apply {
                    layoutManager = LinearLayoutManager(activity)
                    adapter = ManuAdapter(it, this@MainFragment)
                    root.loadingicon_main.visibility = View.GONE
                    root.loadingtext_main.visibility = View.GONE
                }
            }, {
            })

        return root
    }

    override fun passData(position: Int, name: String, image: String) {
        val bundle = Bundle()
        bundle.putInt("input_pos", position)
        bundle.putString("input_name", name)
        bundle.putString("input_image", image)

        val transaction = this.parentFragmentManager.beginTransaction()
        val frag2 = CarListFragment()
        frag2.arguments = bundle

        transaction.replace(R.id.frameLayout, frag2)
        transaction.addToBackStack(null)
        transaction.commit()
    }
}

In Fragment #2

  • added the three variables after initiating the fragment
  • grabbed the variables from the Communicator interface
class CarListFragment : Fragment() {

    var inputPos: Int? = null
    var inputName: String = ""
    var inputImage: String = ""

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val root = inflater.inflate(R.layout.fragment_car_list, container, false)

        //code for my fragment in here


        inputPos = arguments?.getInt("input_pos")
        inputName = arguments?.getString("input_name").toString()
        inputImage = arguments?.getString("input_image").toString()
        Picasso.get().load(inputImage).into(root?.manu_pic)
        root?.manu_name?.text = inputName

        return root
    }
}

manu_pic and manu_name are just the id's of the ImageView and TextView in the XML inflated by the fragment.

Hope someone finds this information useful.

Upvotes: 3

Related Questions