ZOFTINO.COM android and web dev tutorials

Android SearchView Dropdown Suggestions

In this post, you can learn how to implement search suggestions drop down using SearchView with an example. The example shows how to capture search text as user types into search view, display dropdown containing search suggestions from database, and clear search view and do further processing with the selected item from dropdown.

To show SearchView with suggestion dropdown, I’ll take the example which is used to show how to implement search in android apps and display search suggestions and results in list view. You can see the search feature example for classes which are not included in this post.

Android SearchView Dropdown Example

To handle searches, you need to add OnQueryTextListener to serach view and implement callback method onQueryTextSubmit and onQueryTextChange.

To show search suggestions as dropdown when user types text into SearchView, in the onQueryTextChange call back method mentioned above, cursor adapter needs to be added to SearchView by calling setSuggestionsAdapter method and passing custom cursor adapter which populates data from columns of each row from cursor into views of suggestion dropdown item layout. You can add listener to each item view to handle user selection and do further processing.

Android SearchView Dropdown Output

android searchview dropdown suggestions example

Custom Cursor Adapter

import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import zoftino.com.androidsearch.R;

public class DealsInfoCursorAdapter extends CursorAdapter {
    private LayoutInflater mLayoutInflater;
    private Context mContext;
    private SearchView searchView;

    public DealsInfoCursorAdapter(Context context, Cursor cursor, SearchView sv) {
        super(context, cursor, false);
        mContext = context;
        searchView = sv;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View v = mLayoutInflater.inflate(R.layout.deal_simple_item_layout, parent, false);
        return v;
    }

    @Override
    public void bindView(View view, final Context context, Cursor cursor) {
        String deal = cursor.getString(cursor.getColumnIndexOrThrow("deal"));
        String cashback = cursor.getString(cursor.getColumnIndexOrThrow("cashback"));

        TextView dealsTv = (TextView) view.findViewById(R.id.tv_deal);
        dealsTv.setText(deal);

        TextView cashbackTv = (TextView) view.findViewById(R.id.tv_cashback);
        cashbackTv.setText("Cashback "+cashback);

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //take next action based user selected item
                TextView dealText = (TextView) view.findViewById(R.id.tv_deal);
                searchView.setIconified(true);
                Toast.makeText(context, "Selected suggestion "+dealText.getText(),
                        Toast.LENGTH_LONG).show();

            }
        });

    }
}

Item 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"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tv_deal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <TextView
        android:id="@+id/tv_cashback"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_deal"/>
</android.support.constraint.ConstraintLayout>

Activity

import android.arch.lifecycle.Observer;
import android.database.Cursor;
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 android.widget.Toast;

import java.util.List;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import zoftino.com.androidsearch.searchview.DealInfo;
import zoftino.com.androidsearch.searchview.DealsInfoCursorAdapter;
import zoftino.com.androidsearch.searchview.DealsListViewAdapter;
import zoftino.com.androidsearch.searchview.LocalRepository;

public class DealsNewSearchActivity 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) {
                    query = "%"+query+"%";
                    localRepository.getDealsListInfo(DealsNewSearchActivity.this, query)
                            .observe(DealsNewSearchActivity.this,
                                    new Observer<List<DealInfo>>() {
                                @Override
                                public void onChanged(@Nullable List<DealInfo> deals) {
                                    if (deals == null) {
                                        return;
                                    }
                                    DealsListViewAdapter adapter =
                                            new DealsListViewAdapter(
                                                    DealsNewSearchActivity.this,
                                            R.layout.deal_item_layout, deals);
                                    listView.setAdapter(adapter);

                                }
                            });
                    return true;
                }

                @Override
                public boolean onQueryTextChange(String newText) {
                    getDealsFromDb(newText);
                    return true;
                }

                private void getDealsFromDb(String searchText) {
                    searchText = "%"+searchText+"%";
                    Observable.just(searchText).observeOn(Schedulers.computation())
                            .map( new Function<String, Cursor>(){
                                @Override
                                public Cursor apply(String searchStrt) throws Exception {
                                    return localRepository.getDealsCursor(
                                            DealsNewSearchActivity.this, searchStrt);
                                }
                            }).observeOn(AndroidSchedulers.mainThread())
                            .subscribe(new Consumer<Cursor>() {
                                @Override
                                public void accept(Cursor cursor) throws Exception {
                                    handleResults(cursor);
                                }
                            }, new Consumer<Throwable>() {
                                @Override
                                public void accept(Throwable throwable) throws Exception {
                                    handleError(throwable);
                                }
                            });


                }
            };
    private void handleResults(Cursor cursor){
        searchView.setSuggestionsAdapter(new DealsInfoCursorAdapter
                (DealsNewSearchActivity.this, cursor, searchView));
    }
    private void handleError(Throwable t){
        Toast.makeText(this, "Problem in Fetching Deals",
                Toast.LENGTH_LONG).show();
    }
}

Room DAO

@Dao
public interface DealDAO {
    @Query("SELECT * FROM DealInfo WHERE deal LIKE :dealText")
    public LiveData<List<DealInfo>> getDealsList(String dealText);

    @Query("SELECT * FROM DealInfo WHERE deal LIKE :dealText")
    public Cursor getDealsCursor(String dealText);
}

LocalRepository

Add below method to LocalRepository class.

public Cursor getDealsCursor(Context context, String query){
    return getDealsDAO(context).getDealsCursor(query);
}