Why does my Recyclerview add new items at random positions? - java

After much reading on how to add an item to the top (or bottom), I realized that list.add(position, item) along with notifyDataSetChanged() is the correct way to add an item at the top (index 0). However, in my code, even after doing that, my RecyclerView adds positions at random places. Below is my CommentsActivity code:
class CommentsActivity : AppCompatActivity() {
private var clickedPostUid = ""
private var currentUserUid = ""
private lateinit var binding_commentsActivity: ActivityCommentsBinding
private val currentUserUID = FirebaseAuth.getInstance().currentUser?.uid!!
private val dbRef = FirebaseDatabase.getInstance().reference
private val uidRef = currentUserUID.let { dbRef.child("Users").child(it) }
private var commentAdapterView: CommentsAdapter? = null
private var commentsList: MutableList<CommentsModel>? = null
var commentsActivityRecyclerView: RecyclerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding_commentsActivity = ActivityCommentsBinding.inflate(layoutInflater)
clickedPostUid = intent.getStringExtra("keyIdentifierPostUid")!!
currentUserUid = intent.getStringExtra("keyIdentifierCurrentUserUid")!!
commentsActivityRecyclerView =
binding_commentsActivity.commentsActivityRecyclerView
val layoutManager = LinearLayoutManager(this)
layoutManager.reverseLayout = true
layoutManager.stackFromEnd = true
commentsActivityRecyclerView!!.layoutManager = layoutManager
commentsList = ArrayList()
commentAdapterView = CommentsAdapter(this, commentsList as ArrayList<CommentsModel>)
commentsActivityRecyclerView!!.adapter = commentAdapterView
commentsActivityRecyclerView!!.setHasFixedSize(true)
LoadAndDisplayComments()
And this is my LoadAndDisplayComments()
private fun LoadAndDisplayComments() {
val commentsRef = dbRef.child("Comments").child(clickedPostUid)
commentsRef.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
TODO("Not yet implemented")
}
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists())
{
commentsList!!.clear()
for (snapshot in p0.children)
{
val getCommentDetails = snapshot.getValue(CommentsModel::class.java)
if (getCommentDetails != null) {
commentsList!!.add(0,getCommentDetails)
}
}
commentAdapterView!!.notifyDataSetChanged()
}
else{
Toast.makeText(this#CommentsActivity, "No comments" , Toast.LENGTH_SHORT).show()
}
}
})
}
This is my comments adapter
class CommentsAdapter(
private var commentsContext : Context,
private var commentsList: MutableList<CommentsModel>
) : RecyclerView.Adapter<CommentsAdapter.CommentsViewHolder>()
{
inner class CommentsViewHolder(#NonNull itemView: View) : RecyclerView.ViewHolder(itemView) {
val showComment : TextView = itemView.comments_recview_item_comment
val showImage : CircleImageView = itemView.comments_recview_item_profile_image
val showUsername : TextView = itemView.comments_recview_item_username
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentsViewHolder {
val itemView = LayoutInflater.from(commentsContext)
.inflate(R.layout.comment_recyclerview_item, parent, false)
return CommentsViewHolder(itemView)
}
override fun getItemCount(): Int {
return commentsList!!.size
}
override fun onBindViewHolder(holder: CommentsViewHolder, position: Int) {
val currentUser = FirebaseAuth.getInstance().currentUser!!
val comment = commentsList!![position]
holder.showComment.text = comment.comment
}
I need that a new comment to be displayed at the top or bottom of the recyclerview. I know how to scroll to 0 index using .SmoothScroll. Would be happy to post the XML if needed. Thank you.

Turns out the error was not in the RecyclerView. It was the way I was storing the values in the database. I was creating a unique Id for my values in Firebase that was causing the error. Instead I used .push to generate unique keys and it worked fine.
Wrong approach (or just not what I wanted):
val uniqueCommentId = UUID.randomUUID().toString()
.replace("-","").toUpperCase(Locale.ROOT)
val postCommentsRef = FirebaseDatabase.getInstance().reference
.child("Comments")
.child(postId).child(uniqueCommentId )
postCommentRef.setValue()
Better approach:
val postCommentsRef = FirebaseDatabase.getInstance().reference
.child("Comments")
.child(postId)
val commentMap = HashMap<String, Any>()
commentMap["userUid"] = currentUserUid
commentMap["postId"] = postId
commentMap["comment"] = comment
postCommentsRef.push().setValue(commentMap)
Worked fine for me after this.

Related

Error Updating Recycler View Item Button Visibility outside Adapter

My goal is to have button inside each RecyclerView item that will appear on click so Im changing the buttons visibility. On the first click it works fine but on clicking again or in any other item it crashes the app.
This is my Adapter.kt
class Adapter (private val orders:ArrayList<Order>, private val listener : OnItemClickListener) : RecyclerView.Adapter<Adapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_pedido,parent,false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = orders[position]
holder.nrPedido.text = currentItem.id.toString()
holder.distancia.text = currentItem.distancia.toString()+"km"
holder.estado.text = currentItem.estado
}
override fun getItemCount(): Int {
return orders.size
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener{
val nrPedido : TextView = itemView.findViewById(R.id.textViewIDPedido)
val distancia : TextView = itemView.findViewById(R.id.textViewDistancia)
val estado : TextView = itemView.findViewById(R.id.textViewEstado)
val buttonEstado : Button = itemView.findViewById(R.id.buttonEstado)
val buttonAceitarRecusar : Button = itemView.findViewById(R.id.buttonAceitarRecusar)
val buttonDados : Button = itemView.findViewById(R.id.buttonDados)
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val holder = ViewHolder(itemView)
val order = orders[adapterPosition]
val position = adapterPosition
if(position != RecyclerView.NO_POSITION && order != null) {
listener.onItemClick(order,position,holder)
}
}
}
interface OnItemClickListener{
fun onItemClick(order: Order,position: Int,holder: ViewHolder)
}
}
This Is my mainActivity where the onItemClick Method is being called
override fun onItemClick(order: Order,position:Int,holder: Adapter.ViewHolder) {
visible = visible?.not()
if(arrayListPedidos.contains(order)){
val clickedItem = arrayListPedidos[position]
if(visible==true){
holder.buttonDados.visibility = View.VISIBLE
}else{
holder.buttonDados.visibility = View.INVISIBLE
}
}else if(arrayListMeusPedidos.contains(order)){
val clickedItem = arrayListMeusPedidos[position]
if(visible==true){
holder.buttonDados.visibility = View.VISIBLE
}else{
holder.buttonDados.visibility = View.INVISIBLE
}
}
}
I think it could be something to do with not notifying that the item is being updated, but it could also be the way im sending the holder.
When I click one time on the item it changed the visibility to visible. If I click again (to make it invisible) or if I click in any other item it crashed the app.
Think about the ViewHolder as a highly volatile view, you don't want to keep any references about it that you use outside the Adapter – I see that you pass the ViewHolder back into the MainActivity if I understand correctly what you are trying to achieve could be done by doing something like this:
class Adapter(
private val orders: ArrayList<Order>,
// Note: Modern callbacks are declared like this, don't worry about passing the
// OnItemClickListener
private val listener: ((Order) -> Unit)
) : RecyclerView.Adapter<ViewHolder> { // Note: Try to use your own ViewHolders
// In situations like this you can use a map or some other data structure to keep track of what
// order button is supposed to be visible or not. You could also keep track of this in the Order
// class itself.
private val isVisibleMap = mutableMapOf<Int, Boolean>().apply {
orders.forEachIndexed { index, _ -> put(index, false) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_pedido,parent,false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = orders[position]
// You must set the visibility during the binding
holder.buttonDados.visibility = if (isVisibleMap[position]) {
View.VISIBLE
} else {
View.GONE
}
holder.nrPedido.text = currentItem.id.toString()
holder.distancia.text = currentItem.distancia.toString()+"km"
holder.estado.text = currentItem.estado
holder.itemView.setOnClickListener {
// Here we update the visibility
isVisibleMap[position] = !isVisibleMap[position]
listener.invoke(currentItem)
}
}
override fun getItemCount(): Int {
return orders.size
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
val nrPedido : TextView = itemView.findViewById(R.id.textViewIDPedido)
val distancia : TextView = itemView.findViewById(R.id.textViewDistancia)
val estado : TextView = itemView.findViewById(R.id.textViewEstado)
val buttonEstado : Button = itemView.findViewById(R.id.buttonEstado)
val buttonAceitarRecusar : Button = itemView.findViewById(R.id.buttonAceitarRecusar)
val buttonDados : Button = itemView.findViewById(R.id.buttonDados)
}
}
Keep in mind that things like this, changing the visibility of an item from the adapter, should be handled within the adapter itself. Hope it helps!
Edit: If you want to change the button visibility programmatically from outside the Adapter, you can always do something like:
class Adapter ... {
fun changeButtonVisibility(isVisible: Boolean, pos: Int) {
isVisibleMap[pos] = isVisible
notifyItemChanged(pos)
}
}

How to pass a list of objects from adapter to activity via broadcast receiver in kotlin

I have a situation where I need to pass a list from adapter to activity, then pass that same list from activity to another adapter. So, I am passing a list from adapter to activity like this, and it works.
if(filteredList.isNotEmpty()){
var json = Gson().toJson(filteredList)
intentPassList.putExtra("filteredList", json)
LocalBroadcastManager.getInstance(holder.context).sendBroadcast(intentPassList)
}
Next, I am receiving my list successfully like this in my activity class
private val receiverFilteredList = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
var recList = intent.getStringExtra("filteredList")
if (recList != null){
var token: TypeToken<ArrayList<Model?>?> = object : TypeToken<ArrayList<Model?>?>() {}
myFilteredList = Gson().fromJson(recList, token.type) // list is received correctly
}
}
}
Now I need to pass this received list to another adapter (recycler view) and my idea was to do this in onCreate in my activity
private var myFilteredList: ArrayList<Model> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LocalBroadcastManager.getInstance(this).registerReceiver(receiverFilteredList, IntentFilter("filteredList"))
if (myFilteredList?.isNotEmpty() == true){
adapter = NewAdapter(myFilteredList!!)
myRecycler?.layoutManager = manager
myRecycler?.adapter = adapter
} else {
adapterInfo = NewAdapter(completeList)
myRecycler?.layoutManager = manager
myRecycler?.adapter = adapter
}
}
}
but it always sends completeList to adapter, because myFilteredList is null. How can i correctly pass received list in receiver to myFilteredList in onCreate?
NewAdapterClass
class NewAdapter(var itemList: ArrayList<Model>) :
RecyclerView.Adapter<NewAdapter.ViewHolder>() {
private lateinit var getContext: Context
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
var x: TextView = view.txtX
val y: TextView = view.txtY
val z: TextView = view.txtZ
val localPreferances = PreferencesManager()
val context: Context = view.context
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
getContext = parent.context
val layoutView = LayoutInflater.from(parent.context).inflate(R.layout.listview_item_list_layout, parent, false)
return ViewHolder(layoutView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var receivedList = itemList[position]
if (receivedList?.dataA != null) {
holder.y?.text = "${receivedList?.dataA}"
} else holder.y?.text = ""
if (receivedList?.dataB != null) {
holder.x?.text = "${receivedList?.dataB}"
} else holder.x?.text = ""
if (receivedList?.dataC != null) {
holder.z?.text = "${receivedList?.dataC}"
} else holder.z?.text = ""
}
override fun getItemCount() = itemList.size
}
You need to inform Adapter about changes to it's data source which is List in your case, also myFilteredList = Gson().fromJson(recList, token.type) will create new instance of ArrayList and assign it to myFilteredList, but the initial instance provided to your adapter was different
private val receiverFilteredList = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
var recList = intent.getStringExtra("filteredList")
if (recList != null){
var token: TypeToken<ArrayList<Model?>?> = object : TypeToken<ArrayList<Model?>?>() {}
myFilteredList.clear() //clear existing data
myFilteredList.addAll(Gson().fromJson(recList, token.type)) // add new data
adapter.notifyDataSetChanged() //reset the list
}
}
}
I had to change approach, broadcast receiver wasn't working for me. I sent the list (Gson().toJson) from adapter via Intent.putExtra, and in the activity I received it like this
recList= intent.getStringExtra("filteredList")
if (recList!= null){
var token: TypeToken<ArrayList<Model?>?> = object : TypeToken<ArrayList<Model?>?>() {}
myFilteredList?.clear()
myFilteredList= Gson().fromJson(recList, token.type)
}
Now everything is working fine.

