This question already has answers here:
How to return DataSnapshot value as a result of a method?
(6 answers)
getContactsFromFirebase() method return an empty list
(1 answer)
Setting Singleton property value in Firebase Listener
(3 answers)
Closed 1 year ago.
Hello I am trying to implement a RecyclerView of users that are in my RealtimeDatabase, but this code dosen't work and I dont know why. Here is the code for Activity.
class NewMessageActivity : AppCompatActivity() {
private val TAG = "NewMessageActivityTag/////////////////////////////"
private lateinit var binding : ActivityNewMessageBinding
private lateinit var realtimeDatabase : FirebaseDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNewMessageBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title = "New Message"
realtimeDatabase = FirebaseDatabase.getInstance("https://firechat-931d2-default-rtdb.asia-southeast1.firebasedatabase.app/")
val users = fetchUsers()
Log.d(TAG,"vkgjhbiuglkjhglioiuhghubgDone2 $users")
binding.rvNewMessageActivity.adapter = UsersAdpater(this,users)
binding.rvNewMessageActivity.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
}
private fun fetchUsers() : ArrayList<UserInfo> {
val ref = realtimeDatabase.getReference("/users")
val userList = ArrayList<UserInfo>()
Log.d(TAG,"pidsnigfngadsnfpoin")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if(snapshot.exists()) {
Log.d(TAG,"$snapshot")
for(child in snapshot.children){
val tempchild = child.getValue(UserInfo::class.java)
Log.d(TAG,"$tempchild")
userList.add(tempchild ?: UserInfo("default_UID","default_email","default_name","default_picurl"))
}
}
}
override fun onCancelled(error: DatabaseError) {
Log.d(TAG,"$error ${error.message}")
}
})
Log.d(TAG,"$userList")
// if(userList.isEmpty()) {
// for(i in 1..10) {
// userList.add(UserInfo("default_UID$i","default_email$i","default_name$i","https://firebasestorage.googleapis.com/v0/b/firechat-931d2.appspot.com/o/ProfileImages%2FL5yOgELQtmXdUcnxf7S6Iqs1OsN2.profileImage?alt=media&token=3573195b-a5bb-499f-a79b-7191d5ad2655"))
// }
// }
return userList
}
}
Now I'll add the data class code
1
Now I'll add the adapter
class UsersAdpater(val context : Context, val gotData : ArrayList<UserInfo>) : RecyclerView.Adapter<UsersAdpater.UserViewHolder>() {
private var data : ArrayList<UserInfo> = gotData
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user_newmessasgesactivity,parent,false)
return UserViewHolder(view)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.username.text = data[position].name
holder.useremail.text = data[position].email
Glide.with(context).load(data[position].profilePicUrl).into(holder.userImage).onLoadFailed(ContextCompat.getDrawable(context,R.drawable.profilepicnormall))
holder.entireLayout.setOnClickListener {
//TODO("IMPLEMENT OPENING A NEW CHAT WITH THIS USER")
Log.d("NewMessagesActivity","${data[position].toString()}")
}
}
override fun getItemCount(): Int {
return data.size
}
private fun updateList(userList : ArrayList<UserInfo>) {
data = userList
}
inner class UserViewHolder(private val itemView : View) : RecyclerView.ViewHolder(itemView) {
val username = itemView.findViewById<TextView>(R.id.item_rvnma_username)
val useremail = itemView.findViewById<TextView>(R.id.item_rvnma_email)
val userImage = itemView.findViewById<ImageView>(R.id.item_rvnma_image)
val entireLayout = itemView
}
}
// In the Log
2 As you can see the the function fetchUsers() has already returned an empty list and the data is logged after that. Why so ? And also why is it returning an empty list when it logs data for every user?
Is it because the fetching of users takes time? Do I need to use Coroutines?
Related
I got some categories from an api and trying to show them on a recycler view but it doesn't work for some reason.
Although the data appears correctly in the logcat, it is sent as null to the Category adapter.
This is the Main Activity (where I'm trying to show the data):
`
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val TAG = "MEALZ"
private lateinit var binding: ActivityMainBinding
private val viewModel:MealsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = CategoryAdapter(this)
binding.categoriesRv.adapter = adapter
viewModel.getMeals()
lifecycleScope.launch {
viewModel.categories.collect {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
}
}
`
This is Recycler Category Adapter :
`
class CategoryAdapter(private val context: Context?) :
RecyclerView.Adapter<CategoryAdapter.CategoryViewHolder>() {
private var categoryList: MutableList<Category?> = mutableListOf<Category?>()
inner class CategoryViewHolder(itemView: CategoryLayoutBinding) :
RecyclerView.ViewHolder(itemView.root) {
val name = itemView.categoryNameTv
val img = itemView.categoryIv
val des = itemView.categoryDesTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val binding = CategoryLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
var category = categoryList[position]
holder.name.text = category?.strCategory
holder.des.text = category?.strCategoryDescription
Glide.with(context as Context).load(category?.strCategoryThumb).into(holder.img)
}
override fun getItemCount(): Int {
return categoryList.size
}
fun setData(CategoryList: List<Category>) {
this.categoryList.addAll(CategoryList)
notifyDataSetChanged() //to notify adapter that new data change has been happened to adapt it
}
}
`
This is the View Model class:
#HiltViewModel
class MealsViewModel #Inject constructor(private val getMealsUseCase: GetMeals): ViewModel() {
private val TAG = "MealsViewModel"
private val _categories: MutableStateFlow<CategoryResponse?> = MutableStateFlow(null)
val categories: StateFlow<CategoryResponse?> = _categories
fun getMeals() = viewModelScope.launch {
try {
_categories.value = getMealsUseCase()
} catch (e: Exception) {
Log.d(TAG, "getMeals: ${e.message.toString()}")
}
}
}
you create your _categories with null as initial value, so first value of categories flow will be null and only second one will contain fetched data. As a workaround, you can check that data is not null:
viewModel.categories.collect {
if (it != null) {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
or introduce some kind of "loading" state
I have one RecyclerView, and if I click one item of it, I want make Data of RecyclerView change.
companion object {
var regionData: MutableLiveData<List<String>> = MutableLiveData()
var smallRegionScreen : Boolean = false
}
So I use MutableLiveData to make Data mutable and keep being observed.
adapter = regionData.value?.let { RegionAdapter(this, it, smallRegionScreen) }!!
I pass regionData.value as Data of Adapter, whose type will be List. And smallRegionScreen is Boolean value.
Since first click of item and second click of item in RecyclerView's taken action will be different, so I differentiate it by this value.
regionDB.get()
.addOnSuccessListener { documents ->
for (document in documents) {
var newArray = ArrayList<String>()
Log.d("리지온1", "$document")
for ((k, v) in document.data) {
regionData.value.add(v.String)
Log.d("리지온", "${regionData.value}")
}
}
adapter.notifyDataSetChanged()
}
binding.regionRecycler.adapter=adapter
binding.regionRecycler.layoutManager= LinearLayoutManager(this)
}
As here, I add item to regionData.value.
But it shows empty Array.
What is problem here?
And My Adapter is below, my process is okay?
class RegionAdapter(private var context: Context, private var regionData: List<String>, private var smallRegionScreen: Boolean): RecyclerView.Adapter<RegionAdapter.RegionViewHolder>() {
var userDB = Firebase.firestore.collection("users")
var userId = Firebase.auth.currentUser?.uid
companion object {
var REGION_RECYCLER_CLICKED = "com.chungchunon.chunchunon_android.REGION_RECYCLER_CLICKED"
}
inner class RegionViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
val regionView: TextView = itemView.findViewById(R.id.regionSelectText)
fun bind (position: Int) {
regionView.text = regionData[position]
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RegionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_region, parent, false)
return RegionViewHolder(view)
}
override fun onBindViewHolder(holder: RegionViewHolder, position: Int) {
holder.bind(position)
holder.itemView.setOnClickListener { view ->
if(!smallRegionScreen) {
var selectedRegion = regionData[position]
var regionSet = hashMapOf(
"region" to selectedRegion
)
userDB.document("$userId").set(regionSet)
var regionDB = Firebase.firestore.collection("region")
regionDB
.document("4ggk4cR82mz46CjrLg60")
.collection(selectedRegion.toString())
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
for ((k, v) in document.data) {
regionData.plus(v.toString())
}
}
smallRegionScreen = true
}
} else {
var selectedSmallRegion = regionData[position]
var regionSet = hashMapOf(
"smallRegion" to selectedSmallRegion
)
userDB.document("$userId").set(regionSet)
}
}
}
override fun getItemCount(): Int {
return regionData.size
}
}
If you want to add data to your MutableLiveData:
val regionDataList = regionData.value
val templateList = mutableListOf<String>()
regionDataList?.forEach { data ->
templateList.add(data)
}
templateList.add(v.String)
regionData.value = templateList
you can add data in the list like this :-
regionData.value.add(v.toString())
I'm developing a simple activity where I have to show a dynamic list of reservations. The list has to be synchronized with the Firebase Realtime DB.
What I did
I created a Reservation class with the relative ReservationAdapter and the dedicated layout item_customer_reservation.xml. Up to now, I populate every item with the same informations, just for testing purposes.
class ReservationAdapter(private val reservationList: Array<Reservation>) : RecyclerView.Adapter<ReservationAdapter.ReservationViewHolder>()
{
inner class ReservationViewHolder(private val binding: ItemCustomerReservationBinding) : RecyclerView.ViewHolder(binding.root)
{
fun bind(reservation: Reservation)
{
Log.w(Constants.CUTITAPP, "ReservationViewHolder:onBind")
binding.apply {
// TODO dummy informations
tvSalonName.text = "test salon"
tvServiceName.text = "test name"
tvServiceDate.text = "test date"
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReservationViewHolder
{
val binding = ItemCustomerReservationBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ReservationViewHolder(binding)
}
override fun onBindViewHolder(reservationViewHolder: ReservationViewHolder, position: Int)
{
reservationViewHolder.bind(reservationList[position])
}
override fun getItemCount() = reservationList.size
}
This is the Fragment that hosts the RecyclerView:
class CustomerHomeFragment : Fragment()
{
private lateinit var db: DatabaseReference
private var uid: String? = null
private var reservationList: Array<Reservation>? = null
private var reservationAdapter: ReservationAdapter? = null
// view binding
private var _binding: FragmentCustomerHomeBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
arguments?.let {
uid = it.getString(Constants.UID) // get the UID from the passed parameter
}
db = FirebaseDatabase.getInstance().reference
reservationList = arrayOf()
reservationAdapter = ReservationAdapter(reservationList!!)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
{
// inflate the layout for this fragment
_binding = FragmentCustomerHomeBinding.inflate(inflater, container, false)
addCustomerReservationsEventListener()
binding.apply {
rvReservations.adapter = reservationAdapter
}
return binding.root
}
override fun onDestroyView()
{
super.onDestroyView()
_binding = null // needed to destroy the binding when the fragment is destroyed
}
/**
* Adds a [ValueEventListener] for the reservations of the customer.
* Populates the reservationList with the [Reservation] of the customer and notifies the adapter.
*/
private fun addCustomerReservationsEventListener()
{
val reservationsReference = db.child(Constants.DB_CUSTOMERS).child(uid!!).child(Constants.DB_CUSTOMER_RESERVATIONS)
val customerReservationsListener = object : ValueEventListener {
override fun onDataChange(reservationUidListSnapshot: DataSnapshot)
{
// get reservationUidList
val reservationUidList = reservationUidListSnapshot.getValue<HashMap<String, String>>()
Log.d(Constants.CUTITAPP, "reservationUidList: $reservationUidList")
if (!reservationUidList.isNullOrEmpty())
{
// get a Reservation for each Uid to populate the list
for (reservationUid in reservationUidList.values)
{
if (reservationUid.isNotEmpty()) // TODO temporary fix
{
db.child(Constants.DB_RESERVATIONS).child(reservationUid).get().addOnSuccessListener { reservationSnapshot: DataSnapshot ->
val reservation = reservationSnapshot.getValue<Reservation>()
reservationList = reservationList?.plus(reservation!!)
reservationAdapter?.notifyDataSetChanged()
Log.d(Constants.CUTITAPP, "reservationList size: ${reservationList?.size}")
Log.d(Constants.CUTITAPP, "reservationAdapter size: ${reservationAdapter?.itemCount}")
}
}
}
}
}
override fun onCancelled(databaseError: DatabaseError)
{
Log.w(getString(R.string.app_name), "CustomerHomeFragment:onCancelled", databaseError.toException())
}
}
reservationsReference.addValueEventListener(customerReservationsListener)
}
companion object
{
#JvmStatic
fun newInstance(uid: String) =
CustomerHomeFragment().apply {
arguments = Bundle().apply {
putString(Constants.UID, uid) // pass the parameters to the created Fragment
}
}
}
}
Problem
The reservationList collects the Reservation of the user (currently in the DB I added one reservation for testing), its size is 1; it correctly contains a Reservation object with the correct data that i have in the DB.
The problem is that the reservationAdapter has 0 items inside, like the notifyDataSetChanged() is not working. The result is that the RecyclerView is empty.
How can I fix that? Thank you!
I manage to get a solution.
reservationList = reservationList?.plus(reservation!!) creates a new Array object, that is not linked to the Adapter (the old empty array is still attached to it).
The solution is to use ArrayList instead of Array in the ReservationAdapter, then change the onDataChange() like this:
override fun onDataChange(reservationUidMapSnapshot: DataSnapshot)
{
reservationList?.clear()
reservationAdapter?.notifyDataSetChanged()
// Get reservationUidMap
val reservationUidMap = reservationUidMapSnapshot.getValue<HashMap<String, String>>()
if (!reservationUidMap.isNullOrEmpty())
{
// Get a Reservation for each UID to populate the list
for (reservationUid in reservationUidMap.values)
{
db.child(Constants.DB_RESERVATIONS).child(reservationUid).get().addOnSuccessListener { reservationSnapshot: DataSnapshot ->
val reservation = reservationSnapshot.getValue<Reservation>()
reservationList?.add(reservation!!)
// Notify the adapter of the changes
reservationAdapter?.notifyDataSetChanged()
}
}
}
}
I hope it will be helpful.
Cheers!
I feel that in this code my layout is not being attached.I verified by toast,log.d but nothing worked.Here this I am using for a recycler view view holder.Code is below
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(mContext).inflate(R.layout.posts_layout,parent,false)
return ViewHolder(view)
}
and onBindViewHolder.I user log to check if this method is being used but,its not used
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
firebaseUser = FirebaseAuth.getInstance().currentUser!!
Log.d("binded", true.toString())
val post = mPost[position]
getPublisherInfo(holder.profileImage ,holder.userName , holder.userFullName ,post.getpublisher())
Glide.with(mContext).load(post.getpostId()).placeholder(R.drawable.image).into(holder.postImage)
}
I also checked if my list size id 0 for it to not come.For it use used log and it showed the list size 5 which is correct.This meant that my data is being retrieved from database.So it think that the my layout inflater is not working.The code for it is.
private fun readPosts() {
val postsRef = FirebaseDatabase.getInstance().reference.child("Posts")
postsRef.addValueEventListener(object : ValueEventListener{
#SuppressLint("NotifyDataSetChanged")
override fun onDataChange(snapshot: DataSnapshot) {
postList.clear()
for(snapshot in snapshot.children){
val post = snapshot.getValue(Post::class.java)
for (uid in (followingList as ArrayList<String>)) {
if (post!!.getpublisher().equals(uid)) {
postList.add(post)
}
Log.d("added", postList.size.toString())
Log.d("added", postList.toString())
postAdapter.notifyDataSetChanged()
}
}
}
override fun onCancelled(error: DatabaseError) {}
})
}
My entire adapter code is
package com.u_me_pro.free.Adapters
class PostAdapter(private val mContext: Context,private val mPost: List<Post>) :
RecyclerView.Adapter<PostAdapter.ViewHolder>() {
private lateinit var firebaseUser : FirebaseUser
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(mContext).inflate(R.layout.posts_layout,parent,false)
Log.d("attached layout","true")
return ViewHolder(view)
}
class ViewHolder(#NonNull itemView: View) : RecyclerView.ViewHolder(itemView){
var description: TextView = itemView.findViewById(R.id.description)
var userName: TextView = itemView.findViewById(R.id.user_name_post)
var userFullName: TextView = itemView.findViewById(R.id.user_full_name_post)
var commentCount: TextView = itemView.findViewById(R.id.comment_count)
var likeCount: TextView = itemView.findViewById(R.id.love_count)
var profileImage: RoundedImageView = itemView.findViewById(R.id.user_profile_image_post)
var postImage: RoundedImageView = itemView.findViewById(R.id.post_image)
var like: CardView = itemView.findViewById(R.id.post_image_like)
var comment: CardView = itemView.findViewById(R.id.post_image_comment)
var save: CardView = itemView.findViewById(R.id.post_image_save)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
firebaseUser = FirebaseAuth.getInstance().currentUser!!
Log.d("binded", true.toString())
val post = mPost[position]
getPublisherInfo(holder.profileImage ,holder.userName , holder.userFullName ,post.getpublisher())
Glide.with(mContext).load(post.getpostId()).placeholder(R.drawable.image).into(holder.postImage)
}
override fun getItemCount(): Int {
return mPost.size
}
private fun getPublisherInfo(profileImage: RoundedImageView, userName: TextView, userFullName: TextView, publisherId: String) {
val usersRef = FirebaseDatabase.getInstance().reference.child("Users").child(publisherId)
Log.d("publisher id",publisherId)
usersRef.addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()){
val user = snapshot.getValue(User::class.java)
userName.text = user!!.getFullname()
userFullName.text = user.getFullname()
Glide.with(mContext).load(user.getImage()).placeholder(R.drawable.profile).into(profileImage)
}
}
override fun onCancelled(error: DatabaseError) {
Log.d(TAG, error.getMessage());
}
})
}
}
Please help me out soon.
Thanks!
You're updating the post list and notifying the adapter of a change but it doesn't appear that you're ever actually passing in the updated list to the adapter.
postAdapter.notifyDataSetChanged()
Won't do anything if the actual data in your adapter hasn't changed.
You should add a way to update your post list in the adapter such as
fun setPostData(posts: List<Post>) {
this.adapterPosts = posts
notifyDataSetChanged() // Either call this here or where you were before
}
This would also require you to create a new variable to hold the passed-in list
private var adapterPosts = mPosts
I dont know what to do in this case because every 7 rows the image drawable is favorite even when it shouldn't be.
class RecipesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recipes)
getRecipes()
}
companion object{
val REC_KEY = "REC_KEY"
}
var adapter = GroupAdapter<GroupieViewHolder>()
private fun getRecipes(){
val ref = FirebaseDatabase.getInstance().getReference("/recipes")
val adapter = GroupAdapter<GroupieViewHolder>()
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
p0.children.forEach{
val reci = it.getValue(Recipes::class.java)
if(reci != null) {
adapter.add(RecipesItem(reci))
}
}
adapter.setOnItemClickListener{ item, view ->
val recItem = item as RecipesItem
val intent = Intent(view.context, RecipeDetail::class.java)
intent.putExtra(REC_KEY, recItem.recipes)
startActivity(intent)
}
recycler_recipes.adapter = adapter
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
This class is to put the items in the views. I need them to be, but when I check for favourite or not there's the problem.
class RecipesItem(val recipes: Recipes): Item<GroupieViewHolder>(){
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.name_recycler.text = recipes.reci_name
viewHolder.itemView.type_recipes.text = recipes.reci_diet
viewHolder.itemView.timeToPrepare.text = recipes.reci_time.plus(" min")
//checkiffav
val user = FirebaseAuth.getInstance().currentUser!!.uid
val fav = FirebaseDatabase.getInstance().getReference("/users/$user/fav_recipes/${recipes.id}")
fav.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot){
if(p0.exists()){
viewHolder.itemView.favorite.setImageResource(R.drawable.favorite)
}
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
override fun getLayout(): Int {
return R.layout.row_recipes
}
}
Basically I have 20 recipes and I have a favorite system, but the recycler view only gets the id of the recipes on every 7 rows, then it repeats the same id's! I'm really new to kotlin and firebase and I dont know how to solve. On every 7 rows the recipe appears has fav because the id that the firebase reference is getting is the same as the first one.
Answered on Groupie issue tracker: https://github.com/lisawray/groupie/issues/320#issuecomment-581915693