ZOFTINO.COM android and web dev tutorials

Android Search Dialog Example

Android system provides support for implementing search functionality in apps. Search functionality can be implemented in two ways - search dialog and SearchView. Search dialog is shown at the top of activity and all the search events are handled by android system. SearchView is customizable and can be placed anywhere in the layout.

In this tutorial, you can learn how to implement search functionality using search dialog provided by android. The tutorial also covers creating custom search results layout and custom search adapter, and implementing search results and search suggestions with image or icon.

Android Search Dialog Example

All the steps required to implement search dialog are explained using an example related to coupons search. The example uses List as data source. The example displays search button as menu item in tool bar, clicking the button will open search dialog. Search dialog shows application icon and text view for inputting search text. The example shows search results as search suggestions while user is typing and list of search item when user submits the search text.

The example shows how to captures and process user search selection from both search suggestion and search results list.

Search Button in Toolbar

android search dialog and custom suggestions example

Search Dialog

Below screen shot shows at top of an activity screen the search dialog, which gets opened when onSearchRequested called in the activity that is tied to searchable activity.

android search dialog example

Search Dialog Search Results

Below screen shot shows in search dialog the search results returned by the searchable activity in response to search submit. You can customize search result layout and provide custom list view adapter to show more elements in the search results.

android search dialog search results example

Android Search Dialog Steps

  • First create searchable activity that performs the search. Android system starts this activity by firing an intent passing search string to it.
  • Define searchable configuration xml and add it to searchable activity in android manifest xml.
  • Configure the searchable activity in android manifest.xml file.
  • Next in manifest xml file, tie the searchable activity to activity where you want to provide search functionality by adding searchable activity info to the activity using meta definition for android.app.default_searchable.
  • To your layout, add search button which is used to trigger search dialog and usually placed in the action bar menu.
  • Handle the search button click event in the activity and call onSearchRequested() to trigger search dialog.
  • To provide search suggestions, create content provider.
  • Add the content provider to searchable configuration to inform android system about source of search suggestions by setting values for attributes such as android:searchSuggestAuthority, android:searchSuggestPath, android:searchSuggestIntentAction, etc.

Searchable Activity

Android system starts searchable activity when user submits search string. Searchable activity performs the search using search string passed to in the SEARCH intent and displays the search results.

The example uses ListView to show the search results. Custom list view adapter has been created to display search results which is a List of strings.

OnItemClickListener has been added to the list view to handle item click event. In OnItemClickListener, selected search item can be obtained and further processing can be done.

Searchable activity handles VIEW intent also. System fires this intent when user selects a search item from the list of search suggestions. You can get more information on this in the content provider section.

 import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;


public class CouponSearchActivity extends AppCompatActivity {
    private ListView listView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.searchable_layout);

        listView = findViewById(R.id.listView);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override public void onItemClick(AdapterView<?> parent, View view,
                                              int position, long id) {
                //execution come here when an item is clicked from
                //the search results displayed after search form is submitted
                //you can continue from here with user clicked search item
                Toast.makeText(CouponSearchActivity.this,
                        "clicked search result item is"+((TextView)view).getText(),
                        Toast.LENGTH_SHORT).show();
            }
        });
        // search
        handleSearch();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        setIntent(intent);
        handleSearch();
    }
    private void handleSearch() {
        Intent intent = getIntent();
        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
            String searchQuery = intent.getStringExtra(SearchManager.QUERY);

            CustomSearchAdapter adapter = new CustomSearchAdapter(this,
                    android.R.layout.simple_dropdown_item_1line,
                    StoresData.filterData(searchQuery));
            listView.setAdapter(adapter);

        }else if(Intent.ACTION_VIEW.equals(intent.getAction())) {
            String selectedSuggestionRowId =  intent.getDataString();
            //execution comes here when an item is selected from search suggestions
            //you can continue from here with user selected search item
            Toast.makeText(this, "selected search suggestion "+selectedSuggestionRowId,
                    Toast.LENGTH_SHORT).show();
        }
    }
}
 

Searchable Activity Custom ListView 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.TextView;

import java.util.List;

public class CustomSearchAdapter extends ArrayAdapter {

    private List<String> dataList;
    private Context mContext;
    private int searchResultItemLayout;

    public CustomSearchAdapter(Context context, int resource, List<String> storeSourceDataLst) {
        super(context, resource, storeSourceDataLst);
        dataList = storeSourceDataLst;
        mContext = context;
        searchResultItemLayout = resource;
    }

    @Override
    public int getCount() {
        return dataList.size();
    }

    @Override
    public String 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);
        }

        TextView resultItem = (TextView) view.findViewById(android.R.id.text1);
        resultItem.setText(getItem(position));
        return view;
    }
}
 

Searchable Activity Layout

Searchable activity layout is used to show search results.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".CouponSearchActivity">
 <ListView android:id="@+id/listView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:layout_marginTop="16dp"
        android:padding="8dp"
        android:scrollbars="none" />
</RelativeLayout>

Searchable Configuration

Below is the searchable configuration. Android system uses it. You can set hint, label, searchButtonText, etc. You can see searchable configuration elements for more information.

Configuration related to search suggestion is also added to searchable configuration. Attributes such as searchSuggestAuthority, searchSuggestPath and searchSuggestIntentAction are defined for our example to configure search suggestions.

 <?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint"
    android:searchSuggestAuthority="com.zoftino.coupons.search"
    android:searchSuggestPath="stores"
    android:searchSuggestIntentAction="android.intent.action.VIEW">
</searchable> 

Manifest

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zoftino.com.androidsearch">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/zoftino"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/zoftino"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data
                android:name="android.app.default_searchable"
                android:value=".CouponSearchActivity" />
        </activity>
        <activity android:name=".CouponSearchActivity">
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
        </activity>

        <provider
            android:name=".CouponsSearchContentProvider"
            android:authorities="com.zoftino.coupons.search"></provider>
    </application>
