holder.bindingAdapterPosition returns -1 - android

binding adapter position is returning -1 and i cannot figure out why, anyone has an idea of what i could be doing wrong ?
holder.bindingAdapterPosition in onCreateViewHolder.
Adapter and viewHolder
class CommentAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var likeClick: LikeClick? = null
private var disLikeClick: DisLikeClick? = null
inner class CommentHolder(view: View) : RecyclerView.ViewHolder(view) {
val nicknameTextView: TextView = itemView.findViewById(R.id.nickname_text_view)
val commentTextView: TextView = itemView.findViewById(R.id.comment_text_view)
val likeCount: TextView = itemView.findViewById(R.id.like_count_text_view)
val disLikeCount: TextView = itemView.findViewById(R.id.dislike_count_text_view)
val likeBtn: ImageButton = itemView.findViewById(R.id.like_image_btn)
val disLikeBtn: ImageButton = itemView.findViewById(R.id.disLike_Image_btn)
}
fun setLikeClickListener(clickListener: LikeClick) {
likeClick = clickListener
}
fun setDisLikeClickListener(clickListener: DisLikeClick) {
disLikeClick = clickListener
}
private val diffCallback = object : DiffUtil.ItemCallback<Comment>() {
override fun areItemsTheSame(oldItem: Comment, newItem: Comment): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Comment, newItem: Comment): Boolean {
return oldItem.id == newItem.id
}
}
private val diff = AsyncListDiffer(this, diffCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_comment, parent, false)
val holder: RecyclerView.ViewHolder = CommentHolder(view)
//set on click listener for like and dislike
Log.d("TESTLOG",diff.currentList.toString())
//TODO FIX
val commentItem = diff.currentList[holder.bindingAdapterPosition]
val likeBtn=view.findViewById<ImageButton>(R.id.like_image_btn)
val dislikeBtn=view.findViewById<ImageButton>(R.id.disLike_Image_btn)
likeBtn.setOnClickListener {
likeClick?.onLikeClick(commentItem.id,likeBtn)
}
dislikeBtn.setOnClickListener {
disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn)
}
return holder
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val commentItem = diff.currentList[position]
val videoHolder = holder as CommentHolder
videoHolder.apply {
commentTextView.text = commentItem.comment
nicknameTextView.text = commentItem.nickname
likeCount.text = commentItem.commentlike.toString()
disLikeCount.text = commentItem.dislike.toString()
//check if user already liked/disliked this comment and change icon based on that
Utils.likes.forEach {
if(it == commentItem.id){
likeBtn.isSelected=true
}
}
Utils.disLikes.forEach {
if(it==commentItem.id){
disLikeBtn.isSelected=true
}
}
}
}
override fun getItemCount(): Int {
return diff.currentList.size
}
fun submitList(list: List<Comment>) {
diff.submitList(list)
}
interface LikeClick {
fun onLikeClick(commentId: Int, likeBtn:ImageButton)
}
interface DisLikeClick {
fun onDisLikeClick(commentId: Int, disLikeBtn:ImageButton)
}
}
Log.d in oncreateViewHolder
Shows what diff.currentlist contains.
D/TESTLOG: [Comment(comment=firstcomment
, commentlike=0, createdAt=2022-07-06T04:35:43.000Z, dislike=0, id=4, nickname=Alpac, videoId=170c3d0c-331f-4da9-974b-39703940f819)]
Where i set the adapter
i submit a list of comments to adapter, set like and dislike listeners then apply adapter to recyclerView
ChikiCommentsFetcher().fetchCommentsOfAVideo(videoId).observe(viewLifecycleOwner){
commentsRecyclerView.apply {
commentAdapter.submitList(it)
commentAdapter.setLikeClickListener(this#CommentsFragment)
commentAdapter.setDisLikeClickListener(this#CommentsFragment)
adapter=commentAdapter
}
}

Holder positions should be handled inside the onBindViewHolder method.
Also, move your clicks to the onBindViewHolder method.
holder.likeBtn.setOnClickListener {
likeClick?.onLikeClick(commentItem.id,likeBtn)
}
holder.dislikeBtn.setOnClickListener {
disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn)
}
check out this RecyclerView Tutorial

Related

How to use pagination in Android

In my application I want create chat application and for show messages list I used PagingLibrary (version 3) for RecyclerView.
I can show messages and add new item, but when added new item replace this new item with previous item. For example, this is messages list:
Hi
How are you?
I'm android developer.
You?
When send new message for example "My job is ...", replace this item instead of "You?" Such as below:
Hi
How are you?
I'm android developer.
My job is ...
But should show such as below:
Hi
How are you?
I'm android developer.
You?
My job is ...
My adapter codes:
class ChatAdapter #Inject constructor() : PagingDataAdapter<Result, ChatAdapter.ViewHolder>(differCallback) {
private lateinit var binding: ItemChatBinding
private val userToken by lazy { GoodPrefs.getInstance().getString(USER_TOKEN, "") }
private lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false)
context = parent.context
return ViewHolder()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!)
//holder.setIsRecyclable(false)
}
inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {
val root = itemView.findViewById(R.id.root) as ConstraintLayout
private val meTxt = itemView.findViewById(R.id.meTxt) as TextView
private val doctorTxt = itemView.findViewById(R.id.doctorTxt) as TextView
#SuppressLint("SetTextI18n")
fun bind(item: Result) {
//Message
if (item.message != null) {
meTxt.text = item.message
meTxt.isVisible = true
doctorTxt.text = item.message
doctorTxt.isVisible = true
} else {
meTxt.isVisible = false
doctorTxt.isVisible = false
}
}
}
fun addNewItem(position: Int, item: Result) {
snapshot()[position]?.message = item.message
notifyItemChanged(position)
}
companion object {
private val differCallback = object : DiffUtil.ItemCallback<Result>() {
override fun areItemsTheSame(oldItem: Result, newItem: Result): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Result, newItem: Result): Boolean {
return oldItem == newItem
}
}
}
}
Fragment codes:
//RecyclerView
chatLayoutManager.reverseLayout = true
chatList.apply {
layoutManager = chatLayoutManager
setHasFixedSize(true)
itemAnimator = null
adapter = chatAdapter.withLoadStateHeaderAndFooter(
header = LoadMoreAdapter { chatAdapter.retry() },
footer = LoadMoreAdapter { chatAdapter.retry() }
)
}
pusherChannel.bind(PUSHER_EVENT_NAME, object : PrivateChannelEventListener {
#SuppressLint("NotifyDataSetChanged")
override fun onEvent(event: PusherEvent) {
val newChat = ResponseChatList.Data.Result(
message = pusherGsonData.message, deliveredAt = pusherGsonData.deliveredAt,
toUserId = toUserData, chatFile = chatFile
)
//Update UI
runOnUiThread {
chatAdapter.addNewItem(chatDataList.indices.first, newChat)
chatList.smoothScrollToPosition(chatDataList.indices.first)
}
How can I fix this problem?

How to limit selectable checkboxes to 3 in recycler view

I have a recycler view which has checkboxes as items. I want to make sure there cannot be selected more than 3 checkboxes. If 3 checkboxes are selected other checkboxes should not be selectable, only selected checkboxes should be able to be deselected. I am not sure if I need to implement that behaviour in adapter or fragment which holds adapter. This is how my adapter looks like:
class HomeAdapter(
private val listener: OnCheckBoxClickListener
) : ListAdapter<String, HomeAdapter.SurveyViewHolder>(HOME_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SurveyViewHolder {
val bind = ItemHomeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SurveyViewHolder(bind)
}
override fun onBindViewHolder(holder: SurveyViewHolder, position: Int) {
getItem(position)?.let { holder.bind(it) }
}
inner class SurveyViewHolder(
val binding: ItemHomeBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(answer: String) = with(binding) {
checkBox.text = answer
}
init {
binding.checkBox.setOnCheckedChangeListener { _, isChecked ->
listener.onCheckBoxClickListener(
index = bindingAdapterPosition,
isChecked = isChecked,
answer = currentList[bindingAdapterPosition]
)
}
}
}
interface OnCheckBoxClickListener {
fun onCheckBoxClickListener(index: Int, isChecked: Boolean, answer: String)
}
companion object {
private val HOME_COMPARATOR = object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean =
oldItem == newItem
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean =
oldItem == newItem
}
}
}
So far, I have tried something like this in fragment:
private fun countCheckedCheckBoxes() = with(binding) {
var num = 0
for (i in 0..recyclerView.childCount) {
(recyclerView.getChildAt(i) as? CheckBox).also {
if (it?.isChecked == true) {
num += 1
}
}
}
if (num == 3) {
// TODO do something here
}
}

How to get selected item in RecyclerView without using onClick method in Kotlin

I need to display some information in Activity from Adapter but without using onClick method - I want to get selected item at any time without click on the item (e.g. when the activity is open, first item is selected (different background color) and I need to show some data from this item. I have similiar question here How to display item info on selected item in RecyclerView using Kotlin but there is List as output of class. I have ListAdapter here in this example.
I know I could use getLayoutManager().findFirstVisibleItemPosition() method in your RecyclerView or getAdapterPosition() at the Adapter but I don't know how.
class ExampleAdapter(var context: Context?, private val listener: OnItemClickListener): ListAdapter<ExampleTuple, ExampleAdapter.ItemViewHolder>(DiffCallback()) {
private var selectedItemPosition: Int = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val binding = RecyclerViewItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ItemViewHolder(binding)
}
//fun getCurrentItem(): ExampleTuple = currentList[selectedItemPosition]
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val currentItem = getItem(position)
if (currentItem != null) {
holder.bind(currentItem)
}
if (selectedItemPosition == position){
holder.itemView.setBackgroundColor(Color.parseColor("#DA745A"))
} else {
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
inner class ItemViewHolder(private val binding: DokladyItemBinding): RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener{
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION){
val item = getItem(position)
if (item != null){
listener.onItemClick(item)
}
}
notifyItemChanged(selectedItemPosition)
selectedItemPosition = absoluteAdapterPosition
notifyItemChanged(selectedItemPosition)
}
binding.root.setOnLongClickListener{
val positionLong = bindingAdapterPosition
if (positionLong != RecyclerView.NO_POSITION){
val itemLong = getItem(positionLong)
if (itemLong != null){
listener.onLongClick(itemLong)
}
}
true
}
}
fun bind(exampleItem: ExampleTuple){
binding.apply {
tvDOKL.text = exampleItem.doklad.toString()
tvORG.text = exampleItem.odj.toString()
tvDATE.text = exampleItem.datuct.toString()
}
}
}
interface OnItemClickListener{
fun onItemClick(exampleItem: ExampleTuple)
fun onLongClick(exampleItem: ExampleTuple)
}
class DiffCallback: DiffUtil.ItemCallback<ExampleTuple>(){
override fun areItemsTheSame(oldItem: ExampleTuple, newItem: ExampleTuple) =
oldItem.doklad == newItem.doklad
override fun areContentsTheSame(oldItem: ExampleTuple, newItem: ExampleTuple) =
oldItem == newItem
}
}
I use binding in Activity
binding.apply {
recyclerView.apply {
adapter = exampleAdapter
layoutManager = LinearLayoutManager(this#MainActivity)
}
exampleViewModel.getAll(idExample).observe(this#MainActivity){
dokladAdapter.submitList(it)
}
}

Error when initialize viewHolder in RecyclerView adapter

How should I initialize viewHolder? I have this error:
What I need to do is to get selected item in recyclerView but without using onClick method. When I get this selected item I need to show Toast message. Item is data class. Is it possible to pass some value from adapter to activity? Like I need to pass actual items from Data Class.
Process: com.pors.coopreaderlast, PID: 7862
kotlin.UninitializedPropertyAccessException: lateinit property viewHolder has not been initialized
at com.pors.coopreaderlast.features.polozka.PolozkaAdapter.getViewHolder(PolozkaAdapter.kt:18)
at com.pors.coopreaderlast.features.polozka.PolozkaAdapter.getCurrentItem(PolozkaAdapter.kt:46)
at com.pors.coopreaderlast.features.polozka.PolozkaActivity.onStart(PolozkaActivity.kt:213)
this is for line where viewHolder is set in Adapter:
lateinit var viewHolder: PolozkaViewHolder
This is Adapter
class PolozkaAdapter(val chosen_item: Int, private val listener: OnItemClickListener): ListAdapter<Polozka, PolozkaAdapter.PolozkaViewHolder>(DiffCallback()){
var selectedItemPosition: Int = chosen_item
lateinit var viewHolder: PolozkaViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PolozkaViewHolder {
val binding = PolozkyItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
//return PolozkaViewHolder(binding)
viewHolder = PolozkaViewHolder(binding)
return viewHolder
}
override fun onBindViewHolder(holder: PolozkaViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
if (selectedItemPosition == position){
holder.itemView.setBackgroundColor(Color.parseColor("#DA745A"))
} else
{
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
fun getCurrentItem(): Polozka = super.getItem(viewHolder.bindingAdapterPosition)
override fun getItemId(position: Int): Long {
return super.getItemId(position)
}
override fun getItemCount(): Int {
return super.getItemCount()
}
inner class PolozkaViewHolder(private val binding: PolozkyItemBinding): RecyclerView.ViewHolder(binding.root){
init {
binding.root.setOnClickListener{
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION){
val item = getItem(position)
if (item != null){
listener.onItemClick(item, position)
}
}
notifyItemChanged(selectedItemPosition)
selectedItemPosition = bindingAdapterPosition
notifyItemChanged(selectedItemPosition)
}
}
fun bind(polozkaPolozka: Polozka){
binding.apply {
tvREG.text = polozkaPolozka.reg
tvVB.text = polozkaPolozka.veb.toString()
}
}
}
interface OnItemClickListener{
fun onItemClick(polozkaDoklad: Polozka, position: Int)
}
class DiffCallback: DiffUtil.ItemCallback<Polozka>(){
override fun areItemsTheSame(oldItem: Polozka, newItem: Polozka) =
oldItem.pvp06pk == newItem.pvp06pk
override fun areContentsTheSame(oldItem: Polozka, newItem: Polozka) =
oldItem == newItem
}
}
This is onCreate method but it can be in onCreate method also.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityPolozkaBinding.inflate(layoutInflater)
idPositionItem = intent.getIntExtra("positionItem",0)
val itemAdapter = PolozkaAdapter(idPositionItem, this)
binding.apply {
recyclerView.apply {
adapter = itemAdapter
layoutManager = LinearLayoutManager(this#ItemActivity)
}
itemViewModel.getall(index,idExp.toString() ).observe(this#PolozkaActivity){
itemAdapter.submitList(it)
}
}
val selectedItem = itemAdapter.getCurrentItem()
Toast.makeText(this, "Reg vybrane polozky je ${selectedItem.reg}", Toast.LENGTH_LONG).show()
I have similar question here: Similar question but here I use binding.
The reason you're getting this exception is that viewHolder has not been initalized at the moment you want to access it. And since it's a lateinit var, it expects to be initialized every time you access it. (see https://kotlinlang.org/docs/properties.html#late-initialized-properties-and-variables)
Instead of using a lateinit var for viewHolder, you can return the instance you created in the onCreateViewHolder() so no need to have an extra field in the adapter for it.
I believe you use viewHolder to find the selected item. In that case I suggest using a boolean (for instance you can name it selected) in your model object Polozka to indicate if the item is selected or not. And in you getCurrentItem() method I would write getCurrentList().find { it.selected } to find the currently selected item. In this case you need to update your list every time you select a new item and mark only that as selected.
class PolozkaAdapter(private val listener: OnItemClickListener): ListAdapter<Polozka, PolozkaAdapter.PolozkaViewHolder>(DiffCallback()){
lateinit var viewHolder: PolozkaViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PolozkaViewHolder {
val binding = PolozkyItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PolozkaViewHolder(binding)
}
override fun onBindViewHolder(holder: PolozkaViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
if (currentItem.selected){
holder.itemView.setBackgroundColor(Color.parseColor("#DA745A"))
} else
{
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
fun getCurrentItem(): Polozka = currentList.find { it.selected }
override fun getItemId(position: Int): Long {
return super.getItemId(position)
}
override fun getItemCount(): Int {
return super.getItemCount()
}
//Model object would look like
data class Polozka(
val selected: Boolean,
//rest of the fields
)
And in the observe you should do it like this.
itemViewModel.getall(index,idExp.toString() ).observe(this#PolozkaActivity){
// mark the item selected
val updatedList = it.mapIndexed { index, item ->
if (index == idPositionItem) {
item.copy(selected = true)
} else {
item
}
}
itemAdapter.submitList(updatedList)
}
}
val selectedItem = itemAdapter.getCurrentItem()
Toast.makeText(this, "Reg vybrane polozky je ${selectedItem.reg}", Toast.LENGTH_LONG).show()
And every time a new item gets selected you need to update your list.

RecyclerView Duplicates (Insert)

In my app, I am trying to add an item to my recycling view. When I add 1 item and call notifyDataSetChanged () there is no problem, only when I want to add a 2nd item and then call the same method the two added items appear 2 times. If I add a 3rd item the 1st added item still appears 2 times the 2nd item now appears 3 times and the 3rd added item also appears 3 times. I'm working with an adapter class for my recyclerview.
I've tried working with NotifyItemInserted(position-1) but it did not work.
Here are some snippets of my code:
My adapter class
class CategoriesAdapter(var clickListener: CategorieListener): ListAdapter<CategorieModel, CategoriesAdapter.CategorieViewHolder>(CategorieDiffCallback()) {
override fun getItemCount(): Int {
return super.getItemCount()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategorieViewHolder {
return CategorieViewHolder.from(parent)
}
override fun onBindViewHolder(holder: CategorieViewHolder, position: Int) {
val reis = getItem(position)
holder.bind(reis, clickListener, position)
}
class CategorieViewHolder private constructor(val binding: LayoutCategorieItemBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(categorie: CategorieModel, clickListener: CategorieListener, position: Int?){
binding.categorie = categorie
var card = binding.catCard
itemView.setOnClickListener {
clickListener.onClick(categorie, card)
}
itemView.ib_delete.setOnClickListener {
clickListener.onDeleteClick(categorie, position!!)
}
binding.catCard.transitionName = categorie.naam
}
companion object {
fun from(parent:ViewGroup) : CategorieViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = LayoutCategorieItemBinding.inflate(layoutInflater, parent, false)
return CategorieViewHolder(binding)
}
}
}
class CategorieDiffCallback : DiffUtil.ItemCallback<CategorieModel>() {
override fun areItemsTheSame(oldItem: CategorieModel, newItem: CategorieModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: CategorieModel, newItem: CategorieModel): Boolean {
return oldItem == newItem
}
}
class CategorieListener(val clicklistener: (categorie: CategorieModel, view:MaterialCardView?, position:Int?) -> Unit){
fun onClick(categorie: CategorieModel, view: MaterialCardView) = clicklistener(categorie, view, 0)
fun onDeleteClick(categorie: CategorieModel, position: Int) = clicklistener(categorie, null, position)
}
}
My Fragment behind the scenes
private fun setAdapter() {
val adap = CategoriesAdapter(CategoriesAdapter.CategorieListener{ categorie, cardview, position ->
if(cardview == null){
deleteCategorie(categorie, position!!)
}else{
findNavController().navigate(CategoriesFragmentDirections.actionCategoriesFragmentToItemTakenFragment(categorie.naam,categorie))
}
})
binding.categories.apply {
layoutManager = LinearLayoutManager(this.context)
adapterobj = adap
adapter = adap
}
}
private fun setCardButtons() {
binding.btnAdd.setOnClickListener {
val categorie = CategorieModel(binding.catName.editText?.text.toString(),null,null, userId,0)
newCategorie(categorie)
}
binding.btnCancel.setOnClickListener {
toggleKeyboard()
}
}
private fun newCategorie(cat: CategorieModel){
//Toast.makeText(requireContext(), categories.size.toString(), Toast.LENGTH_SHORT).show()
model.createCategorie(cat)
model.categorieCreate.observe(viewLifecycleOwner, Observer {response ->
response?.let {
if(response.isSuccessful){
afterCreateActions(response.body()?.result)
}else{
afterErrorActions()
}
}
})
}
private fun afterCreateActions(cat: CategorieModel?) {
categories.add(cat!!)
adapterobj.notifyDataSetChanged()
toggleKeyboard()
toggleForm()
Snackbar.make(binding.mainView, "${cat?.naam} werd succesvol toevoegd!", Snackbar.LENGTH_LONG)
.setAnimationMode(Snackbar.ANIMATION_MODE_FADE)
.show()
}
The problem was that I was Observing liveData in an clicklistener, that was the main problem! Thanks to Ramakrishna Joshi n!
Visit answer!

Categories

Resources