In-app Search function implementation
– Android TV app hands on Tutorial 13

In-app search

Search function implementation

The usage of SearchFragment of Leanback support library and UI related implementation are explained in previous chapter. In this chapter, I will explain (background) search function logic.

Most of the implementation in this chapter is just a explanation of googlesamples.

In-app search algorithm

Search algorithm is implemented in loadRows method, especially the main logic is in doInBackground method of AsyncTask. It is actually simple, just checking the string “query” is contained in either title or description of Movie items.

The UI related task Adapter handling is done in UI thread. onPreExecute initializes mRowsAdapter, which will contain the search results, by clear method. Searching itself is executed in background thread in doInBackground, and it makes new ListRow which contains the search results. This listRow is added to Adapter in UI thread by onPostExecute method. 

    private void loadRows() {
        // offload processing from the UI thread
        new AsyncTask<String, Void, ListRow>() {
            private final String query = mQuery;

            @Override
            protected void onPreExecute() {
                mRowsAdapter.clear();
            }

            @Override
            protected ListRow doInBackground(String... params) {
                final List<Movie> result = new ArrayList<>();
                for (Movie movie : mItems) {
                    // Main logic of search is here. 
                    // Just check that "query" is contained in Title or Description or not.
                    if (movie.getTitle().toLowerCase(Locale.ENGLISH)
                            .contains(query.toLowerCase(Locale.ENGLISH))
                            || movie.getDescription().toLowerCase(Locale.ENGLISH)
                            .contains(query.toLowerCase(Locale.ENGLISH))) {
                        result.add(movie);
                    }
                }

                ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
                listRowAdapter.addAll(0, result);
                HeaderItem header = new HeaderItem("Search Results");
                return new ListRow(header, listRowAdapter);
            }

            @Override
            protected void onPostExecute(ListRow listRow) {
                mRowsAdapter.add(listRow);
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

Execute search

Since search algorithm is already implemented above, we only need to call this function to get search results.

    @Override
    public ObjectAdapter getResultsAdapter() {
        Log.d(TAG, "getResultsAdapter");
        // Delete previously implemented mock code.
        // mRowsAdapter (Search result) is already prepared in loadRows method
        return mRowsAdapter;
    }

    ...

    @Override
    public boolean onQueryTextSubmit(String query) {
        Log.i(TAG, String.format("Search Query Text Submit %s", query));
        mQuery = query;
        loadRows();
        return true;
    }

Call loadRows method in onQueryTextSubmit method. Easy implementation is already done! let’s try build and run the code here.

Search function works correctly.

We have already finished minimum implementation for search function. Next step is to enable search even during the user’s text typing.

Dynamic search execution

To detect when user typing search query, we can use onQueryTextChange method. So basic concept is to just execute loadRows method in onQueryTextChange to implement dynamic (during user input) search. However, onQueryTextChange will be executed every time user input one words, and we should not call loadRows method when it has already executed. Here, Handler is used to manage executing loadRows method.

    private static final long SEARCH_DELAY_MS = 1000L;

    private final Handler mHandler = new Handler();
    private final Runnable mDelayedLoad = new Runnable() {
        @Override
        public void run() {
            loadRows();
        }
    };

     ...

    /**
     * Starts {@link #loadRows()} method after delay.
     * @param query the word to be searched
     * @param delay the time to wait until loadRows will be executed (milliseconds).
     */
    private void loadQueryWithDelay(String query, long delay) {
        mHandler.removeCallbacks(mDelayedLoad);
        if (!TextUtils.isEmpty(query) && !query.equals("nil")) {
            mQuery = query;
            mHandler.postDelayed(mDelayedLoad, delay);
        }
    }

loadQueryWithDelay method uses Handler to post loadRows task with a little delay so that loadRows task is not executed too frequently. The last modification is to call this loadQueryWithDelay method from onQueryTextChange.

    @Override
    public boolean onQueryTextChange(String newQuery){
        Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
        loadQueryWithDelay(newQuery, SEARCH_DELAY_MS);
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        Log.i(TAG, String.format("Search Query Text Submit %s", query));
        // No need to delay(wait) loadQuery, since the query typing has completed.
        loadQueryWithDelay(query, 0);
        return true;
    }

Build and run

As can be seen by video, search has executed even during the user is typing search query.

Source code is on github.

Leave a Comment

Your email address will not be published. Required fields are marked *