Search code examples
androidkotlinandroid-fragmentsandroid-recyclerviewandroid-adapter

How to pass data from adapter to fragment?


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")
        }

    })

}

}


Solution

  • 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