Android Search Functionality Using SearchView & Room

One of the common features in any application is the search feature. In this post, you can learn how to implement search feature in android applications using SearchView widget, Room persistence framework for accessing SQLite database, ListView and custom adapter for displaying search results and search suggestions.

Adding SearchView Widget

SearchView can be placed anywhere in the layout, but the best place for search view as per android standard is action bar or tool bar. To keep search view as action view on tool bar, you need to create a menu item setting actionViewClass to SearchView and set other action view attributes like showAsAction.

Then inflate the menu in onCreateOptionsMenu method of the action where you want the search feature. Then get SearchView object from menu and add SearchView.OnQueryTextListener to it by calling setOnQueryTextListener method. SearchView.OnQueryTextListener has two callback methods onQueryTextSubmit and onQueryTextChange.

Method onQueryTextSubmit gets called when user submits search by hitting enter button or clicking submit button on the search widget. In this method, database search can be performed using the text entered into search view widget. You can enable the search button in search view widget by calling setSubmitButtonEnabled method and passing boolean value of true.

Method onQueryTextChange gets called as text changes when user types into search view widget. Using this method, search can be performed and results can be displayed as user enters search text into SearchView.

Search Functionality Using SearchView & Room Example

I’ll show how to create search feature with deals example app. The example uses Room to access SQLite database which contains deals. On search test change in SearchView or submit of search, Room dao is used to fetch search results as LiveData so that the process runs in the background thread not in the main thread.

In the LiveData observer, each item data of search results will be populated in the item layout using custom ListView adapter. Then the adapter will be added to ListView which is part of the activity layout.

Android Search Functionality Example Output

Menu with SearchView

<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:title="Search" android:icon="@drawable/search" app:showAsAction="ifRoom|collapseActionView" app:actionViewClass="android.support.v7.widget.SearchView"/> </menu>

Activity

import android.arch.lifecycle.Observer; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.widget.ListView; import java.util.List; import zoftino.com.androidsearch.searchview.DealInfo; import zoftino.com.androidsearch.searchview.DealsListViewAdapter; import zoftino.com.androidsearch.searchview.LocalRepository; public class DealsSearchActivity extends AppCompatActivity { private SearchView searchView; private ListView listView; private LocalRepository localRepository = new LocalRepository(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.deals_search); Toolbar myToolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(myToolbar); listView = findViewById(R.id.search_results_list); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.search_menu, menu); searchView = (SearchView) menu.findItem(R.id.action_search) .getActionView(); searchView.setSubmitButtonEnabled(true); searchView.setOnQueryTextListener(onQueryTextListener); return super.onCreateOptionsMenu(menu); } private SearchView.OnQueryTextListener onQueryTextListener = new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { getDealsFromDb(query); return true; } @Override public boolean onQueryTextChange(String newText) { getDealsFromDb(newText); return true; } private void getDealsFromDb(String searchText) { searchText = "%"+searchText+"%"; localRepository.getDealsListInfo(DealsSearchActivity.this, searchText) .observe(DealsSearchActivity.this, new Observer<List<DealInfo>>() { @Override public void onChanged(@Nullable List<DealInfo> deals) { if (deals == null) { return; } DealsListViewAdapter adapter = new DealsListViewAdapter( DealsSearchActivity.this, R.layout.deal_item_layout, deals); listView.setAdapter(adapter); } }); } }; }

Activity Layout

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.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=".DealsSearchActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> <ListView android:id="@+id/search_results_list" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/toolbar"/> </android.support.constraint.ConstraintLayout>

Custom Adapter

import android.content.Context; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; import zoftino.com.androidsearch.R; public class DealsListViewAdapter extends ArrayAdapter { private List<DealInfo> dataList; private Context mContext; private int searchResultItemLayout; public DealsListViewAdapter(Context context, int resource, List<DealInfo> storeSourceDataLst) { super(context, resource, storeSourceDataLst); dataList = storeSourceDataLst; mContext = context; searchResultItemLayout = resource; } @Override public int getCount() { return dataList.size(); } @Override public DealInfo getItem(int position) { return dataList.get(position); } @Override public View getView(int position, View view, @NonNull ViewGroup parent) { if (view == null) { view = LayoutInflater.from(parent.getContext()) .inflate(searchResultItemLayout, parent, false); } DealInfo di = getItem(position); TextView dealsTv = (TextView) view.findViewById(R.id.tv_deal); dealsTv.setText(di.getDeal()); TextView cashbackTv = (TextView) view.findViewById(R.id.tv_cashback); cashbackTv.setText("Cashback "+di.getCashback()); ImageView storeIb = (ImageView) view.findViewById(R.id.ib_store); int storeImageId = mContext.getResources().getIdentifier(di.getStore().toLowerCase(), "drawable", mContext.getPackageName()); storeIb.setImageResource(storeImageId); return view; } }

