Home > Software engineering >  How to pass data from adapter to fragment?
How to pass data from adapter to fragment?

Time:04-16

I've been trying to pass data(the email and phone of a user) from my adapter to my fragment. From what I've read online I should use a interface for this but I cant the data into my fragment still. Can anyone explain in steps how I should add a interface and how to put data into my interface from my adapter so I can call it in my fragment. Or is there another way to pass data from my adapter to my fragment. Below are my adapter and my fragment.

Adapter:

package ie.wit.savvytutor.adapters
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import ie.wit.savvytutor.R
import ie.wit.savvytutor.activity.MainActivity
import ie.wit.savvytutor.fragments.ViewChatFragment
import ie.wit.savvytutor.models.UserModel


class UserAdapter(private val userList: ArrayList<UserModel>, val context: Context) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {


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


class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    val username: TextView = itemView.findViewById(R.id.userNameView)

}

override fun onBindViewHolder(holder: UserViewHolder, position: Int, ) {
    val currentItem = userList[position]



    holder.username.text = currentItem.email
    holder.itemView.setOnClickListener {
        println(currentItem)

        




        val optionsFrag = ViewChatFragment()
        (context as MainActivity).getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragment_container, optionsFrag, "OptionsFragment").addToBackStack(
                null
            )
            .commit()
    }


}

override fun getItemCount(): Int {
    return userList.size

}

}

Fragment

package ie.wit.savvytutor.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup    
import androidx.annotation.Nullable
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.*
import ie.wit.savvytutor.R
import ie.wit.savvytutor.adapters.UserAdapter
import ie.wit.savvytutor.models.UserModel

class TutorChatFragment : Fragment() {

private lateinit var userRecyclerView: RecyclerView
private lateinit var userArrayList: ArrayList<UserModel>
private lateinit var dbRef: DatabaseReference
private lateinit var mAuth: FirebaseAuth



override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    dbRef = FirebaseDatabase.getInstance("DATABASE LINK").getReference("Users").ref
    mAuth = FirebaseAuth.getInstance()
}

@Nullable
override fun onCreateView(
    inflater: LayoutInflater,
    @Nullable container: ViewGroup?,
    @Nullable savedInstanceState: Bundle?
): View {
    //inflate the fragment layout
    val root = inflater.inflate(R.layout.tutor_chat_fragment, container, false)
    userRecyclerView = root.findViewById(R.id.userListView)
    userRecyclerView.layoutManager = LinearLayoutManager(context)
    userRecyclerView.setHasFixedSize(true)


    userArrayList = arrayListOf<UserModel>()
    getUser()
    return root
}

private fun getUser() {

    userArrayList.clear()

    dbRef.addValueEventListener(object: ValueEventListener{
        override fun onDataChange(snapshot: DataSnapshot) {
            for (postSnapshot in snapshot.children) {
                val currentUser = postSnapshot.getValue(UserModel::class.java)

                //BUG FIX 1.26.13

                val email = currentUser?.email
                if (email != null) {
                    userArrayList.add(currentUser)
                }

                userRecyclerView.adapter?.notifyDataSetChanged()
                userRecyclerView.adapter = context?.let { UserAdapter(userArrayList, it) }

            }
        }

        override fun onCancelled(error: DatabaseError) {
            TODO("Not yet implemented")
        }

    })

}

}

CodePudding user response:

If you want to use an interface, you just need to define one with a function to receive your data, make the fragment implement it, then pass the fragment to the adapter as an implementation of that interface:

data class UserData(val email: String, val phone: String)

class UserAdapter(
    private val userList: ArrayList<UserModel>,
    val context: Context,
    val handler: UserAdapter.Callbacks // added this here, so you're passing it in at construction
) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {

    ...

    private fun doWhatever(email: String, phone: String) {
        // pass the data to the handler (which will probably be your Fragment)
        handler.handleUserData(UserData(email, phone))
    }

    // nested inside the UserAdapter class to keep things tidy
    interface Callbacks {
        fun handleUserData(data: UserData)
    }
}

Then in the Fragment:

// add the Callbacks interface type
class TutorChatFragment : Fragment(), UserAdapter.Callbacks {
    override fun onCreateView(
        inflater: LayoutInflater,
        @Nullable container: ViewGroup?,
        @Nullable savedInstanceState: Bundle?
    ): View {
        ...
        userRecyclerView.layoutManager = LinearLayoutManager(context)

        // set up the adapter here, passing this fragment as the Callbacks handler
        userRecyclerView.adapter = UserAdapter(userArrayList, context, this)
        ...
    }

    // interface implementation
    override fun handleUserData(data: UserData) {
        // whatever
    }
}

And that's it. You're not hardcoding a dependency on that particular Fragment type, just the interface, and this fragment implements it so it can pass itself.


A more Kotliny way to do it is to ignore interfaces and just pass a function instead

class UserAdapter(
    private val userList: ArrayList<UserModel>,
    val context: Context,
    val handler: (UserData) -> Unit // passing a function that takes a UserData instead
) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {

    ...

    private fun doWhatever(email: String, phone: String) {
        // call the handler function with your data (you can write handler.invoke() if you prefer)
        handler(UserData(email, phone))
    }
}
// no interface this time
class TutorChatFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        @Nullable container: ViewGroup?,
        @Nullable savedInstanceState: Bundle?
    ): View {
        ...
        userRecyclerView.layoutManager = LinearLayoutManager(context)

        // pass in a handler function
        userRecyclerView.adapter = UserAdapter(userArrayList, context) { userData ->
            handleUserData(userData)
        }
        // or if you're just passing it to that function down there,
        // you could do UserAdapter(userArrayList, context, ::handleUserData)
        // and pass the function reference
        ...
    }

    // might be convenient to still do this in its own function
    private fun handleUserData(data: UserData) {
        // whatever
    }
}

Ideally you should be doing what I've done there - create the adapter once during setup, and have a function on it that allows you to update it. Your code creates a new one each time you get data. You do this the same way in both though

Your other option is using a view model that the adapter and fragment both have access to, but this is how you do the interface/callback approach

CodePudding user response:

Actually there is one very easy way to get data from your adapter in to your fragment or activity. It is called using Higher Order Functions. In your adapter Add higher order function in your adapter.

class UserAdapter(private val userList: ArrayList<UserModel>, val context: Context) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
//your rest of the adapter's code

private var onItemClickListener:((UserModel)->Unit)? = null
fun setOnItemClickListener(listener: (UserModel)->Unit) {
     onItemClickListener = listener
   }
}

In Your UserViewHolder

val rootView = itemView.rootView

In Your onBindViewHolder set a click listener on rootView

holder.rootView.setOnClickListener {
   onItemClickListener?.let{
          it(currentItem)
    }
}

In Your Fragment

//create the instance of UserAdapter
userAdapter.setOnItemClickListener {
  //here you have your UserModel in your fragment, do whatever you want to with it
}

And, a suggestion in the last. Start using ViewBinding, it will save you from a lots of hectic work.

  • Related