androidkotlinandroid-recyclerviewandroid-adapterandroid-search

Empty dataset indicator for RecyclerView not showing


I'm trying to show a Snackbar with a message whenever the dataset of a filtered RecyclerView contains 0 items but for some reason, the Snackbar doesn't appear at all. Does the relevant code need to go in the Fragment or Adapter? Can this be donw without libraries?

class MyFragment : androidx.fragment.app.Fragment() {
    private lateinit var mRecyclerView: RecyclerView
    private var mAdapter: MyAdapter? = null
    private lateinit var mSearchView: SearchView
    private lateinit var mSearchMenuItem: MenuItem
    private val myList = ArrayList<Item>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.recycler_view, container, false)

        mRecyclerView = view.findViewById(R.id.myRecyclerView)
        mRecyclerView.setHasFixedSize(true)
        mRecyclerView.layoutManager =
            androidx.recyclerview.widget.LinearLayoutManager(this.activity)
        mRecyclerView.addItemDecoration(
                androidx.recyclerview.widget.DividerItemDecoration(
                        context, LinearLayout.VERTICAL
                )
        )

        myList.add(
                Item(
                        getString(R.string.item_a)
                )
        )

        mAdapter = MyAdapter(requireActivity(), myList)

        mRecyclerView.adapter = mAdapter

        return view
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
         val mSnackbar = Snackbar.make(
                requireView(),
                getString(R.string.no_items),
                Snackbar.LENGTH_LONG
        )
        
        mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String): Boolean {
                return false
            }

            override fun onQueryTextChange(newText: String): Boolean {
                mAdapter!!.filter.filter(newText)
                mAdapter!!.notifyDataSetChanged()

                if (mAdapter!!.itemCount <1) {
                    mSnackbar.show()
                } else {
                    mSnackbar.dismiss()
                }

                return false
            }
        })

        super.onCreateOptionsMenu(menu, inflater)
    }
}

Adapter code

class MyAdapter(
    private val mCtx: Context,
    var myList: MutableList<Item>,
) : androidx.recyclerview.widget.RecyclerView.Adapter<MyAdapter.MyViewHolder>(), Filterable {
    private val myListFull = myList.toMutableList()

val mSnackbar = Snackbar.make(
        requireView(),
        getString(R.string.my_message),
        Snackbar.LENGTH_INDEFINITE
)

    private val companyFilter = object : Filter() {
        override fun performFiltering(constraint: CharSequence?): FilterResults {
            val filteredList = ArrayList<Item>()

            if (constraint == null || constraint.isEmpty()) {
                filteredList.addAll(myListFull)
            } else {
                val filterPattern = constraint.toString().toLowerCase().trim { it <= ' ' }

                for (item in myListFull) {
                    if (item.Name.toLowerCase().contains(filterPattern)
                    ) {
                        filteredList.add(item)
                    }
                }
            }

            val results = FilterResults()
            results.values = filteredList
            return results
        }

        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
            myList.clear()
            myList.addAll(results!!.values as List<Item>)
            notifyDataSetChanged()

        if (filteredList.isEmpty()) {
            mSnackbar.show()
        } else {
            mSnackbar.dismiss()
        }

        }
    }

    private fun String.matchesIgnoreCase(otherString: String): Boolean {
        return this.toLowerCase().contains(otherString.trim().toLowerCase())
    }

    class MyViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView
    .ViewHolder(itemView) {
        var tvTitle: TextView = itemView.findViewById(R.id.title)
        var tvSubtitle: TextView = itemView.findViewById(R.id.subtitle)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(mCtx)
        val v = inflater.inflate(R.layout.list_item, parent, false)
        return MyViewHolder(v)
    }

    override fun getItemCount(): Int {
        return myList.size
    }

    override fun getFilter(): Filter {
        return companyFilter
    }
}

Solution

  • I'm guessing it's this:

    Filtering operations performed by calling filter(java.lang.CharSequence) or filter(java.lang.CharSequence, android.widget.Filter.FilterListener) are performed asynchronously. When these methods are called, a filtering request is posted in a request queue and processed later.

    So you're checking myList (through getItemCount) immediately after calling filter, and the data hasn't updated yet because the filter / publish stuff hasn't finished running. So your snackbar gets dismissed

    The publishResults method runs on the UI thread, so maybe display the snackbar there, if you need to?