In the last update for my Android project BookTracker I have implemented an AutoCompleteTextView with suggestions for a book title which are fetched from the Google Books. There were a few requirements for such a view:
Suggestions data fetching must be performed in a separate thread
Data fetching should start only when a user pauses typing (to prevent a bunch of requests to a server after every entered character)
Suggestions should be shown only if the user enters a string of some minimum length (no reason to start data fetching for string of 2 or 3 characters)
An animated progress bar must be shown on the right side of the view when suggestions fetching is in progress
The final result looks like this:
Step 1 – Implement an adapter for AutoCompleteTextView
The adapter for the AutoCompleteTextView is a core component where suggestions are loaded and stored. The BookAutoCompleteAdapter must implement the Filterable interface in order for you to capture the user input from the AutoCompleteTextView and pass it as a search criteria to the web service. A single method of the Filterable interface is getFilter() that must return a Filter instance which actually performs the data loading and publishing. Filter subclasses must implement 2 methods: performFiltering (CharSequence constraint) and publishResults (CharSequence constraint, Filter.FilterResults results).
The performFiltering method is invoked in a worker thread so there is no need to create and start a new thread manually. It’s done already by the Filter itself. The publishResults method is invoked in the UI thread to publish filtering results in the user interface.
publicclassBookAutoCompleteAdapterextendsBaseAdapterimplementsFilterable{privatestaticfinalintMAX_RESULTS=10;privateContextmContext;privateList<Book>resultList=newArrayList<Book>();publicBookAutoCompleteAdapter(Contextcontext){mContext=context;}@OverridepublicintgetCount(){returnresultList.size();}@OverridepublicBookgetItem(intindex){returnresultList.get(index);}@OverridepubliclonggetItemId(intposition){returnposition;}@OverridepublicViewgetView(intposition,ViewconvertView,ViewGroupparent){if(convertView==null){LayoutInflaterinflater=(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);convertView=inflater.inflate(R.layout.simple_dropdown_item_2line,parent,false);}((TextView)convertView.findViewById(R.id.text1)).setText(getItem(position).getTitle());((TextView)convertView.findViewById(R.id.text2)).setText(getItem(position).getAuthor());returnconvertView;}@OverridepublicFiltergetFilter(){Filterfilter=newFilter(){@OverrideprotectedFilterResultsperformFiltering(CharSequenceconstraint){FilterResultsfilterResults=newFilterResults();if(constraint!=null){List<Books>books=findBooks(mContext,constraint.toString());// Assign the data to the FilterResultsfilterResults.values=books;filterResults.count=books.size();}returnfilterResults;}@OverrideprotectedvoidpublishResults(CharSequenceconstraint,FilterResultsresults){if(results!=null&&results.count>0){resultList=(List<Books>)results.values;notifyDataSetChanged();}else{notifyDataSetInvalidated();}}};returnfilter;}/** * Returns a search result for the given book title. */privateList<Book>findBooks(Contextcontext,StringbookTitle){// GoogleBooksProtocol is a wrapper for the Google Books APIGoogleBooksProtocolprotocol=newGoogleBooksProtocol(context,MAX_RESULTS);returnprotocol.findBooks(bookTitle);}}
Step 2 – Create an XML layout for a suggestion list row
When suggestions has been fetched, a list of results would be displayed bellow the view. Each list row consists of two lines: a book name and an author.
Step 3 – Add a delay before sending a data request to a web service
With a standard AutoCompleteTextView a filtering is initiated after each entered character. If the user types text nonstop, data fetched for the previous request may become invalid on every new letter appended to the search string. You get extra expensive and unnecessary network calls, chance of exceeding API limits of your web service, stale suggestion results loaded for an incomplete search string. The way we go – add a small delay before user types the character and a request is sent to the web. If during this time the user enters the next character, the request for the previous search string is cancelled and rescheduled for delay time again. If the user doesn’t change text during delay time, the request is sent. To implement this behaviour we create a custom implementation of AutoCompleteTextView and override the method performFiltering(CharSequence text, int keyCode). The variable mAutoCompleteDelay defines time in milliseconds after a request will be sent to a server if the user doesn’t change the search string.
It’s very important to provide a feedback to the user when he is typing text. We have to display an animated progress in the same view to say to the user “Hey, suggestions are loading right now and will be displayed shortly”. In that way the user will expect something to happen and can wait until response is received. Without such a feedback the user will even not suspect that a field has suggestions.
We put the ProgressBar widget and the DelayAutoCompleteTextView to the FrameLayout and align the progress to the right side of the input field. We set android:visibility="gone" as the initial state of the progress:
123456789101112131415161718192021
<FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="@dimen/margin_default"><com.melnykov.booktracker.ui.DelayAutoCompleteTextViewandroid:id="@+id/et_book_title"android:inputType="textCapSentences"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingRight="@dimen/padding_auto_complete"android:imeOptions="flagNoExtractUi|actionSearch"/>the te
<ProgressBarandroid:id="@+id/pb_loading_indicator"style="?android:attr/progressBarStyleSmall"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical|right"android:layout_marginRight="@dimen/margin_default"android:visibility="gone"/></FrameLayout>
The ProgressBar is connected to the DelayAutoCompleteTextView via setLoadingIndicator(ProgressBar view) method of the latter. It’s visibility is set to View.VISIBLE when a filtering starts and to View.GONE when completes.
Now insert this layout where you need it.
Step 5 – Assemble components together
Finally when we have all components ready we can assemble them together:
123456789101112
DelayAutoCompleteTextViewbookTitle=(DelayAutoCompleteTextView)findViewById(R.id.et_book_title);bookTitle.setThreshold(THRESHOLD);bookTitle.setAdapter(newBookAutoCompleteAdapter(this));// 'this' is Activity instancebookTitle.setLoadingIndicator((android.widget.ProgressBar)findViewById(R.id.pb_loading_indicator));bookTitle.setOnItemClickListener(newAdapterView.OnItemClickListener(){@OverridepublicvoidonItemClick(AdapterView<?>adapterView,Viewview,intposition,longid){Bookbook=(Book)adapterView.getItemAtPosition(position);bookTitle.setText(book.getTitle());}});
bookTitle.setThreshold(THRESHOLD) specifies the minimum number of characters the user has to type in the edit box before the drop down list is shown.
bookTitle.setLoadingIndicator((android.widget.ProgressBar) findViewById(R.id.pb_loading_indicator)) binds the ProgressBar view to the DelayAutoCompleteTextView.
It’s important to set OnItemClickListener for the DelayAutoCompleteTextView and set a correct value to the target input field. Without doing that a string obtained via call to toString() of a selected object will be pasted to the field.