ProgressBar not updating in RecyclerView item row

I'm uploading some photos to my server, and want to view the progress of individual uploads. However, my RecyclerView isn't updating.
I know the progress is being updated, as I can see the progress in the my upload task, and the photo is uploaded to the server. I thought the viewholder would be what I would use to update the view, but my progress listener isn't' called.
Does anyone see what I have done wrong? Or could do better?
The PhotoUploadItem is the object that gets updated with the progress.
class PhotoUploadItem(var photoStore: PhotoStore) {
interface PhotoUploadProgressListener {
fun onProgressChanged(progress: Int)
}
private var uploadProgress = 0
private var listener: PhotoUploadProgressListener? = null
fun setUploadProgress(uploadProgress: Int) {
this.uploadProgress = uploadProgress
listener?.onProgressChanged(uploadProgress)
}
fun subscribe(listener: PhotoUploadProgressListener) {
this.listener = listener
}
fun unsubscribe() {
this.listener = null
}
fun progress() = uploadProgress
fun identifier(): Int = photoStore.key
}
The PhotoUploadsAdapter takes a list of them.
class PhotoUploadsAdapter : RecyclerView.Adapter<PhotoUploadsAdapter.CustomViewHolder>() {
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<PhotoUploadItem>() {
override fun areItemsTheSame(oldItem: PhotoUploadItem, newItem: PhotoUploadItem) = oldItem.identifier() == newItem.identifier()
override fun areContentsTheSame(oldItem: PhotoUploadItem, newItem: PhotoUploadItem) = oldItem.hashCode() == newItem.hashCode()
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_photo_upload, parent, false))
}
override fun getItemCount() = differ.currentList.size
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val item = differ.currentList[position]
holder.itemView.apply {
///..... set other fields
tvDisplayName?.text = item.photoStore.displayName
// THIS DOESN'T WORK
item.subscribe(object : PhotoUploadItem.PhotoUploadProgressListener{
override fun onProgressChanged(progress: Int) {
Log.i("UploadViewHolder", "${item.identifier()} has progress: $progress")
progressBar?.progress = progress
tvProgressAmount?.text = String.format(Locale.ROOT, "%d%%", progress)
}
})
}
}
}

