📜 ⬆️ ⬇️

Android AutoCompleteTextView with web service hints

For one of my Android apps, Book Tracker, I implemented a custom AutoCompleteTextView with hints for book titles that are dynamically loaded from Google Books as you type the name of the book.

The task before the component was as follows:

Final result:


')

Step 1 - Implementing a Custom Adapter for AutoCompleteTextView


The adapter for AutoCompleteTextView is a key component in which hints are loaded and stored. BookAutoCompleteAdapter implements the Filterable interface to intercept user input from an AutoCompleteTextView and pass it as a search query to a web service. The only Filterable interface method is getFilter (), which must return an instance of the Filter class that loads and publishes data. The heirs of the Filter class must implement two methods: performFiltering (CharSequence constraint) and publishResults (CharSequence constraint, Filter.FilterResults results).

The performFiltering method will be called automatically in a separate stream, so there is no need to create and launch a new stream manually. This has already been done for the developer in the Filter class. The publishResults method is called in the UI stream to publish the results on the screen.

BookAutoCompleteAdapter.java

public class BookAutoCompleteAdapter extends BaseAdapter implements Filterable { private static final int MAX_RESULTS = 10; private final Context mContext; private List<Book> mResults; public BookAutoCompleteAdapter(Context context) { mContext = context; mResults = new ArrayList<Book>(); } @Override public int getCount() { return mResults.size(); } @Override public Book getItem(int index) { return mResults.get(index); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(mContext); convertView = inflater.inflate(R.layout.simple_dropdown_item_2line, parent, false); } Book book = getItem(position); ((TextView) convertView.findViewById(R.id.text1)).setText(book.getTitle()); ((TextView) convertView.findViewById(R.id.text2)).setText(book.getAuthor()); return convertView; } @Override public Filter getFilter() { Filter filter = new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults filterResults = new FilterResults(); if (constraint != null) { List<Books> books = findBooks(mContext, constraint.toString()); // Assign the data to the FilterResults filterResults.values = books; filterResults.count = books.size(); } return filterResults; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { if (results != null && results.count > 0) { mResults = (List<Books>) results.values; notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } }}; return filter; } /** * Returns a search result for the given book title. */ private List<Book> findBooks(String bookTitle) { // GoogleBooksService is a wrapper for the Google Books API GoogleBooksService service = new GoogleBooksService (mContext, MAX_RESULTS); return service.findBooks(bookTitle); } } 


Step 2 - Creating XML Markup for the Prompt String


When the tips are loaded, a drop-down list with the results will be shown. Each line consists of two elements: the name of the book and the name of the author.

simple_dropdown_item_2line.xml

 <?xml version="1.0" encoding="utf-8"?> <TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:mode="twoLine" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> <TextView android:id="@+id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:textAppearance="?android:attr/textAppearanceLargePopupMenu"/> <TextView android:id="@+id/text2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/text1" android:layout_alignStart="@id/text1" android:layout_marginBottom="16dp" android:textAppearance="?android:attr/textAppearanceSmall"/> </TwoLineListItem> 


Step 3 - Add Delay Before Sending Request To Server


When using standard AutoCompleteTextView, a request is initiated after each character entered. If the user is typing text without stopping, prompts received for the previous request may be irrelevant when entering each subsequent character. This creates unnecessary and resource-intensive calls to the server, there is a chance of exceeding the API limits that the web service can have, and also outdated results are loaded for the previous state of the query string.

In order to avoid the above problems, you need to add a small delay between entering a character and sending a request to the server. If during this delay a person enters the next character, the request for the previous line is canceled and carried forward to the delay time. If the user does not change the string during this time, the request is sent to the server.

To implement the above behavior, you need to create a custom implementation of AutoCompleteTextView and override the performFiltering method (CharSequence text, int keyCode). The mAutoCompleteDelay field specifies the time in milliseconds after which the request will be sent to the server if the user has not entered new characters.

DelayAutoCompleteTextView.java

 public class DelayAutoCompleteTextView extends AutoCompleteTextView { private static final int MESSAGE_TEXT_CHANGED = 100; private static final int DEFAULT_AUTOCOMPLETE_DELAY = 750; private int mAutoCompleteDelay = DEFAULT_AUTOCOMPLETE_DELAY; private ProgressBar mLoadingIndicator; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { DelayAutoCompleteTextView.super.performFiltering((CharSequence) msg.obj, msg.arg1); } }; public DelayAutoCompleteTextView(Context context, AttributeSet attrs) { super(context, attrs); } public void setLoadingIndicator(ProgressBar progressBar) { mLoadingIndicator = progressBar; } public void setAutoCompleteDelay(int autoCompleteDelay) { mAutoCompleteDelay = autoCompleteDelay; } @Override protected void performFiltering(CharSequence text, int keyCode) { if (mLoadingIndicator != null) { mLoadingIndicator.setVisibility(View.VISIBLE); } mHandler.removeMessages(MESSAGE_TEXT_CHANGED); mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_TEXT_CHANGED, text), mAutoCompleteDelay); } @Override public void onFilterComplete(int count) { if (mLoadingIndicator != null) { mLoadingIndicator.setVisibility(View.GONE); } super.onFilterComplete(count); } } 


Step 4 - Adding Animated Progress to the Input Field


It is very important to provide feedback when the user types the text. It is necessary to show animated progress in the field of entering the name of the book. Progress is needed in order to inform the person that the tips are loaded and will be displayed soon. This way the user will be aware and will be able to wait until they appear. Without such feedback, a person may not even suspect that the field may show hints.

The ProgressBar and DelayAutoCompleteTextView elements must be placed in FrameLayout and the ProgressBar is aligned on the right side of the parent group. You also need to initially hide the progress using the android: visibility = "gone" attribute setting.

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp"> <com.melnykov.booktracker.ui.DelayAutoCompleteTextView android:id="@+id/book_title" android:inputType="textCapSentences" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingRight="32dp" android:imeOptions="flagNoExtractUi|actionSearch"/> <ProgressBar android:id="@+id/progress_bar" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|right" android:layout_marginRight="16dp" android:visibility="gone"/> </FrameLayout> 


The ProgressBar connects to the DelayAutoCompleteTextView using the latter’s setLoadingIndicator (ProgressBar view) method. The visibility of the progress element is set in View.VISIBLE when prompts are loaded and in View.GONE when download is complete.

Step 5 - Connect Components


Now that all the parts are ready, you need to put them together:

 DelayAutoCompleteTextView bookTitle = (DelayAutoCompleteTextView) findViewById(R.id.book_title); bookTitle.setThreshold(4); bookTitle.setAdapter(new BookAutoCompleteAdapter(context)); bookTitle.setLoadingIndicator((ProgressBar) findViewById(R.id.progress_bar)); bookTitle.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { Book book = (Book) adapterView.getItemAtPosition(position); bookTitle.setText(book.getTitle()); } }); 


bookTitle.setThreshold (4) defines the minimum number of characters that a user must enter in order for hints to be displayed.

bookTitle.setLoadingIndicator ((ProgressBar) findViewById (R.id.progress_bar)) connects the ProgressBar with DelayAutoCompleteTextView.

It is important to set OnItemClickListener for DelayAutoCompleteTextView and assign the correct value to the input field. If you do not do this, the result of calling the toString () method of the selected object will be inserted into the field instead of the book name.

Source: https://habr.com/ru/post/243853/


All Articles