How to use the same RecyclerView adapter with Activity and Fragment in Kotlin?

Viewed 805

I have created an app that works perfectly. I decided to make some changes to my app, that is, use Fragment instead of Activity at some places. This activity has a RecyclerView in it and that is used to load data which is fetched from the Firestore. Everything will remain the same except Fragment will be used instead of Activity.

Now the problem I have is with the recyclerView adapter where it takes context.

open class CargoListAdapter(
    private val context: Context,
    private var list: ArrayList<Cargo>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

When a button is clicked, I have to check the context to take further action accordingly.

            holder.binding.ibDelete.setOnClickListener {

            when (context) {
                is OrderActivity -> {
                    val activity = holder.itemView.context as? OrderActivity

                    activity?.viewSpecialRequest(
                        model.custom_request
                    )
                }

                is MyOrderDetailsActivity -> {
                    val activity = holder.itemView.context as? MyOrderDetailsActivity

                    activity?.viewSpecialRequest(
                        model.custom_request
                    )
                }

                is CargoFragment -> {
                    val activity = holder.itemView.context as? CargoFragment

                    activity?.editDeleteSpecialRequestDialog(
                        holder.binding.ibEditDeleteNote,
                        holder.binding.ibAddNote,
                        model.id,
                        model.custom_request
                    )

                }
            }
        }

This is how I am calling from one of the activities.

val cargoListAdapter = CargoListAdapter(this@OrderActivity, mCargoList)

I tried to call from the fragment like below but I can see an error "Incompatible types: CargoFragment and Context" at is CargoFragment -> {

val cargoListAdapter = CargoListAdapter(requireContext(), mCargoList)

EDIT: This is not part of the question but to show what I have done when there are a couple of buttons in the recyclerView and take action depending on which button is clicked. Someone has already asked about it in the accepted answer's comment box.

Now the onClickItem of the adapter class is changed as per my requirement. Where, as you can see, I have action:String and which will let me know which button is clicked and what function/action needs to be done further.

open class CargoListAdapter(
    private val context: Context,
    private var list: ArrayList<Cargo>,
    private val onClickItem: (pos: Int,addButton: ImageButton,deleteButton: ImageButton,model: Cargo,action:String) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>()

In the onBindViewHolder I have the following.

 holder.binding.ibRemoveItem.setOnClickListener {
                        onClickItem(holder.adapterPosition,
                            holder.binding.ibAddNote,
                            holder.binding.ibDeleteNote,
                            model,
                            "removeItem")
}

Whenever a button is clicked a button onClickListener is set up as above and depending on the button I change the value for 'action'. As you can see, in the above example, it's 'removeItem' and hence from the below code the action that comes under 'removeItem' will take place. Following is what I have in the 'CargoFragment'

when (action) {
"DeleteNote" -> {
 ///TAKE SOME ACTION HERE
}
"AddNote" -> {
///TAKE SOME ACTION HERE
 }
"removeItem" -> {
///TAKE SOME ACTION HERE
}

This is what I have done to handle the situation where there are multiple buttons in the recyclerView and it's working without any flaws. I hope some experts will comment on this so that beginners can know whether this is OK to follow.

2 Answers

Instead of using an interface and accessing function by using Activity or Fragment instance you can use Kotlin higher order functions to make the adapter independent and use it anywhere in the code.

open class CargoListAdapter(
    private val context: Context,
    private var list: ArrayList<Cargo>,
    private val onClickItem:(pos: Int, viewId: Int) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

Call onClickItem in ViewHolder on click :

    holder.binding.ibDelete.setOnClickListener {
        onClickItem(adapterPosition, it.id)
    }

    holder.binding.ibEdit.setOnClickListener {
        onClickItem(adapterPosition, it.id)
    }

In Activity or Fragment :

adapter = CargoListAdapter(context, list){position, viewId->
   //receive click here
    when(viewId){
        R.id.ibDelete -> //Delete Action
        R.id.ibEdit -> //Delete Action
        // You can use sam click listener for multiple views by passing id
    }
}

Checkout this gist for complete adapter example : https://github.com/Noddy20/Gists/blob/master/RecyclerViewAdapter-1.kt

you check the context and then convert it to activity or fragment where the adapter was created, and after you call activity/fragment method viewSpecialRequest this is not very correct, if you want to call activity/fragment methods after click on ibDelete you need to add for example an interface.

open class CargoListAdapter(
    private val context: Context,
    private var list: ArrayList<Cargo>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

interface OnItemActionsListener {
        fun onDeleteClicked()
    }

var onItemActionsListener:OnItemActionsListener? =null

 holder.binding.ibDelete.setOnClickListener {
     onItemActionsListener?.onDeleteClicked()
 } 

********* 
 }
}

OrderActivity

adapter = CargoListAdapter(this, arrayListOf())
adapter.onItemActionsListener = object  : CargoListAdapter.OnItemActionsListener {
                    override fun onDeleteClicked() {
                        // TODO logic
                    }
                }

CargoFragment

adapter = CargoListAdapter(requireActivity(), arrayListOf())
adapter.onItemActionsListener = object  : CargoListAdapter.OnItemActionsListener {
                    override fun onDeleteClicked() {
                        // TODO logic
                    }
                }

if you need to do the logic in the adapter, instead of context check, you can use enum.

public enum DeleteCaseEnum {
    NONE, CASE_1, CASE_2, CASE_3
}

open class CargoListAdapter(
    private val context: Context,
    private var list: ArrayList<Cargo>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

var deleteCase:DeleteCaseEnum  = DeleteCaseEnum .NONE

   holder.binding.ibDelete.setOnClickListener {

            when (deleteCase) {
                is DeleteCaseEnum.CASE_1-> {
                 // TODO
                }
              
                is DeleteCaseEnum.CASE_2-> {
                 // TODO
                }
            }

OrderActivity

adapter = CargoListAdapter(this, arrayListOf())
adapter.deleteCase = DeleteCaseEnum.CASE_1

CargoFragment

adapter = CargoListAdapter(requireActivity(), arrayListOf())
adapter.deleteCase = DeleteCaseEnum.CASE_2
Related