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.
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.