
In this article, I will show you how you can implement an instant search feature in android using the Firebase firestore database.
Prerequisite : Kotlin Coroutines, Firestore.

Database Structure- :

Coding:
- Create suggestion_textview_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suggestion_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingStart="56dp"
android:paddingTop="8dp"
android:paddingEnd="24dp"
android:paddingBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title"
android:textColor="@color/textColorPrimary"
android:textSize="16sp"/>
2) Create ArrayAdapter for recyclerview to hold the data.
class ArrayAdapter(private var suggestions: List<String>, private val activity: Activity) :
RecyclerView.Adapter<ArrayAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflate = LayoutInflater.from(parent.context)
.inflate(R.layout.suggestion_textview_item, parent, false)
return ViewHolder(inflate, activity)
}
override fun getItemCount(): Int {
return suggestions.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(suggestions[position])
}
fun setList(suggestionList: List<String>) {
suggestions = suggestionList
notifyDataSetChanged()
}
class ViewHolder(itemView: View, val activity: Activity) : RecyclerView.ViewHolder(itemView) {
val text: MaterialTextView = itemView.suggestion_text
fun bind(data: String) {
text.text = data
itemView.setOnClickListener {
Log.d("Clicked", data)
}
}
}
}
3) Create a custom toolbar search layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<ImageView
android:id="@+id/search_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:contentDescription="@string/search_icon"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:src="@drawable/ic_search"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/search_edittext"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_toEndOf="@id/search_icon"
android:autofillHints="@string/search"
android:background="@drawable/search_edittext_"
android:hint="@string/search"
android:inputType="text"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/search_icon"
app:layout_constraintTop_toTopOf="parent" />
</RelativeLayout>
4) Create a SearchFragment
class SearchFragment : Fragment() {
val firestore = Firebase.firestore
lateinit var searchSuggestionAdapter: ArrayAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_search, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
(activity as MainActivity).supportActionBar!!.title = getString(R.string.search)
(activity as MainActivity).supportActionBar!!.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
searchSuggestionAdapter =
ArrayAdapter(ArrayList(), requireActivity())
search_suggestion_recyclerview.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = searchSuggestionAdapter
}
val inflate = LayoutInflater.from(requireContext())
.inflate(R.layout.search_toolbar_custom_view, container, false)
(activity as MainActivity).supportActionBar!!.customView = inflate
inflate.search_edittext.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
var searchFor = ""
val searchText = s.toString().trim()
if (searchText == searchFor)
return
searchFor = searchText
GlobalScope.launch(IO) {
delay(300)
if (searchText != searchFor)
return@launch
inflate.search_edittext.text.toString()
val suggestionList = ArrayList<String>()
val suggestionSnapshot =
Firebase.firestore.collection("tags").whereArrayContains(
"keywords",
searchText.toLowerCase(Locale.ROOT)
).limit(16)
.get().await()
if (suggestionSnapshot.isEmpty)
suggestionList.add("No Search Result Found")
suggestionSnapshot.forEach {
suggestionList.add(it["name"] as String)
}
withContext(Main)
{
searchSuggestionAdapter.setList(suggestionList)
}
}
}
override fun afterTextChanged(p0: Editable?) {
if (TextUtils.isEmpty(p0)) {
search_suggestion_recyclerview.visibility = View.GONE
category_recyclerview.visibility = View.VISIBLE
materialTextView2.visibility = View.VISIBLE
} else {
search_suggestion_recyclerview.visibility = View.VISIBLE
category_recyclerview.visibility = View.GONE
materialTextView2.visibility = View.GONE
}
}
})
GlobalScope.launch(IO) {
val categories = firestore.collection("trendingTags")
.orderBy("timestamp", Query.Direction.DESCENDING).limit(50).get().await()
.toObjects(Tag::class.java)
.map { tag ->
tag.name
}
withContext(Main)
{
if (isAdded) {
progress_bar.hide()
category_recyclerview.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = ArrayAdapter(categories, requireActivity())
}
}
}
}
}
}
5) Create SearchFragment layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.SearchFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_suggestion_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/suggestion_textview_item" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/materialTextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/trending_tags"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/category_recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/materialTextView2" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/progress_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.ContentLoadingProgressBar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6) Initialize the fragment Run the app.