How to Implement Search/Filter for RecyclerView

July 24, 2020

Overview

The best way is to filter the items sent into adapter, but I can’t do this because I am using FirestoreRecyclerAdapter.

I cannot filter via Firestore database operation because I am doing partial text search.

I perform search by by manipulating RecyclerView.Adapter.getItemCount and RecyclerView.Adapter.getItem.

Perform search by selectively hiding the view is not recommended, as sometimes you might end up with a blank view which still occupy the space.

Another interesting solution is via SortedList, as shown in this example.

Model

The adapter support 2 types of view

class Note {
    open val name: String?
        get() = doc.get("name") as? String
}
class Update(val doc: DocumentSnapshot) {
    open val content: String?
        get() = doc.get("content") as? String
}

Adapter

Calling search will trigger notifyDataSetChanged, which trigger getItemCount and getItem which will show the appropriate items.

private class LocalAdapter(options: FirestoreRecyclerOptions<Post>, val viewModel: HomeViewModel) : FirestoreRecyclerAdapter<Post, LocalAdapter.ViewHolder>(options) {
    private var searchText: String = ""
    // indicate if search mode
    private val isSearch: Boolean
        get() = searchText.isNotBlank()
    // store the list of filtered search items
    private var searchItems: MutableList<Post> = mutableListOf()

    override fun getItemViewType(position: Int): Int {
        return when(val item = getItem(position)) {
            is Note -> R.layout.home_item_note
            is Update -> R.layout.home_item_update
            else -> R.layout.home_item_note
        }
        // return super.getItemViewType(position)
    }

    override fun getItemCount(): Int {
        return if (isSearch) {
            searchItems = mutableListOf()
            for (position in 0 until super.getItemCount()) {
                var item = super.getItem(position)
                when(item) {
                    is Note -> {
                        // will regex be faster?
                        // or store lowercase text(all related fields) as map
                        // or use internal text search engine
                        if (item.name?.toLowerCase()?.contains(searchText) == true) {
                            searchItems.add(item)
                        }
                    }
                    is Update -> {
                        if (item.content?.toLowerCase()?.contains(searchText) == true) {
                            searchItems.add(item)
                        }
                    }
                }
            }
            searchItems.size
        }
        else {
            super.getItemCount()
        }
    }

    override fun getItem(position: Int): Post {
        return if (!isSearch) {
            super.getItem(position)
        }
        else {
            searchItems[position]
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalAdapter.ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(viewType, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(
        holder: LocalAdapter.ViewHolder,
        position: Int,
        item: Post
    ) {
        // holder.batchNoTextView.text = "#${item.batchNo}"
        // holder.nameTextView.text = item.name
        when(item) {
            is Note -> {
                bindNote(holder, item)
            }
            is Update -> {
                bindUpdate(holder, item)
            }
        }
    }

    private fun bindNote(holder: ViewHolder, item: Note) {
        holder.apply {
            noteNameTextView.text = item.name
        }
    }

    private fun bindUpdate(holder: ViewHolder, item: Update) {
        holder.apply {
            updateContentTextView.text = item.content
        }
    }

    fun search(text: String) {
        searchText = text
        notifyDataSetChanged()
    }

    inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
        LayoutContainer
}

SearchView

Refer to Add SearchView To Android Toolbar to capture search input.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.