How to make Recyclerview items clickable?

I am trying to make a notes app which will allows the user make simple notes and save it. I used realm database to save the users notes. The notes are showing up fine in the recyclerview but I cannot click them.
What I want to happen is, When one of the notes in the recyclerview is clicked I want to start a new acitivty.
adapter
class NotesAdapter(private val context:Context, private val notesList: RealmResults<Notes>):RecyclerView.Adapter<RecyclerView.ViewHolder>(){
private lateinit var mOnClickListener:View.OnClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.notes_rv_layout,parent,false)
v.setOnClickListener(mOnClickListener)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return notesList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.titleTV.text = notesList[position]!!.title
holder.itemView.descTV.text = notesList[position]!!.description
holder.itemView.idTV.text = notesList[position]!!.id.toString()
}
private fun onClick(view:View) {
}
class ViewHolder(v: View?):RecyclerView.ViewHolder(v!!){
val title = itemView.findViewById<TextView>(R.id.titleTV)
val desc = itemView.findViewById<TextView>(R.id.descTV)
val id = itemView.findViewById<TextView>(R.id.idTV)
}
}
main activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//init views
realm = Realm.getDefaultInstance()
addNotes = findViewById(R.id.addNotes)
notesRV = findViewById(R.id.NotesRV)
//onclick add notes button
addNotes.setOnClickListener {
val intent = Intent(this, AddNotesActivity::class.java)
startActivity(intent)
finish()
}
notesRV.layoutManager = StaggeredGridLayoutManager(2,LinearLayoutManager.VERTICAL)
getAllNotes()
}
private fun getAllNotes() {
notesList = ArrayList()
val results:RealmResults<Notes> = realm.where<Notes>(Notes::class.java).findAll()
notesRV.adapter = NotesAdapter(this, results)
notesRV.adapter!!.notifyDataSetChanged()
}
}
use onBindViewHolder for that:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.titleTV.text = notesList[position]!!.title
holder.itemView.descTV.text = notesList[position]!!.description
holder.itemView.idTV.text = notesList[position]!!.id.toString()
holder.itemView.setOnClickListener{
val intent = Intent(holder.itemView.context,YourActivity::class.java)
holder.itemView.context.startActivity(intent)
}
}

