Search code examples
javaandroidandroid-searchmanager

Does ActionBarSherlock 4.2 support search suggestions for a SearchView?


A month ago, I dropped-in ActionBarSherlock 4.2 into my project. I got everything to work, except the search suggestions for my SearchView. The way I was creating search suggestions was using the method in the Android documentation.

Does ActionBarSherlock support search suggestions? I tried to dig through the issue list on the Github page but the issue seems closed but I can't seem to follow the discussion and understand whether it really is a resolved or not. I thought that some of you who've been using ActionBarSherlock might know better.


Solution

  • It doesn't. But I have found a way to make it query your ContentProvider. I looked into the source of SuggestionsAdapter from API 17 where the query executes and got an idea of replacing this method. Also I found that ActionbarSherlock's SuggestionsAdapter does not use your SearchableInfo.

    Edit com.actionbarsherlock.widget.SuggestionsAdapter in your ActionBarSherlock project:

    Add a line

    private SearchableInfo searchable;
    

    in constructor, add

    this.searchable = mSearchable;
    

    Replace getSuggestions method with this one:

    public Cursor getSuggestions(String query, int limit) {
    
        if (searchable == null) {
            return null;
        }
    
        String authority = searchable.getSuggestAuthority();
        if (authority == null) {
            return null;
        }
    
        Uri.Builder uriBuilder = new Uri.Builder()
                .scheme(ContentResolver.SCHEME_CONTENT)
                .authority(authority)
                .query("")  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
                .fragment("");  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
    
        // if content path provided, insert it now
        final String contentPath = searchable.getSuggestPath();
        if (contentPath != null) {
            uriBuilder.appendEncodedPath(contentPath);
        }
    
        // append standard suggestion query path
        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
    
        // get the query selection, may be null
        String selection = searchable.getSuggestSelection();
        // inject query, either as selection args or inline
        String[] selArgs = null;
        if (selection != null) {    // use selection if provided
            selArgs = new String[] { query };
        } else {                    // no selection, use REST pattern
            uriBuilder.appendPath(query);
        }
    
        if (limit > 0) {
            uriBuilder.appendQueryParameter("limit", String.valueOf(limit));
        }
    
        Uri uri = uriBuilder.build();
    
        // finally, make the query
        return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
    }
    

    Now it queries my ContentProvider but crashes with default adapter saying that no layout_height loading some xml file from support library. So you have to use custom SuggestionsAdapter. This is what worked for me:

    import com.actionbarsherlock.widget.SearchView;
    
    import android.app.SearchManager;
    import android.app.SearchableInfo;
    import android.content.ContentResolver;
    import android.content.Context;
    import android.database.Cursor;
    import android.net.Uri;
    import android.support.v4.widget.CursorAdapter;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    
    public final class DrugsSearchAdapter extends CursorAdapter
    {
        private static final int QUERY_LIMIT = 50;
    
        private LayoutInflater inflater;
        private SearchView searchView;
        private SearchableInfo searchable;
    
        public DrugsSearchAdapter(Context context, SearchableInfo info, SearchView searchView)
        {
            super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
            this.searchable = info;
            this.searchView = searchView;
            this.inflater = LayoutInflater.from(context);
        }
    
        @Override
        public void bindView(View v, Context context, Cursor c)
        {
            String name = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
            TextView namet = (TextView) v.findViewById(R.id.list_item_drug_name);
            namet.setText(name);
    
            String man = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2));
            TextView manuf = (TextView) v.findViewById(R.id.list_item_drug_manufacturer);
            manuf.setText(man);
        }
    
        @Override
        public View newView(Context arg0, Cursor arg1, ViewGroup arg2)
        {
            return this.inflater.inflate(R.layout.list_item_drug_search, null);
        }
    
        /**
         * Use the search suggestions provider to obtain a live cursor.  This will be called
         * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
         * The results will be processed in the UI thread and changeCursor() will be called.
         */
        @Override
        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
            String query = (constraint == null) ? "" : constraint.toString();
            /**
             * for in app search we show the progress spinner until the cursor is returned with
             * the results.
             */
            Cursor cursor = null;
            if (searchView.getVisibility() != View.VISIBLE
                    || searchView.getWindowVisibility() != View.VISIBLE) {
                return null;
            }
            try {
                cursor = getSuggestions(searchable, query, QUERY_LIMIT);
                // trigger fill window so the spinner stays up until the results are copied over and
                // closer to being ready
                if (cursor != null) {
                    cursor.getCount();
                    return cursor;
                }
            } catch (RuntimeException e) {
            }
            // If cursor is null or an exception was thrown, stop the spinner and return null.
            // changeCursor doesn't get called if cursor is null
            return null;
        }
    
        public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) {
    
            if (searchable == null) {
                return null;
            }
    
            String authority = searchable.getSuggestAuthority();
            if (authority == null) {
                return null;
            }
    
            Uri.Builder uriBuilder = new Uri.Builder()
                    .scheme(ContentResolver.SCHEME_CONTENT)
                    .authority(authority)
                    .query("") 
                    .fragment(""); 
    
            // if content path provided, insert it now
            final String contentPath = searchable.getSuggestPath();
            if (contentPath != null) {
                uriBuilder.appendEncodedPath(contentPath);
            }
    
            // append standard suggestion query path
            uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
    
            // get the query selection, may be null
            String selection = searchable.getSuggestSelection();
            // inject query, either as selection args or inline
            String[] selArgs = null;
            if (selection != null) {    // use selection if provided
                selArgs = new String[] { query };
            } else {                    // no selection, use REST pattern
                uriBuilder.appendPath(query);
            }
    
            if (limit > 0) {
                uriBuilder.appendQueryParameter("limit", String.valueOf(limit));
            }
    
            Uri uri = uriBuilder.build();
    
            // finally, make the query
            return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
        }
    
    }
    

    And set this adapter in SearchView

    searchView.setSuggestionsAdapter(new DrugsSearchAdapter(this, searchManager.getSearchableInfo(getComponentName()), searchView));