Room Entity

import android.arch.persistence.room.Entity; import android.arch.persistence.room.PrimaryKey; @Entity public class DealInfo { @PrimaryKey(autoGenerate = true) private int _id; private String store; private String deal; private String cashback; public int get_id() { return _id; } public void set_id(int _id) { this._id = _id; } public String getStore() { return store; } public void setStore(String store) { this.store = store; } public String getDeal() { return deal; } public void setDeal(String deal) { this.deal = deal; } public String getCashback() { return cashback; } public void setCashback(String cashback) { this.cashback = cashback; } }

Room DAO

import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Query; import android.database.Cursor; import java.util.List; @Dao public interface DealDAO { @Query("SELECT * FROM DealInfo WHERE deal LIKE :dealText") public LiveData<List<DealInfo>> getDealsList(String dealText); }

Room Database

import android.arch.persistence.room.Database; import android.arch.persistence.room.RoomDatabase; @Database(entities = {DealInfo.class}, version = 1) public abstract class DealsDatabase extends RoomDatabase { public abstract DealDAO dealDAO(); }

Local Repository

import android.arch.lifecycle.LiveData; import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.Room; import android.arch.persistence.room.RoomDatabase; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import java.util.List; public class LocalRepository { private static DealsDatabase dealsDatabase; private static final Object LOCK = new Object(); public synchronized static DealsDatabase getDealsDatabase(Context context) { if (dealsDatabase == null) { synchronized (LOCK) { if (dealsDatabase == null) { dealsDatabase = Room.databaseBuilder(context, DealsDatabase.class, "DEALS DB") .fallbackToDestructiveMigration() .addCallback(dbCallback).build(); } } } return dealsDatabase; } public DealDAO getDealsDAO(Context context){ return getDealsDatabase(context).dealDAO(); } public LiveData<List<DealInfo>>getDealsListInfo(Context context, String query){ return getDealsDAO(context).getDealsList(query); } public Cursor getDealsCursor(Context context, String query){ return getDealsDAO(context).getDealsCursor(query); } private static RoomDatabase.Callback dbCallback = new RoomDatabase.Callback(){ public void onCreate (SupportSQLiteDatabase db){ } public void onOpen (SupportSQLiteDatabase db){ //first delete existing data and insert laates deals db.execSQL("Delete From DealInfo"); ContentValues contentValues = new ContentValues(); contentValues.put("store", "Amazon"); contentValues.put("deal", "60% off on fashion"); contentValues.put("cashback", "3%"); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "Amazon"); contentValues.put("deal", "70% off on all mobiles"); contentValues.put("cashback", "1%"); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "JcPenney"); contentValues.put("deal", "20% off on fashion"); contentValues.put("cashback", "5%"); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "JcPenney"); contentValues.put("deal", "40% off on electronics"); contentValues.put("cashback", "6%"); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "Sears"); contentValues.put("deal", "50% off on fashion"); contentValues.put("cashback", "6%"); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "Macys"); contentValues.put("deal", "60% off on fashion"); contentValues.put("cashback", ""); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "Kohls"); contentValues.put("deal", "36% off on fashion"); contentValues.put("cashback", ""); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "Walmart"); contentValues.put("deal", "75% off on fashion"); contentValues.put("cashback", ""); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); contentValues = new ContentValues(); contentValues.put("store", "Nordstrom"); contentValues.put("deal", ""); contentValues.put("cashback", "30% off on fashion"); db.insert("DealInfo", OnConflictStrategy.IGNORE, contentValues); } }; }

Dependencies

To use room, add below libraries to your project by updating gradle build file with below dependency inclusions.