Android SearchView in Room Database

Welcome, let’s extend our previous chapter where we learnt about Room Database, and here we are going to search the words from the database using SearchView.

Android SearchView in Room Database in Kotlin

Let’s start the implementation: firstly, add the dependencies and plugin for kapt

apply plugin: 'kotlin-kapt'
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
    // Room dependencies
    implementation 'androidx.room:room-runtime:2.0.0'
    kapt 'androidx.room:room-compiler:2.0.0'
}

Let’s create the table and fill it

@Entity(tableName = "MindOrksDb")
data class Chapter(
    @PrimaryKey
    @ColumnInfo(name = "chapterName") val chapterName: String
) {
    companion object {
        fun populateData(): Array<Chapter> {
            return arrayOf<Chapter>(
                Chapter("MindOrks"),
                Chapter("GetMeAnApp"),
                Chapter("BestContentApp"),
                Chapter("Hackerspace")
            )
        }
    }
}

Our DAO(Database access object interface) where we develop methods use to insert and get the data

@Dao
interface ChapterDao {
    @Insert
    fun insert(chapter: Array<Chapter>)
   @Query("SELECT * FROM MindOrksDb  WHERE chapterName LIKE :query")
    fun getChapterName(query: String): LiveData<List<Chapter>>
}

Now, our database class, here we will create the database and also request to fill the database as soon as the database created. So that user can directly start searching when opening this app

@Database(entities = arrayOf(Chapter::class), version = 1)
abstract class ChapterDatabase : RoomDatabase() {
   abstract fun chapterDao(): ChapterDao
   companion object {
        private var INSTANCE: ChapterDatabase? = null
       fun getDatabase(context: Context): ChapterDatabase? {
            if (INSTANCE == null) {
                synchronized(ChapterDatabase::class) {
                    INSTANCE = Room.databaseBuilder(
                            context.getApplicationContext(),
                            ChapterDatabase::class.java, "chapter.db"
                    ).addCallback(object : RoomDatabase.Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            Executors.newSingleThreadScheduledExecutor().execute(object : Runnable {
                                override fun run() {
                                    getDatabase(context)!!.chapterDao().insert(Chapter.populateData())
                                    Log.d("DatabaseFilled", "DatabaseFilled")
                                }
                            })
                        }
                    })
                           .build()
                }
            }
            return INSTANCE
        }
    }
}

Let’s get to the user side, will build our main screen layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".MainActivity">
   <ListView
        android:id="@+id/lvSearchResult"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>

we will create the design of the item when a user starts typing and words coming at the bottom as a list

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:orientation="vertical">
   <TextView
        android:id="@+id/tvSearch"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Let’s add the search buttonAdd search icon in the drawable from image assets.

Right click on drawable>ImageAssets. Find the search icon and add it.

Let’s add a menu which will show our search icon.

Create a menu folder if not presented inside a res folder and add a search item

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_search"
        android:icon="@drawable/ic_search_view"
        android:title="Search"
        app:actionViewClass="androidx.appcompat.widget.SearchView"
        app:showAsAction="ifRoom|collapseActionView" />
</menu>

Let’s get back to the MainActivity to implement the user interactionFirstly, create the database

private var chapterDatabase: ChapterDatabase? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    chapterDatabase = ChapterDatabase.getDatabase(this)!!
}

Overrides the options menu method to interact with the search icon which is on the menu. Now, how the search view listen when a user starts typing?

We need to set a listener called setOnQueryTextListener. Here, we will query the database whenever the user changes the texts.

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
   val inflater = menuInflater
    inflater.inflate(R.menu.menu_searchview, menu)
   val searchItem = menu!!.findItem(R.id.action_search)
    searchView = searchItem.actionView as SearchView
   searchView.setSubmitButtonEnabled(true)
    searchView.setQueryHint("Search either - MindOrks, GetMeAnApp, BestContentApp, Hackerspace")
   searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
       override fun onQueryTextChange(newText: String): Boolean {
            return true
        }
       override fun onQueryTextSubmit(query: String): Boolean {
            return true
        }
   })
    return super.onCreateOptionsMenu(menu)
}

Create an Adapter class which will bind our data to the ListView. Here, we are linking the data to the listview

class SearchAdapter(contextt: Context, val layout: Int, val chapter: List<Chapter>) : ArrayAdapter<Chapter>(contextt, layout, chapter) {
   override fun getCount(): Int {
        return chapter.size
    }
   override fun getItem(position: Int): Chapter? {
        return chapter.get(position)
    }
   override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var retView: View
        var vi = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
       if (convertView == null) {
            retView = vi.inflate(layout, null)
        } else {
            retView = convertView
        }
       var chapterItem = getItem(position)
        val chapterName = retView.findViewById(R.id.tvSearch) as TextView
        chapterName.text = chapterItem!!.chapterName
       return retView
    }
}

Let’s create a method to get the data from the database. As soon as we get the data, we will update our list view to display updated data to the user.

private fun getNamesFromDb(searchText: String) {
    val searchTextQuery = "%$searchText%"
    chapterDatabase!!.chapterDao().getChapterName(searchTextQuery)
            .observe(this, object : Observer<List<Chapter>> {
                override fun onChanged(chapter: List<Chapter>?) {
                    if (chapter == null) {
                        return
                    }
                    val adapter = SearchAdapter(
                            this@MainActivity,
                            R.layout.search_item,
                            chapter
                    )
                    lvSearchResult.adapter = adapter
                }
            })
}

Now, we will call getNamesFromDb() method every time a user changes a text

searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
   override fun onQueryTextChange(newText: String): Boolean {
        getNamesFromDb(newText)
        return true
    }
   override fun onQueryTextSubmit(query: String): Boolean {
        getNamesFromDb(query)
        return true
    }
})

So, this will be our MainActivity class

class MainActivity : AppCompatActivity() {
   private var chapterDatabase: ChapterDatabase? = null
    private lateinit var searchView: SearchView
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        chapterDatabase = ChapterDatabase.getDatabase(this)!!
    }
   override fun onCreateOptionsMenu(menu: Menu?): Boolean {
       val inflater = menuInflater
        inflater.inflate(R.menu.menu_searchview, menu)
       val searchItem = menu!!.findItem(R.id.action_search)
        searchView = searchItem.actionView as SearchView
       searchView.setSubmitButtonEnabled(true)
        searchView.setQueryHint("Search either - MindOrks, GetMeAnApp, BestContentApp, Hackerspace")
       searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
           override fun onQueryTextChange(newText: String): Boolean {
                getNamesFromDb(newText)
                return true
            }
           override fun onQueryTextSubmit(query: String): Boolean {
                getNamesFromDb(query)
                return true
            }
       })
        return super.onCreateOptionsMenu(menu)
    }
   private fun getNamesFromDb(searchText: String) {
        val searchTextQuery = "%$searchText%"
        chapterDatabase!!.chapterDao().getChapterName(searchTextQuery)
                .observe(this, object : Observer<List<Chapter>> {
                    override fun onChanged(chapter: List<Chapter>?) {
                        if (chapter == null) {
                            return
                        }
                        val adapter = SearchAdapter(
                                this@MainActivity,
                                R.layout.search_item,
                                chapter
                        )
                        lvSearchResult.adapter = adapter
                    }
                })
    }
}

Let’s try to run this application and try to find the words from it.

Try to explore more on this and share with us on our slack and twitter channel.

New Chapters Coming Soon