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.
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.
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.
<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>
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);
}
});
}
};
}
<?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>
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;
}
}
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;
}
}
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);
}
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();
}
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);
}
};
}
To use room, add below libraries to your project by updating gradle build file with below dependency inclusions.
dependencies {
....
implementation 'android.arch.lifecycle:extensions:1.0.0-rc1'
implementation 'android.arch.persistence.room:runtime:1.0.0-rc1'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0-rc1'
}