1

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
    }
}
wbk727
  • 8,017
  • 12
  • 61
  • 125

1 Answers1

0

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?

cactustictacs
  • 17,935
  • 2
  • 14
  • 25
  • This will not work because the list will appear blank when the fragment is loaded. Please post your suggested code. – wbk727 Dec 04 '20 at 13:42
  • If this is the problem (which is probably is since the filtering is async) then you'll have to show/dismiss your snackbar after your ``publishResults`` code runs. It's up to you how you do that! I'm just helping you find where the problem is – cactustictacs Dec 04 '20 at 14:15
  • `requireView()` doesn't work in an adapter so what should I be using instead? I also noticed that `getString()` doesn't work either. – wbk727 Dec 04 '20 at 16:09
  • ``requireView()`` is a method on a ``Fragment`` (or ``Activity``) that gets its layout view (or throws an exception if it can't access it, like if it's not added yet). It's the one you're creating in ``onCreateView``. ``getString()`` is also a ``Fragment`` method but you can run it on a ``Context`` as well. It might be best to pass those things into your adapter when you create it, or some kind of callback listener so the ``Fragment`` can handle displaying the snackbar, so that's separate from the adapter. ``Fragment``s don't always have a context or activity though... – cactustictacs Dec 05 '20 at 12:53
  • I'd strongly prefer to use the option with less code as possible. I have added `mCtx.getResources` before `getString` and that that part now works. Can something like this be used for the `view` part? – wbk727 Dec 05 '20 at 15:43
  • Just pass the view you inflated into the adapter's constructor, that's probably easiest? And the reason I suggested moving the snackbar stuff out of the adapter is because really, you should be doing more checking to see if you have a ``Context``/``Activity`` yet, ``requireActivity`` will crash if the fragment isn't associated with one yet, which can happen with things like fragment recreation. Just giving you a heads up! – cactustictacs Dec 05 '20 at 15:58