I am building a small dictioanry app as a side project, screenshot below:
My getFilter() method is working properly and is filtering the results correctly, and when you click on the actual result, a dialog window with information appears. Everything looks fine.
The problem comes when I click on the empty area below the results.
Then I start getting dialog windows for items from the full arraylist items, even though they seem to be invisible.
It seems like their onClick method gets triggered inside the empty space.
Check screenshot below:
Why is this happening and how can I fix it? Is this a normal filtering behaviour?
Below is the code for my Adapter:
class RecyclerViewAdapter(private var items: ArrayList<Item>, private val listener: OnItemClickListener) : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>(), Filterable {
private var itemsFull = ArrayList(Item)
inner class ViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var item: TextView = itemView.findViewById(R.id.tv_item)
val icon: ImageButton = itemView.findViewById(R.id.addToFavourites)
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
val position: Int = absoluteAdapterPosition
val currentItem = items[position]
val item = currentItem.itemName
val definition = currentItem.itemDefinition
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(item,definition,currentItem)
}
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.item,viewGroup,false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val currentItem = items[position]
viewHolder.item.text = currentItem.itemName
viewHolder.icon.setOnClickListener {
if (!Constants.favourites.contains(currentItem)) {
Constants.favourites.add(currentItem)
viewHolder.icon.startAnimation(AnimationUtils.loadAnimation(viewHolder.icon.context, R.anim.shake))
Toasty.custom(viewHolder.icon.context, R.string.addedToFavs,R.drawable.ic_star_full,R.color.blue_700,Toast.LENGTH_SHORT,true, true).show()
}else{
Toasty.custom(viewHolder.icon.context, R.string.alreadyAdded, R.drawable.ic_attention,R.color.blue_700,Toast.LENGTH_SHORT,true, true).show()
}
}
}
override fun getItemCount() = items.size
interface OnItemClickListener {
fun onItemClick(item: String, definition: String, currentItem: Item)
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val results = FilterResults()
if (constraint==null || constraint.isEmpty()) {
results.values = itemsFull
results.count = itemsFull.size
} else {
val filteredItems = ArrayList<Item>()
for (row in itemsFull) {
if (row.itemName.lowercase()
.startsWith(constraint.toString().lowercase().trim())
) {
filteredItems.add(row)
}
}
results.values = filteredItems
results.count = filteredItems.size
}
return results
}
@SuppressLint("NotifyDataSetChanged")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
@Suppress("UNCHECKED_CAST")
items.clear()
items.addAll(results?.values as ArrayList<Item>)
notifyDataSetChanged()
}
}
}
}
The layout containing the RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SearchFragment">
<LinearLayout
android:id="@+id/searchLinear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:background="@color/blue_700"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/bg_linear">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginLeft="@dimen/_17sdp"
android:layout_marginStart="@dimen/_17sdp"
android:layout_marginEnd="@dimen/_17sdp"
android:layout_marginRight="@dimen/_17sdp"
android:layout_marginTop="@dimen/_5sdp"
android:layout_marginBottom="@dimen/_5sdp"
tools:ignore="UselessParent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/alphabet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
tools:listitem="@layout/item_letter"
android:overScrollMode="never"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/searchCardView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_below="@id/searchLinear">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
tools:listitem="@layout/item"/>
</androidx.cardview.widget.CardView>
</RelativeLayout>
UPDATE:
This is how I use the adapter in my Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val searchView = activity?.findViewById<SearchView>(R.id.searchView)
searchView?.imeOptions = EditorInfo.IME_ACTION_DONE
searchView?.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
(mAdapter2 as RecyclerViewAdapter).filter.filter(newText)
return false
}
})
mRecyclerView2 = binding.searchRecyclerView
mRecyclerView2.setHasFixedSize(true)
mRecyclerView2.addItemDecoration(DividerItemDecoration(context,DividerItemDecoration.VERTICAL))
mLayoutManager2 = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
mAdapter2 = RecyclerViewAdapter(Constants.wordList as ArrayList<Item>, this)
mRecyclerView2.layoutManager = mLayoutManager2
mRecyclerView2.adapter = mAdapter2
}
My onItemClick method:
override fun onItemClick(item: String, definition: String, currentItem: Item) {
val args = Bundle()
args.putString("Item", item)
args.putString("Definition", definition)
val inflatedFragment = InflatedItemFragment()
inflatedFragment.arguments = args
val fm = requireActivity().supportFragmentManager
inflatedFragment.show(fm, "inflatedItem")
}
UPDATE 2:
I edited the publishResults like so:
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
items.clear()
items.addAll(results?.values as ArrayList<Item>)
notifyDataSetChanged()
}
Now when I click on the empty area I get an error:
2021-08-28 13:23:19.220 8125-8125/com.jpdevzone.knowyourwords
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jpdevzone.knowyourwords, PID: 8125
java.lang.IndexOutOfBoundsException: Index: 3, Size: 1
at java.util.ArrayList.get(ArrayList.java:437)
at com.jpdevzone.knowyourwords.adapters.RecyclerViewAdapter$ViewHolder.onClick(RecyclerViewAdapter.kt:29)
and its pointing to this line inside the onClick method:
val currentItem = items[position]