I tried to add a new data to firebase and then show it in a recyclerview, but after i add the data the recyclerview just loop the whole data until the data is done uploading thus creating a view like this :
the looped recyclerview
As you can see in the picture that in that link, i tried to add "food 6" data but as the result for the adding process the recyclerview keep updating the items inside it until the adding process complete
Here is my adapter code
class FoodAdapter (private var dataList : ArrayList<Food>): RecyclerView.Adapter<FoodAdapter.MyViewholder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewholder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.food_recyclerview,parent,false)
return MyViewholder(itemView)
}
override fun onBindViewHolder(holder: MyViewholder, position: Int) {
val currentItem = dataList[position]
Log.w("adapater",currentItem.image_pic.toString())
Picasso.get().load(currentItem.image_pic).into(holder.foodPic)
holder.food_name.text = currentItem.name
holder.food_price.text = currentItem.price.toString()
if (currentItem.avail == true){
holder.food_avail.text = "Tersedia"
holder.food_avail.setTextColor(Color.GREEN)
} else {
if (currentItem.avail == false){
holder.food_avail.text = "Habis"
holder.food_avail.setTextColor(Color.RED)
} else {
holder.food_avail.text = "Error"
}
}
}
override fun getItemCount(): Int {
return dataList.size
}
inner class MyViewholder (itemView : View): RecyclerView.ViewHolder(itemView){
val foodPic : ImageView = itemView.findViewById(R.id.iv_gambar_makanan)
val food_name : TextView = itemView.findViewById(R.id.tv_nama_makanan)
val food_avail : TextView = itemView.findViewById(R.id.tv_status_makanan)
val food_price : TextView = itemView.findViewById(R.id.tv_harga_makanan)
}
}
here is my update data to firebase code
private fun addDatatoFirebase() {
val dataRef = ref.child(preferences.getValue("username").toString()).child("FoodList/"+ UUID.randomUUID().toString())
var PicUrl = ""
val addImage = StorageRef.child(preferences.getValue("username").toString())
.child("food_pics/" + UUID.randomUUID())
Log.i("Cycle", "Add Image to Firebase")
addImage.putFile(FilePath).addOnSuccessListener {
addImage.downloadUrl.addOnSuccessListener {
PicUrl = it.toString()
dataRef.child("image_pic").setValue(PicUrl)
}
}
Log.i("URL",addImage.toString())
dataRef.addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
id = snapshot.ref
Log.w("PicUrl data",PicUrl)
dataRef.child("name").setValue(food_name)
dataRef.child("avail").setValue(availability)
dataRef.child("price").setValue(food_price.toInt())
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(applicationContext,"Error",Toast.LENGTH_SHORT)
}
})
}
and here is the code to get the data
private fun getFoodData() {
val foodData = ref.child(preferences.getValue("username").toString()).child("FoodList")
foodData.addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot != null) {
for (userSnapshot in snapshot.children) {
var data = userSnapshot.getValue(Food::class.java)
foodDataArrayList.add(data!!)
}
}
foodList.adapter = FoodAdapter(foodDataArrayList)
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(applicationContext,"Error",Toast.LENGTH_SHORT)
}
})
foodList.adapter = FoodAdapter(foodDataArrayList)
}
Can anyone show me how to fix this issue?
You missed out on one thing. Whenever data gets changed, you get all the entries again and again without removing the previous un-updated data. So, the simple solution would be to clear the list before getting the data. Try this code:
private fun getFoodData() {
val foodData = ref.child(preferences.getValue("username").toString()).child("FoodList")
foodData.addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot != null) {
foodDataArrayList.clear() // added this line
for (userSnapshot in snapshot.children) {
var data = userSnapshot.getValue(Food::class.java)
foodDataArrayList.add(data!!)
}
}
foodList.adapter = FoodAdapter(foodDataArrayList)
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(applicationContext,"Error",Toast.LENGTH_SHORT)
}
})
}
Related
EDIT
i changed my code and got a result but it breaks the data into separate values. it reads all the data from the database for each child. i have 2 childs but it only retrieves the last childs data and display its values separately. it stores its value from the variable eta_text
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
clear_all = view.findViewById(R.id.clear_all)
notificationView = view.findViewById(R.id.notificationList)
notificationArray = arrayListOf()
getNotifData()
var linearLayoutManager = LinearLayoutManager(context)
notificationView.layoutManager = linearLayoutManager
notificationView.setHasFixedSize(true)
}
private fun getNotifData() {
val user = FirebaseAuth.getInstance().currentUser
val useremail = user!!.email
dbref = FirebaseDatabase.getInstance().reference
dbref.child("Students").orderByChild("email").equalTo(useremail.toString()).addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
for (ds in snapshot.children) {
val idNumber: String? = ds.key
dbref.child("Notification").child(idNumber.toString()).addValueEventListener(object : ValueEventListener{
override fun onDataChange(dsnapshot: DataSnapshot) {
for (dsd in dsnapshot.children) {
val key: String? = dsd.key
dbref.child("Notification").child(idNumber.toString()).child(key.toString()).addValueEventListener(object : ValueEventListener{
override fun onDataChange(dsnap: DataSnapshot) {
notificationArray.clear()
if (dsnap.exists()){
for (queueSnapshot in dsnap.children){
notificationArray.add(Notification(queueSnapshot.value.toString()))
}
notifadapter = MyAdapter_Notification(notificationArray)
notificationView.adapter = notifadapter
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
My data class
package com.example.sqms
data class Notification(var eta_text : String ?= null,
var office_name : String ?= null,
var time_text : String ?= null)
My Adapter
class MyAdapter_Notification (private val notificationList : ArrayList<Notification>)
: RecyclerView.Adapter<MyAdapter_Notification.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val notificationView = LayoutInflater.from(parent.context).inflate(R.layout.notification_view,parent,false)
return MyViewHolder(notificationView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = notificationList[position]
holder.eta_text.text = currentItem.eta_text
holder.office_name.text = currentItem.office_name
holder.time_text.text = currentItem.time_text
}
override fun getItemCount(): Int {
return notificationList.size
}
inner class MyViewHolder(notificationView : View) : RecyclerView.ViewHolder(notificationView){
val eta_text : TextView = itemView.findViewById(R.id.eta_text)
val office_name : TextView = itemView.findViewById(R.id.office_name)
val time_text : TextView = itemView.findViewById(R.id.time_text)
}
}
picture below is the values from database
database
picture below is the data that is displayed separately
notification recycler view
i just solved it. i just removed the 3rd nested databasereference and changed the data writing
private fun getNotifData() {
val user = FirebaseAuth.getInstance().currentUser
val useremail = user!!.email
dbref = FirebaseDatabase.getInstance().reference
dbref.child("Students").orderByChild("email").equalTo(useremail.toString()).addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
for (ds in snapshot.children) {
val idNumber: String? = ds.key
dbref.child("Notification").child(idNumber.toString()).addValueEventListener(object : ValueEventListener{
override fun onDataChange(dsnapshot: DataSnapshot) {
notificationArray.clear()
if (dsnapshot.exists()){
for (queueSnapshot in dsnapshot.children){
val notif = queueSnapshot.getValue(Notification::class.java)
if (notif != null) {
notificationArray.add(notif)
}
}
notifadapter = MyAdapter_Notification(notificationArray)
notificationView.adapter = notifadapter
}
if (notifadapter.itemCount==0) {
notificationView.visibility = View.GONE;
new_notif.visibility = View.VISIBLE;
}
else {
notificationView.visibility = View.VISIBLE;
new_notif.visibility = View.GONE;
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
My problem is that I want to select an item in a RecyclerView and it should change the color and if I click on another the first selected item should change to the default color (and the last clicked should have the selected color).
I have already a color change of the selected one and if I click on the selected one again it changes to default color. Now I am only missing that if I click on an unselected item and if I have already a selected item they "switch" the color
This is my SubItem class:
class SubItem(val channel: Channel) : Item<GroupieViewHolder>() {
#SuppressLint("ResourceAsColor")
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
val profileImageUrl = channel.channel_logo
viewHolder.itemView.sub_item_name.text = channel.channel_name
viewHolder.itemView.sub_item_layout.setBackgroundResource(R.color.white)
viewHolder.itemView.sub_item_name.setTextColor(R.color.colorSecondaryText)
val targetImageView = viewHolder.itemView.sub_item_profile
try {
Picasso.get().load(profileImageUrl)
.placeholder(R.drawable.ic_baseline_account_circle_24)
.into(targetImageView)
}catch (e:Exception){
Log.d("SubItem","${e.message}")
}
viewHolder.itemView.sub_item_layout.setOnClickListener {
if (selected_position == position){
selected_position = null
viewHolder.itemView.sub_item_layout.setBackgroundResource(R.color.white)
viewHolder.itemView.sub_item_name.setTextColor(R.color.colorSecondaryText)
}
else{
selected_position = position
viewHolder.itemView.sub_item_layout.setBackgroundResource(R.color.colorSecondaryText)
viewHolder.itemView.sub_item_name.setTextColor(R.color.black)
}
}
}
override fun getLayout(): Int {
return R.layout.subscription_item
}
}
If it is helping here is my function where I add the items to the RecyclerView
private fun fetchSubs() {
val uid = auth.uid
val user = database.getReference("/users/$uid/subscriptions")
val adapter = GroupAdapter<GroupieViewHolder>()
user.addListenerForSingleValueEvent(object : ValueEventListener{
#SuppressLint("NotifyDataSetChanged")
override fun onDataChange(p0: DataSnapshot) {
p0.children.forEach{
val sub = it.getValue(Subscription::class.java) ?: return
if (sub.subscribed == true) {
val ref = database.getReference("/channels/${sub.channel_uid}")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val channel = p0.getValue(Channel::class.java) ?: return
adapter.add(SubItem(channel))
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
adapter.setOnItemClickListener{ item, view ->
val subItem = item as SubItem
val channelName = subItem.channel.channel_name
val channelUid = subItem.channel.uid
Toast.makeText(requireContext(),"$channelName : $channelUid", Toast.LENGTH_SHORT).show()
fetchSubs()
}
sub_recyclerview.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
}
sorry that I am not using Models and Adapter
For everyone who uses groupie like me this could help you in future.
This is my solution for my Problem.
SubItem class
class SubItem(val channel: Channel) : Item<GroupieViewHolder>() {
#SuppressLint("ResourceAsColor")
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
val profileImageUrl = channel.channel_logo
viewHolder.itemView.sub_item_name.text = channel.channel_name
val targetImageView = viewHolder.itemView.sub_item_profile
try {
Picasso.get().load(profileImageUrl)
.placeholder(R.drawable.ic_baseline_account_circle_24)
.into(targetImageView)
}catch (e:Exception){
Log.d("SubItem","${e.message}")
// Toast.makeText(,e.message,Toast.LENGTH_SHORT).show()
}
checkFilter(viewHolder,position)
}
#SuppressLint("ResourceAsColor")
private fun checkFilter(v: GroupieViewHolder, p: Int) {
when (SubscriptionsFragment.list[p]) {
true -> {
v.itemView.sub_item_layout.setBackgroundResource(R.color.colorDivider)
v.itemView.sub_item_name.setTextColor(R.color.black)
}
false -> {
v.itemView.sub_item_layout.setBackgroundResource(R.color.white)
v.itemView.sub_item_name.setTextColor(R.color.colorSecondaryText)
}
}
}
override fun getLayout(): Int {
return R.layout.subscription_item
}
}
My function with setOnItemClickListener
private fun fetchSubs() {
val uid = auth.uid
val user = database.getReference("/users/$uid/subscriptions")
val adapter = GroupAdapter<GroupieViewHolder>()
user.addListenerForSingleValueEvent(object : ValueEventListener{
#SuppressLint("NotifyDataSetChanged")
override fun onDataChange(p0: DataSnapshot) {
list = mutableListOf()
p0.children.forEach{
val sub = it.getValue(Subscription::class.java) ?: return
if (sub.subscribed == true) {
val ref = database.getReference("/channels/${sub.channel_uid}")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val channel = p0.getValue(Channel::class.java) ?: return
list.add(false) // created in companion object: var list = mutableListOf<Boolean>()
oldList.add(false) // created in companion object: var oldlist = mutableListOf<Boolean>()
adapter.add(SubItem(channel))
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
adapter.setOnItemClickListener{ item, view ->
val subItem = item as SubItem
val pos = adapter.getAdapterPosition(subItem)
// Here happens the magic
list[pos] = !list[pos] // change selected item from false to true or from true to false
val l = list[pos] // saving Boolean
list = mutableListOf()
oldList.forEach{ // using oldList to loop so many times I need
list.add(false) // setting all to false
}
if (l){ // if Boolean is true
list[pos] = !list[pos] // change selected item from false to true
}
val channelUid = subItem.channel.uid
fetchVideos(channelUid)
adapter.notifyDataSetChanged() // refresh all items in SubItem
}
try {
sub_recyclerview.adapter = adapter
} catch(e:Exception){
Log.d("fetchSubs","${e.message}")
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
There are multiple RecyclerView in my application. Each one consists of the same records, but with different filters.
For example, the first RecyclerView contains new records, the second RecyclerView contains the most popular, etc.
I am trying to get "voices" with different filters. But in the end I get 2 identical lists.
My ViewModel:
private var recentlyAddedVoices = MutableLiveData<List<VoicesModel>>()
private val topFreeVoices = MutableLiveData<List<VoicesModel>>()
private val favExists = MutableLiveData<Boolean>()
private val addToFavoriteResult = MutableLiveData<Boolean>()
val homeVoicesData: MutableLiveData<Pair<List<VoicesModel>?, List<VoicesModel>?>> =
object: MediatorLiveData<Pair<List<VoicesModel>?, List<VoicesModel>?>>() {
var voices: List<VoicesModel>? = null
var freeVoices: List<VoicesModel>? = null
init {
addSource(recentlyAddedVoices) { voices ->
this.voices = voices
voices?.let { value = voices to it }
}
addSource(topFreeVoices) { free ->
this.freeVoices = free
freeVoices?.let { value = freeVoices to it }
}
}
}
fun loadRecentlyAddedVoices(){
REF_DATABASE_ROOT.child(NODE_STICKERS).addValueEventListener(object :
ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val tmpList: MutableList<VoicesModel> = mutableListOf()
for (ds in snapshot.children) {
val voices: VoicesModel? = ds.getValue(VoicesModel::class.java)
voices!!.pushKey = ds.key.toString()
tmpList.add(voices)
}
recentlyAddedVoices.postValue(tmpList)
}
override fun onCancelled(error: DatabaseError) {
}
})
}
fun loadTopFree(){
REF_DATABASE_ROOT.child(NODE_STICKERS).
orderByChild(CHILD_IS_FREE).
equalTo(true).
addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val tmpList: MutableList<VoicesModel> = mutableListOf()
for (ds in snapshot.children) {
val voices: VoicesModel? = ds.getValue(VoicesModel::class.java)
voices!!.pushKey = ds.key.toString()
tmpList.add(voices)
}
topFreeVoices.postValue(tmpList)
}
override fun onCancelled(error: DatabaseError) {
}
})
}
Observe in Fragment:
firebaseViewModel.homeVoicesData.observe(this){ (recentlyAdded, topFree) ->
// recentlyAdded and topFree equals identical value
UpdateUI()
}
I wanted to get a list of data from the realtime database using coroutines and MVVM and put them to recyclerview. It runs but the data from the realtime database are added after the recyclerview.adapter initialization, thus returning list.size to 0
ViewModel.kt
class DasarhukumdetailsViewModel : ViewModel() {
val database = FirebaseDatabase.getInstance().reference
var dasarHukumList = ArrayList<DasarHukum>()
fun getDHData(KEYVALUE: String?) = liveData(Dispatchers.Main.immediate) {
val postListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snapshot in snapshot.children) {
val res = snapshot.getValue(DasarHukum::class.java)
Log.d("dataAdd", "Adding: ${res?.filename}")
dasarHukumList.add(res!!)
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("readDHList", "loadPost:onCancelled", databaseError.toException())
throw databaseError.toException()
}
}
try {
if (KEYVALUE != null) {
database.child("dasarhukum").child(KEYVALUE).addValueEventListener(postListener)
}
emit(Resource.success(dasarHukumList))
} catch (e: Exception) {
emit(Resource.error(
null,
e.message ?: "Unknown Error"
))
}
}
Fragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
[...]
observerSetup(KEYVALUE)
rvSetup()
return binding.root
}
fun observerSetup(keyvalue: String?) {
viewModel.getDHData(keyvalue).observe(viewLifecycleOwner, {
when (it.status) {
Status.SUCCESS -> {
it?.data.let { dhList ->
dasarHukumAdapter.dasarhukumList = dhList
dasarHukumAdapter.notifyDataSetChanged()
}
}
Status.ERROR -> {
Toast.makeText(context, "Error getting documents: ${it.message}", Toast.LENGTH_LONG)
Log.e("realDB", it.message!!)
}
}
})
}
fun rvSetup() {
with(binding.rvDasarHukum) {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
adapter = dasarHukumAdapter
}
}
RVAdapter.kt
class DasarHukumAdapter : RecyclerView.Adapter<DasarHukumAdapter.DasarHukumViewHolder>() {
var dasarhukumList: List<DasarHukum>? = null
set(value) {
notifyDataSetChanged()
field = value
}
class DasarHukumViewHolder(private val binding: ItemDasarhukumBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(dasarHukum: DasarHukum?) {
binding.dasarhukum = dasarHukum
binding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DasarHukumViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemDasarhukumBinding.inflate(layoutInflater, parent, false)
return DasarHukumViewHolder(binding)
}
override fun onBindViewHolder(holder: DasarHukumViewHolder, position: Int) {
val dasarHukum = dasarhukumList?.get(position)
Log.d("dhVH", "Adding: ${dasarHukum?.name}")
holder.bind(dasarHukum)
}
override fun getItemCount(): Int {
Log.d("dhCount", "List size: ${dasarhukumList?.size}")
return dasarhukumList?.size ?: 0
}
How can the recyclerview waits for the viewmodel.getDHData() to returns the arraylist first then initialize so it can be displayed to the recyclerview?
By the time you are trying to emit the result using the following line of code:
emit(Resource.success(dasarHukumList))
The data hasn't finished loading yet, hence the zero size of the list. Firebase API is asynchronous, so you need to wait for the data in order to use it in another operation. So any code that needs data from the Realtime Database needs to be inside the "onDataChange()" method, or be called from there. So the simplest solution, in this case, would be to move the logic regarding the emitting the result, inside the callback:
fun getDHData(KEYVALUE: String?) = liveData(Dispatchers.Main.immediate) {
val postListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snapshot in snapshot.children) {
val res = snapshot.getValue(DasarHukum::class.java)
Log.d("dataAdd", "Adding: ${res?.filename}")
dasarHukumList.add(res!!)
}
try {
if (KEYVALUE != null) {
database.child("dasarhukum").child(KEYVALUE).addValueEventListener(postListener)
}
emit(Resource.success(dasarHukumList))
} catch (e: Exception) {
emit(Resource.error(
null,
e.message ?: "Unknown Error"))
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("readDHList", "loadPost:onCancelled", databaseError.toException())
throw databaseError.toException()
}
}
}
I am getting issues of getting my USER KEY and it returned as null even if there's a username.
Thing is I am just trying to get my username.
I am currently using firebase database
class NewMessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_message)
supportActionBar?.title="Select User"
/*
val adapter = GroupAdapter<ViewHolder>()
adapter.add(UserItem())
adapter.add(UserItem())
adapter.add(UserItem())
new_message_list.adapter = adapter
*/
fetchusers()
}
companion object {
val USER_KEY = "USER_KEY"
}
private fun fetchusers(){
val ref = FirebaseDatabase.getInstance().getReference("/users")
ref.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<ViewHolder>()
p0.children.forEach {
Log.d("NewMessage", it.toString())
val user = it.getValue(User::class.java)
if (user != null){
adapter.add(UserItem(user))
}
}
adapter.setOnItemClickListener { item, view ->
val userItem = item as UserItem
val intent = Intent(view.context, ChatLogActivity::class.java)
intent.putExtra(USER_KEY, userItem.user.username)
startActivity(intent)
finish()
}
new_message_list.adapter = adapter
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
class UserItem(val user: User): Item<ViewHolder>() {
override fun bind(viewHolder: ViewHolder, position: Int){
//list stuff
viewHolder.itemView.username_textview_new.text = user.username
Picasso.get().load(user.profileImageUrl).into(viewHolder.itemView.imageview_new_msg)
}
override fun getLayout(): Int {
return R.layout.user_row_new_message
}
}
This one really frustrated me for hours. I needed this for my chat log title for each person
Maybe I should skip this?
I am just new to android development
Can anyone help?
error in debug