13

I've added search view widget to my action bar and would like to handle autocomplete feature. After writing more then 3 letters it should fulfill http request to my web API which will return json result and should show search widget suggestions. But in documentation is observed the case with content providers. How can I organize autocomplete feature?

Added search view in menu xml file:

    <item android:id="@+id/search"
    android:icon="@drawable/ic_search_white_24dp"
    android:title="Search"
    [namespace]:showAsAction="always"
    [namespace]:actionViewClass="android.widget.SearchView" />

Associates searchable configuration with the SearchView:

public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.navigation, menu);

    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));

    return super.onCreateOptionsMenu(menu);
}

Added searchable configuration:

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint"
    android:searchSuggestAuthority="com.my.domain.searchable_activity" />

And ultimately added empty responsible activity.

Tigran Vardanyan
  • 305
  • 1
  • 3
  • 10

1 Answers1

22

You can't do this with setSearchableInfo() and a search configuration.

The problem is that SearchView needs a CursorAdapter and you are retrieving data from the server, not the database.

However, I have done something like this before with these steps:

  • Set up your SearchView to use a CursorAdapter;

        searchView.setSuggestionsAdapter(new SimpleCursorAdapter(
                context, android.R.layout.simple_list_item_1, null, 
                new String[] { SearchManager.SUGGEST_COLUMN_TEXT_1 }, 
                new int[] { android.R.id.text1 }));
    
  • Create an AsyncTask to read the JSON data from your server and create a MatrixCursor from the data:

    public class FetchSearchTermSuggestionsTask extends AsyncTask<String, Void, Cursor> {
    
        private static final String[] sAutocompleteColNames = new String[] { 
                BaseColumns._ID,                         // necessary for adapter
                SearchManager.SUGGEST_COLUMN_TEXT_1      // the full search term
        };
    
        @Override
        protected Cursor doInBackground(String... params) {
    
            MatrixCursor cursor = new MatrixCursor(sAutocompleteColNames);
    
            // get your search terms from the server here, ex:
            JSONArray terms = remoteService.getTerms(params[0]);
    
            // parse your search terms into the MatrixCursor
            for (int index = 0; index < terms.length(); index++) {
                String term = terms.getString(index);
    
                Object[] row = new Object[] { index, term };
                cursor.addRow(row);
            }
    
            return cursor;
        }
    
        @Override
        protected void onPostExecute(Cursor result) {
            searchView.getSuggestionsAdapter().changeCursor(result);
        }
    
    }
    
  • Set an OnQueryTextListener to kick off your remote server task or start your search activity:

        searchView.setOnQueryTextListener(new OnQueryTextListener() {
    
            @Override
            public boolean onQueryTextChange(String query) {
    
                if (query.length() >= SEARCH_QUERY_THRESHOLD) {
                    new FetchSearchTermSuggestionsTask().execute(query);
                } else {
                    searchView.getSuggestionsAdapter().changeCursor(null);
                }
    
                return true;
            }
    
            @Override
            public boolean onQueryTextSubmit(String query) {
    
                // if user presses enter, do default search, ex:
                if (query.length() >= SEARCH_QUERY_THRESHOLD) {
    
                    Intent intent = new Intent(MainActivity.this, SearchableActivity.class);
                    intent.setAction(Intent.ACTION_SEARCH);
                    intent.putExtra(SearchManager.QUERY, query);
                    startActivity(intent);
    
                    searchView.getSuggestionsAdapter().changeCursor(null);
                    return true;
                }
            }
        });
    
  • Set an OnSuggestionListener on the SearchView to execute your search:

        searchView.setOnSuggestionListener(new OnSuggestionListener() {
    
            @Override
            public boolean onSuggestionSelect(int position) {
    
                Cursor cursor = (Cursor) searchView.getSuggestionsAdapter().getItem(position);
                String term = cursor.getString(cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
                cursor.close();
    
                Intent intent = new Intent(MainActivity.this, SearchableActivity.class);
                intent.setAction(Intent.ACTION_SEARCH);
                intent.putExtra(SearchManager.QUERY, term);
                startActivity(intent);
    
                return true;
            }
    
            @Override
            public boolean onSuggestionClick(int position) {
    
                return onSuggestionSelect(position);
            }
        });
    
kris larson
  • 30,387
  • 5
  • 62
  • 74
  • Thanks, works for me. In my case the suggestion line represents an user icon, name, location and image button. What type of CursorAdapter you can offer? – Tigran Vardanyan Jun 07 '16 at 12:05
  • In which case your `MatrixCursor` would have 5 fields: the `_id` field, the icon field, the name field, the location field, and the image field. `SimpleCursorAdapter` probably won't be adequate for the icon and image, so you would have to subclass `CursorAdapter` and override `newView()` to inflate a custom layout and `bindView()` to map the cursor data to your layout widgets. A note about `bindView()`: It is passed a `Cursor` but no position. That's because the cursor is already positioned on the correct record; all you have to do is grab the current field values. – kris larson Jun 07 '16 at 13:50
  • Awesome response; just a quick fix: in the call to changeCursor in onPostExecute, the argument should be `result` not `cursor` – Vee Apr 29 '17 at 01:06
  • I just did that to see if anyone was paying attention. :) Fixed, thx – kris larson Apr 30 '17 at 08:59