How to enable next item in RecyclerView?

I want the next item in the RecyclerView can be clicked. For example, if item 1 has score > 10, then the item 2 can be clicked, but the item 3,4,5,6... cannot be clicked. If item 2 has scrore > 10,then the item 3 can be clicked,but the item 4,5,6,7... cannot be clicked. If item 3 has score > 10, then the item 4 can be clicked, but the item 5,6,7,8... cannot be clicked.
class RecyclerView
class MathCategoryDetailActivity : AppCompatActivity() {
private var progressBar: ProgressBar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_math_category_detail)
progressBar = findViewById(R.id.progressBar)
recycler_math_category_detail.layoutManager = GridLayoutManager(this, 2)
recycler_math_category_detail.setHasFixedSize(true)
val adapter = MathCategoryDetailAdapter(
this,
DBHelperOther.getInstance(this).allCategoryDetail(Common.selectedCategory!!.id)
)
Common.selectedCategory!!.id
recycler_math_category_detail.addItemDecoration(
SplacesItemDecoration(
4
)
)
recycler_math_category_detail.adapter = adapter
}
}
Adapter of RecyclerView
class MathCategorySpecifyDetailAdapter(
internal var context: Context,
internal var categoryDetailList: List<MathCategorySpecifyDetail>) :
RecyclerView.Adapter<MathCategorySpecifyDetailAdapter.MyViewHolder1>() {
private var dialog: Dialog? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder1 {
val itemView =
LayoutInflater.from(context).inflate(R.layout.activity_math_category_specify_detail_adapter, parent, false)
return MyViewHolder1(itemView)
}
override fun getItemCount(): Int {
return categoryDetailList.size
}
override fun onBindViewHolder(holder: MyViewHolder1, position: Int) {
holder.card_category_detail_2.isEnabled = false
holder.txt_category_specify_detail_2_name.isEnabled = false
//if(the id =1 , item can be clicked)
if(categoryDetailList[position].id == 1) {
holder.card_category_detail_2.isEnabled = true
holder.txt_category_specify_detail_2_name.isEnabled = true
if(categoryDetailList[position].highScore> 10) {
//I added code here, but it doens't work
categoryDetailList[position].id++
holder.card_category_detail_2.isEnabled = true
}
}
holder.txt_category_specify_detail_2_name.text = categoryDetailList[position].name
holder.score.text = categoryDetailList[position].highScore.toString()
dialog = Dialog(context)
dialog!!.setContentView(R.layout.activity_lesson)
holder.card_category_detail_2.setOnClickListener {
var txtLesson: TextView = dialog!!.findViewById(R.id.txtLesson)
txtLesson.text = categoryDetailList[position].lesson
var txtDescribe: TextView = dialog!!.findViewById(R.id.txtDescribe)
txtDescribe.text = categoryDetailList[position].nameOfTypeDetail
var txtType: TextView = dialog!!.findViewById(R.id.txtType)
txtType.text = categoryDetailList[position].mathCategoryDetail
var btnPlay: Button =dialog!!.findViewById(R.id.btnPlay)
btnPlay.setOnClickListener{
Common2.selectedCategoryId = categoryDetailList[position]
val intent = Intent(context, QuestionActivity::class.java)
context.startActivity(intent)
}
var window : Window? = dialog!!.window
window!!.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
dialog!!.show()
}
}
inner class MyViewHolder1(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
internal val txt_category_specify_detail_2_name: TextView
internal val score: TextView
internal val card_category_detail_2: CardView
internal lateinit var iOnRecyclerViewItemClickListener2: IOnRecyclerViewItemClickListener2
fun setiOnRecyclerViewItemClickListener2(iOnRecyclerViewItemClickListener2: IOnRecyclerViewItemClickListener2) {
this.iOnRecyclerViewItemClickListener2 = iOnRecyclerViewItemClickListener2
}
init {
txt_category_specify_detail_2_name =
itemView.findViewById(R.id.txt_category_specify_detail_2_name) as TextView
score = itemView.findViewById(R.id.score) as TextView
card_category_detail_2 = itemView.findViewById(R.id.card_category_detail_2) as CardView
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
iOnRecyclerViewItemClickListener2.onClick(view, adapterPosition)
}
}
}
MathCategorySpecifyDetail
class MathCategorySpecifyDetail(
var id: Int,
var idMathCategoryDetail: Int,
var name: String,
var lesson: String,
var nameOfTypeDetail: String,
var mathCategoryDetail: String,
var highScore: Int
)
Common1
object Common1 {
var selectedCategoryIdDetail: MathCategoryDetail? = null
}
I have tried in many ways but it doesn't work. Please help me
Try creating a method inside the adapter that will enable/disable the items.
For example:
fun toggleEnabled(enabled: Boolean){
//enable or disable the items according to the score
}
Use RxJava or LiveData for observing the score. It will check and trigger the toggleEnabled() every time when the score is changed.
p.s. You can get items by position (f.e. if the score is between 10 and 20 you will get the item with index of 1 from your adapter and call toggleEnabled() for that item.)

Categories