9

I have created a searchable activity. Now, i want to add search suggestions that are taken from web service. I want to get those suggestions asynchronously. According to Adding Custom Suggestions I need to override the query method, do my suggestion search, build my own MatrixCursor and return it. but this is the problem, my request for getting the suggestion is an asynchronically one. so when result is back from net it out side of query method's scope.

toko
  • 91
  • 1
  • 4
  • by search suggestion you mean AutoCompleteTextView?? – Archie.bpgc Jul 23 '12 at 11:52
  • 1
    No. I am using the Android search dialog. as describe in documentation: "When using the Android search dialog or search widget, you can provide custom search suggestions that are created from data in your application." what I am trying to do is get those suggestion from the server and not from DB. – toko Jul 24 '12 at 05:10

3 Answers3

5

Here is an example of SearchView with suggestions coming from a network service (I used Retrofit):

@Override
public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_search_activity, menu);

        final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search));

        final CursorAdapter suggestionAdapter = new SimpleCursorAdapter(this,
                android.R.layout.simple_list_item_1,
                null,
                new String[]{SearchManager.SUGGEST_COLUMN_TEXT_1},
                new int[]{android.R.id.text1},
                0);
        final List<String> suggestions = new ArrayList<>();

        searchView.setSuggestionsAdapter(suggestionAdapter);
        searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
            @Override
            public boolean onSuggestionSelect(int position) {
                return false;
            }

            @Override
            public boolean onSuggestionClick(int position) {
                searchView.setQuery(suggestions.get(position), false);
                searchView.clearFocus();
                doSearch(suggestions.get(position));
                return true;
            }
        });

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {

                MyApp.autocompleteService.search(newText, new Callback<Autocomplete>() {
                    @Override
                    public void success(Autocomplete autocomplete, Response response) {
                        suggestions.clear();
                        suggestions.addAll(autocomplete.suggestions);

                        String[] columns = {
                                BaseColumns._ID,
                                SearchManager.SUGGEST_COLUMN_TEXT_1,
                                SearchManager.SUGGEST_COLUMN_INTENT_DATA
                        };

                        MatrixCursor cursor = new MatrixCursor(columns);

                        for (int i = 0; i < autocomplete.suggestions.size(); i++) {
                            String[] tmp = {Integer.toString(i), autocomplete.suggestions.get(i), autocomplete.suggestions.get(i)};
                            cursor.addRow(tmp);
                        }
                        suggestionAdapter.swapCursor(cursor);
                    }

                    @Override
                    public void failure(RetrofitError error) {
                        Toast.makeText(SearchFoodActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
                        Log.w("autocompleteService", error.getMessage());
                    }
                });
                return false;
            }
        });

        return true;
}
moondroid
  • 1,730
  • 17
  • 20
  • 2
    Can you please explain your implementation better? Like, what the method `doSearch(suggestions.get(position))` does? Thank – Henrique de Sousa Apr 12 '16 at 13:18
  • Worked for me. The doSearch method is just the method that does whatever it is that you want to do when the user clicks on a search suggestion – PrashanD Sep 19 '18 at 17:54
1

It seems that the request to the suggestion content provider is not run on the UI thread, anyway, according to this answer: https://stackoverflow.com/a/12895381/621690 . If you can change your http request you could simply call it blocking inside the query method. Might help to listen for interruptions or other signals (custom ones maybe) to stop unnecessary requests.

Another option - if you do not want to change any request classes that are already asynchronous (like if you are using Robospice) - should be to just return the MatrixCursor reference and populate it later on. The AbstractCursor class already implements the Observer pattern and sends out notifications in case of changes. If the search system is listening it should handle any changes in the data. I have yet to implement that myself so I cannot confirm that it will work out as nicely as I picture it. (Have a look at CursorLoader's source for more inspiration.)

And, anyway, isn't that the whole point of a cursor? Otherwise we could simply return a list with data.

UPDATE: For me, using a MatrixCursor didn't work out. Instead, I have implemented two other solutions:

  1. Using the AutoCompleteTextField in combination with a custom subclass of ArrayAdapter which itself uses a custom subclass of Filter. The method Filter#performFiltering() (which I override with the synchronous call to the remote service) is called asynchronously and the UI thread is not blocked.
  2. Using the SearchWidget with a SearchableActivity and a custom ArrayAdapter (without custom Filter). When the search intent comes in, the remote request is started (Robospice) and when it comes back via callback, I call the following custom method on my ArrayAdapter<Tag> subclass:

    public void addTags(List<Tag> items) {
        if (items != null && items.size() > 0) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                super.setNotifyOnChange(false);
                for (Tag tag : items) {
                    super.add(tag);
                }
                super.notifyDataSetChanged();
            } else {
                super.addAll(items);
            }
        }
    }
    

This method takes care of triggering the notifications on the adapter and thus the search result list.

Community
  • 1
  • 1
Risadinha
  • 16,058
  • 2
  • 88
  • 91
  • Definitely the right idea. I'm trying to do exactly this, but it looks like Android's search suggestions don't listen for cursor changes. :/ Specifically, I'm calling `ContentResolver.notifyChange()`, but I don't see that Android's [`SuggestionAdapter`](https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/SuggestionsAdapter.java) calls `registerContentObserver()`. Any ideas? Am I missing anything? – ryan Nov 05 '13 at 01:50
  • It sounded like the right idea but it didn't work out (at least not for me). Updated my answer with my current solutions. – Risadinha Nov 05 '13 at 13:12
  • thanks for the update! those current solutions make sense for search results. i'm still looking for a way to update [search _suggestions_](http://developer.android.com/guide/topics/search/adding-custom-suggestions.html) after they've been returned, though. i suspect there may not be a way...but if you know one, please do let us know! – ryan Nov 05 '13 at 21:23
0

The closest thing I have found to solve this, is by using a ContentProvider and do the network request on the Query method of your provider (even when this goes against the best practices), with the result you can create a MatrixCursor as it's show here and here.

I'm still searching for other options like using a SyncAdapter, which seems overwhelming for the purpose of just showing suggestions that aren't used anywhere else.

Another option, that I took to do this asynchronously is to use an AutoCompleteTextView, that way you can create a custom adapter, where you can implement the getFilter function as it is shown in this answer.

Community
  • 1
  • 1
Aldo Reyes
  • 515
  • 1
  • 4
  • 12
  • Creating MatrixCursor as it's show in the examples, doesn't solves the problem since I want to get the suggestions asynchronously from the server. – toko Jul 30 '12 at 10:11
  • @toko please see my answer edit, I added another option, that's the one I took at the end, that won't block your UI thread while you get the data from the web. – Aldo Reyes Jul 30 '12 at 14:29
  • the problem is i'am not using AutoCompleteTextView. I am using search interface dialog: http://developer.android.com/guide/topics/search/search-dialog.html – toko Aug 01 '12 at 11:04
  • @toko well if you have to stick with the search-dialog, you need to make that call to the server not Async, put your network request inside the Query function of your Provider... which is against the guidelines, although in this case you might not have another option. – Aldo Reyes Aug 01 '12 at 15:04