I am working with RecyclerView and the issue is when is click on one item it become selected.after That when click on second item the first and second item both are selected at the same time.
Code For Onclick:
init {
itemView.setOnClickListener {
modesList[adapterPosition].isSelected = true
val modelWrapper = modesList[adapterPosition]
val formatModel = modelWrapper.mode
itemListener.recyclerViewListClicked(
formatModel,
formatModel.getId(),
formatModel.modeName,
formatModel.modeName_Number,
formatModel.modeBrightnessProgressBar,
formatModel.modeSpeedProgressBar,
formatModel.colorValues,
adapterPosition
)
}
You should get the position of an item in your adapter and pass it to the activity or fragment and simply do your job.
Here you can see an example.
class MyAdapter : RecyclerView.Adapter<MyAdapter.MyAdapterViewHolder>() {
inner class MyAdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: YourEntityObject, newItem: YourEntityObject): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: YourEntityObject, newItem: YourEntityObject): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyAdapterViewHolder{
return MyAdapterViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false))
}
override fun getItemCount(): Int {
return differ.currentList.size
}
private var onItemClickListener: ((YourEntityObject) -> Unit)? = null
override fun onBindViewHolder(holder: MyAdapterViewHolder, position: Int) {
val info = differ.currentList[position]
holder.itemView.apply {
setOnClickListener {
onItemClickListener?.let { it(repoInfo) }
}
}
}
fun setOnItemClickListener(listener: (YourEntityObject) -> Unit) {
onItemClickListener = listener
}
}
Now in your fragment just simply write as following.
private lateinit var myAdapter: MyAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
adapterOperation()
}
private fun adapterOperation() {
with(myAdapter) {
setOnItemClickListener {
toast(it.your_entity_property)// for example toast the name of list item
}
}
}
private fun setupRecyclerView() {
adapter = MyAdapter()
your_recyclerveiw.apply {
adapter = myAdapter
layoutManager = LinearLayoutManager(activity)
}
}
}
You have to declare itemid and itemviewtype in your Recycler Adapter to overcome this.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Related
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
I am replacing a regular RecyclerView with a ListAdapter.
I am trying to implement a delete on swipe on the list within a fragment.
I have created the following ListAdapter class, the code for which is as follows:
class NewsAdapter(private val listener: OnItemClickListener) : ListAdapter<Article, NewsAdapter.ArticleViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val binding = ItemArticlePreviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ArticleViewHolder(binding)
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val currentArticle = getItem(position)
holder.bind(currentArticle)
}
inner class ArticleViewHolder(private val binding: ItemArticlePreviewBinding): RecyclerView.ViewHolder(binding.root) {
init {
binding.apply {
root.setOnClickListener {
val position = adapterPosition
if(position != RecyclerView.NO_POSITION) {
val article = getItem(position)
listener.onItemClick(article)
}
}
}
}
fun bind(article: Article) {
binding.apply {
Glide.with(itemView.context).load(article.urlToImage).into(ivArticleImage)
tvSource.text = article.source.name
tvTitle.text = article.title
tvDescription.text = article.description
tvPublishedAt.text = article.publishedAt
}
}
}
/*
* Had to create my own function here since for some reason,
* newsAdapter.currentList is not working in any fragment
* */
fun getArticleAt(position: Int): Article {
return getItem(position)
}
interface OnItemClickListener {
fun onItemClick(article: Article)
}
class DiffCallback : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
}
Within the fragment where this list resides, I have the following:
...
lateinit var newsAdapter: NewsAdapter
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
val article = newsAdapter.getArticleAt(position) // this works, but it is not what I want
viewModel.deleteArticle(article)
Snackbar.make(view, "Article successfully deleted.", Snackbar.LENGTH_LONG)
.apply {
setAction("Undo") {
viewModel.saveArticle(article)
}
show()
}
}
}).attachToRecyclerView(rvSavedNews)
...
}
...
Notice above, that I had to call:
val article = newsAdapter.getArticleAt(position)
within the fragment, since for some reason, I cannot use currentList as follows:
val article = newsAdapter.currentList[position]
The delete on swipe works just fine from what I can tell, however, I don't think I am always getting the most current list. Note: calling submitList on my adapter works just fine for other purposes. currentList seems to not be callable and I cannot figure out why.
I want to add an object from my RecyclerView to database by clicking on its icon. I use MVVM pattern, so I should add a object in Fragment. I have 2 callbacks and second one should add an object to database when by clicking. How can I get object so as to use it in viewModel.saveWord() method in adapter callback. Or I should do it anywhere else?
RecyclerView Adapter:
class SearchDefAdapter(
private var infoListener: OnItemClickListener,
private var addListener: OnItemClickListener
):
ListAdapter<Def, SearchDefViewHolder>(differCallback) {
interface OnItemClickListener {
fun onItemClick(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchDefViewHolder {
return SearchDefViewHolder(
SearchWordCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
addListener,
infoListener
)
}
override fun onBindViewHolder(holder: SearchDefViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
RecyclerView ViewHolder:
class SearchDefViewHolder(
private val binding: SearchWordCardBinding,
addListener: SearchDefAdapter.OnItemClickListener,
infoListener: SearchDefAdapter.OnItemClickListener
): RecyclerView.ViewHolder(binding.root) {
fun bind(data: Def) {
with (binding) {
searchCardTv.text = "${data.text} - ${data.tr[0].text}"
}
}
init {
binding.addSearchCard.setOnClickListener {
addListener.onItemClick(adapterPosition)
}
binding.infoSearchCard.setOnClickListener {
infoListener.onItemClick(adapterPosition)
}
}
}
RecyclerView differ callback:
val differCallback = object : DiffUtil.ItemCallback<Def>() {
override fun areItemsTheSame(oldItem: Def, newItem: Def): Boolean {
return oldItem.text == newItem.text
}
override fun areContentsTheSame(oldItem: Def, newItem: Def): Boolean {
return oldItem == newItem
}
}
Adapter initialization in fragment:
searchDefAdapter = SearchDefAdapter(
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
if (!translationsList.isNullOrEmpty()){
val bundle = Bundle()
bundle.putStringArray(INFO_BUNDLE_ID, translationsList[position])
val wordFragment = WordFragment()
wordFragment.arguments = bundle
parentFragmentManager.beginTransaction().apply {
replace(R.id.searchFragment, wordFragment)
commit()
}
}
}
},
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
//viewModel.saveWord()
}
}
)
Change your callback parameter from position to your item type.
interface OnItemClickListener {
fun onItemClick(def: Def)
}
//...
class SearchDefViewHolder(
private val binding: SearchWordCardBinding,
addListener: SearchDefAdapter.OnItemClickListener,
infoListener: SearchDefAdapter.OnItemClickListener
): RecyclerView.ViewHolder(binding.root) {
private lateinit var def: Def
fun bind(data: Def) {
with (binding) {
searchCardTv.text = "${data.text} - ${data.tr[0].text}"
}
def = data
}
init {
binding.addSearchCard.setOnClickListener {
addListener.onItemClick(def)
}
binding.infoSearchCard.setOnClickListener {
infoListener.onItemClick(def)
}
}
}
Then in your Fragment, your listeners are easier to define and directly give you the item to write to database.
In my scenario, I have two fragments. In my first Fragment I have a RecyclerView with SelectionTracker. After selecting an item in my first fragment I can navigate to the second fragment. But from my second fragment if I navigate back to the first fragment using back button all my previous selection is lost. How can I prevent this?
I am using Jetpack Navigation Component to handle Fragment navigation.
Here is my fragment
class SelectVideoForCourseFragment : Fragment(R.layout.fragment_select_video_for_course) {
.....
..
private val adapter by lazy { MediaListAdapter() }
private lateinit var selectionTracker: SelectionTracker<String>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeListeners()
initializeRecyclerView(savedInstanceState)
fetchMedias()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
selectionTracker.onSaveInstanceState(outState)
}
private fun fetchMedias() {
val mediaRepo = MediaRepo()
mediaRepo.getVideos(contentResolver).observe(viewLifecycleOwner) {
adapter.submitItems(it)
}
}
private fun initializeRecyclerView(savedInstanceState: Bundle?) {
binding.mediaList.layoutManager = GridLayoutManager(requireContext(), 3)
binding.mediaList.addItemDecoration(GridSpacingItemDecoration(3, 5.dp, false))
binding.mediaList.adapter = adapter
selectionTracker = SelectionTracker.Builder(
"tracker-select-video-for-course",
binding.mediaList,
MediaItemKeyProvider(adapter),
MediaItemDetailsLookup(binding.mediaList),
StorageStrategy.createStringStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectSingleAnything()
).build()
if (savedInstanceState != null) {
selectionTracker.onRestoreInstanceState(savedInstanceState)
}
adapter.tracker = selectionTracker
}
......
...
}
My RecyclerView Adapter:
class MediaListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val differ = AsyncListDiffer(this, DIFF_CALLBACK_MEDIA)
var tracker: SelectionTracker<String>? = null
fun submitItems(updatedItems: List<Any>) {
differ.submitList(updatedItems)
}
fun getSelections(): List<MediaEntity> {
....
..
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 1) {
val binding = ListItemImageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
ViewHolderImage(binding)
} else {
val binding = ListItemVideoBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
ViewHolderVideo(binding)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolderImage) {
tracker?.let {
val item = differ.currentList[position] as ImageEntity
holder.bind(item , it.isSelected(item.uri.toString()))
}
} else {
holder as ViewHolderVideo
val item = differ.currentList[position] as VideoEntity
tracker?.let {
holder.bind(item, it.isSelected(item.uri.toString()))
}
}
}
override fun getItemCount(): Int = differ.currentList.size
override fun getItemViewType(position: Int): Int {
return if (differ.currentList[position] is ImageEntity) {
1
} else {
2
}
}
inner class ViewHolderImage(
private val binding: ListItemImageBinding): RecyclerView.ViewHolder(binding.root
) {
fun bind(item: ImageEntity, isSelected: Boolean) {
.....
...
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<String> =
object : ItemDetailsLookup.ItemDetails<String>() {
override fun getPosition(): Int = absoluteAdapterPosition
override fun getSelectionKey(): String {
val item = differ.currentList[absoluteAdapterPosition] as ImageEntity
return item.uri.toString()
}
override fun inSelectionHotspot(e: MotionEvent): Boolean = true
}
}
inner class ViewHolderVideo(
private val binding: ListItemVideoBinding,
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: VideoEntity, isSelected: Boolean) {
.....
...
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<String> =
object : ItemDetailsLookup.ItemDetails<String>() {
override fun getPosition(): Int = absoluteAdapterPosition
override fun getSelectionKey(): String {
val item = differ.currentList[absoluteAdapterPosition] as VideoEntity
return item.uri.toString()
}
override fun inSelectionHotspot(e: MotionEvent): Boolean = true
}
}
companion object {
val DIFF_CALLBACK_MEDIA = object: DiffUtil.ItemCallback<Any>() {
.....
...
}
}
}
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!