</manifest>
 

Activity with Search Button

This activity handles search menu item click event, calls onSearchRequested method to start search dialog.

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar myToolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(myToolbar);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.search_m:
                //start search dialog
                super.onSearchRequested();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}
 

Toolbar Menu with Search Button

 <?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/search_m"
        android:icon="@drawable/search"
        android:title="@string/search_hint"
        app:showAsAction="always"></item>
</menu>

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="zoftino.com.androidsearch.MainActivity">
    <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"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="ANDROID SEARCH DIALOG EXAMPLE"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar"/>
</android.support.constraint.ConstraintLayout>

Search Dialog Suggestions Content Provider

If you want to show search suggestions as user types into search box, you need to define content provider. Once content provider is defined, android system needs to be informed about it by adding search suggestion configuration to searchable configuration file. Android system calls the content provider to get search suggestions as user types in.

Source of data for content provider can be same as the one used in searchable activity which gets fired when user submits search string or different data with less details, but the cursor that the content provider returns should have specific column names otherwise each item will be displayed as blank item in the search suggestion list.

Column name for displaying text are SearchManager.SUGGEST_COLUMN_TEXT_1 and SearchManager.SUGGEST_COLUMN_TEXT_2. Column names for icons are SearchManager.SUGGEST_COLUMN_ICON_1 and SearchManager.SUGGEST_COLUMN_ICON_2. For more information on search suggestion columns, see search manager.

In our example, content provider returns MatrixCursor containing search suggestions from List of data. Since our example displays text and left icon, we used SearchManager.SUGGEST_COLUMN_TEXT_1 and SearchManager.SUGGEST_COLUMN_ICON_1 columns.

When user clicks a search suggestion, android system fires intent with the intent action mentioned in the searchable configuration. If search suggestions cursor contains column SearchManager.SUGGEST_COLUMN_INTENT_DATA, android system adds the data from SearchManager.SUGGEST_COLUMN_INTENT_DATA column to intent so that the activity which handles the selection can identify the row user selected from the search suggestions and do further processing. This data can be obtained by calling getDataString on the intent.

As mentioned before, searchable activity handles two intent actions, SEARCH action which gets fired when user submits the search and second intent action is the one which is added to searchable configuration and gets fired when user selects an item from search suggestions, in our example it is VIEW action.

Search Suggestions Example Output

Below screen shot shows search suggestions with icon and details for each item.

android search dialog and custom suggestions with icons example

Search Suggestions ContentProvider

 package zoftino.com.androidsearch;


import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

public class CouponsSearchContentProvider extends ContentProvider {

    private static final String STORES = "stores/"+ SearchManager.SUGGEST_URI_PATH_QUERY+"/*";

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI("com.zoftino.coupons.search", STORES, 1);
    }

    private static String[] matrixCursorColumns = {"_id",
            SearchManager.SUGGEST_COLUMN_TEXT_1,
            SearchManager.SUGGEST_COLUMN_ICON_1,
            SearchManager.SUGGEST_COLUMN_INTENT_DATA };

    @Override
    public boolean onCreate() {
        return true;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {

        String queryType = "";
        switch(uriMatcher.match(uri)){
            case 1 :
                String query = uri.getLastPathSegment().toLowerCase();
                return getSearchResultsCursor(query);
            default:
                return null;
        }
    }

    private MatrixCursor getSearchResultsCursor(String searchString){
        MatrixCursor searchResults =  new MatrixCursor(matrixCursorColumns);
        Object[] mRow = new Object[4];
        int counterId = 0;
        if(searchString != null){
            searchString = searchString.toLowerCase();

            for(String rec :  StoresData.getStores()){
                if(rec.toLowerCase().contains(searchString)){
                    mRow[0] = ""+counterId++;
                    mRow[1] = rec;

                    mRow[2] = getContext().getResources().getIdentifier(getStoreName(rec),
                            "drawable", getContext().getPackageName());
                    mRow[3] = ""+counterId++;

                    searchResults.addRow(mRow);
                }
            }
        }
        return searchResults;
    }

    private String getStoreName(String suggestion){
        String suggestionWords[] = suggestion.split(" ");
        return suggestionWords[0].toLowerCase();
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

Notice that android search dialog adds SearchManager.SUGGEST_URI_PATH_QUERY to content provider uri.

Search Data

 import java.util.ArrayList;
import java.util.List;

public class StoresData {
    private static List<String> stores ;
    static {
        stores =  new ArrayList<String>();
        stores.add("Amazon");
        stores.add("Sears");
        stores.add("Ebay Home");
        stores.add("Macys Home");
        stores.add("JCpenney Kids");
        stores.add("Ebay Electronics");
        stores.add("Amazon Appliance");
        stores.add("Ebay Mobiles");
        stores.add("Ebay Kids");
        stores.add("Amazon Fashion");
        stores.add("Ebay Travel");
        stores.add("JCpenney Home");
        stores.add("JCpenney Luggage");
        stores.add("JCpenney Appliance");
        stores.add("JCpenney Fashion");
        stores.add("Amazon Luggage");
        stores.add("Macys Jewellery");
        stores.add("JCpenney Jewellery");
        stores.add("Amazon Jewellery");
    }

    public static List<String> getStores(){
        return stores;
    }

    public static List<String> filterData(String searchString){
        List<String> searchResults =  new ArrayList<String>();
        if(searchString != null){
            searchString = searchString.toLowerCase();

            for(String rec :  stores){
                if(rec.toLowerCase().contains(searchString)){
                    searchResults.add(rec);
                }
            }
        }
        return searchResults;
    }
}