Android SharedPreferences Tutorial and Example

Smart text entry with AutoCompleteTextView on Android

Typing the same long values into a search bar again and again is one of those paper-cuts that slow any Android project down. Whether you are shipping a delivery app that handles Sydney suburbs or letting tradies in Brisbane punch in part numbers, suggesting the input before the user finishes typing saves keystrokes and stops typos in their tracks.

AutoCompleteTextView sits in the framework widget toolkit specifically for this job, and pairing it with a sensible adapter turns an ordinary EditText into a quick-pick field with almost no extra code. The widget extends EditText, so it behaves exactly like one until the user starts typing. From the first character onward it filters a backing list and surfaces matches in a dropdown anchored to the field. Compared with building a custom RecyclerView overlay, this approach needs roughly ten lines of XML and ships with keyboard handling, accessibility hooks, and theming already wired in. If you prefer Material styles, the same logic drops straight into the Material Components library with a couple of import tweaks.

How AutoCompleteTextView compares with its siblings

Android offers three flavours of the component, and most confusion comes from picking the wrong one. The plain widget from android.widget is fine for classic projects or for parts of an app that still ride on older themes. MultiAutoCompleteTextView covers the case where users stack several values separated by a token such as a comma. MaterialAutoCompleteTextView wraps the classic widget with floating labels, error states, and outlined or dense styles that match Material You themes.

Feature Plain AutoCompleteTextView MultiAutoCompleteTextView MaterialAutoCompleteTextView
Single-token matching Yes No Yes
Multi-token matching No Yes No
Default token separator None Whitespace and comma None
Floating label and error text No No Yes
Minimum API level 1 1 21
Recommended for modern UI Limited Limited Yes

For most new Australian app builds targeting Android 8.0 and up, MaterialAutoCompleteTextView is the sensible default. The plain widget is still handy when you need a library-free footprint or want to keep the view inside a tiny helper module. Reach for MultiAutoCompleteTextView only when the form genuinely needs free-form, comma-separated tags like email recipients or label filters.

Wiring the view into your layout

The XML is the same shape as an EditText, so existing forms can be migrated by swapping the tag. Drop this into a ConstraintLayout inside a form that captures an Australian delivery address:

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/suburbLayout"
    style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Suburb">

    <com.google.android.material.textfield.MaterialAutoCompleteTextView
        android:id="@+id/suburbInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:completionThreshold="1" />
</com.google.android.material.textfield.TextInputLayout>

Wrapping the view in a TextInputLayout gives you the animated hint, helper text, and counter support without writing extra Kotlin. If you are sticking with the framework widget, replace the inner tag with <AutoCompleteTextView> from android.widget and drop the parent TextInputLayout. Both versions work fine inside a ScrollView or a plain vertical LinearLayout, so no special container is required.

Layout habits worth keeping

  • Anchor the dropdown with android:dropDownAnchor when the field sits near the bottom of the screen, so the list does not clip on smaller devices.
  • Place android:hint on the TextInputLayout rather than the child so the floating label animation behaves correctly.
  • Reserve room above the field for the dropdown when the form lives inside a modal — Australian shippers often run suburb pickers at checkout.

Feeding the adapter with real data

Behind the scenes the widget needs an Adapter, and ArrayAdapter does the heavy lifting for static lists. Picture a small reminder app aimed at cafes around Melbourne and Hobart — the suggestion strings could be store codes like MEL-CBD-01 or HOB-SAL-02. Pull them from assets or a hard-coded list and you are done:

val stores = arrayOf(
    "MEL-CBD-01", "MEL-SOU-04", "MEL-NOR-02",
    "HOB-SAL-02", "SYD-PAR-03", "BNE-FORT-01"
)
val adapter = ArrayAdapter(
    this,
    android.R.layout.simple_dropdown_item_1line,
    stores
)
suburbInput.setAdapter(adapter)

For longer or remote lists — such as pulling every Australian postcode from a CSV or hitting an internal store-finder API — extend BaseAdapter and override getFilter() so the widget can ask for a narrowed subset as the user types. The default filter only matches with a startsWith check, so any substring matching you need will require your own Filter implementation.

Reading and Writing Complex Objects with SharedPreferences and Gson walks through persisting a small custom list of recently used suburbs on the device, which is a handy pairing for an autocomplete dataset that grows over time and survives process restarts.

Responding when a user picks a row

The widget exposes OnItemClickListener and OnItemSelectedListener. Item click is the bread-and-butter event; selection fires when focus moves into the field with a value already highlighted. A typical handler for a delivery form looks like this:

suburbInput.setOnItemClickListener { parent, view, position, id ->
    val chosenSuburb = parent.getItemAtPosition(position) as String
    suburbLayout.error = null
    recalculateShipping(chosenSuburb)
}

suburbInput.setOnItemSelectedListener { parent, _, position, _ ->
    Log.d("SuburbPicker", "highlighted ${parent.getItemAtPosition(position)}")
}

Clearing the error on the wrapping TextInputLayout is a small but worthwhile touch. Australian shoppers on flaky regional NBN connections often retry a few entries when suggestions load slowly, and visible validation errors that reset cleanly feel much friendlier than lingering red labels.

Tuning the threshold and dropdown behaviour

The default completionThreshold of 2 means the dropdown stays hidden until the user has typed two characters. Drop it to 1 for short suggestion pools such as Australian state abbreviations (NSW, VIC, QLD, WA, SA, TAS, ACT, NT). For larger lists, lifting it to 3 reduces flicker and cuts down on noisy matches that fire too early.

Dials worth turning before you ship

  • android:dropDownHeight caps the list height when many rows match. Lock it to a sensible fixed value or wrap_content depending on how much of the screen you can spare.
  • android:completionHint shows a small footer inside the dropdown — handy for prompting the user with a line like "Start typing your street".
  • android:dropDownVerticalOffset shifts the list to sit clear of any custom icons anchored above the field.

Call suburbInput.dismissDropDown() from onPause() so the dropdown does not leak across activity transitions. Easy line to forget, and it only bites after a rotation, but it prevents a steady trickle of bug reports reaching your support inbox.

Going Material without losing your sanity

Migrating from the framework widget to Material Components is mostly mechanical. Swap the import, push the view inside a TextInputLayout, and confirm your theme inherits from Theme.Material3.DayNight. The only behavioural change is that the floating label is now driven by the parent layout, not the child view, so drop any redundant setHint calls on the AutoCompleteTextView itself.

If your project keeps the framework widget for legacy screens, the two will coexist happily. Point both at the same ArrayAdapter and the same simple_dropdown_item_1line row layout so the dropdown looks consistent across every form. There is no need to rewrite the dataset just to upgrade the look.

For a quick sanity check on what your Material-themed field should actually look like, browse a hands-on demo collection of small Android widgets and see how they style the floating label, helper text, and dropdown rows together. Useful reference when you cannot decide between the outlined and filled variants.

Common pitfalls worth planning for

Most of these issues only show up after the field has been live for a while. Tap on the trigger view fires onItemClick even when the user did not change the underlying value, because the widget treats the current text as a confirmed pick the first time the field gains focus.

Custom adapter rows that forget to override getFilter() will silently fall back to the default prefix matcher and surprise everyone when substring searches stop working. Items that contain only whitespace match everything once the field is empty, causing the dropdown to appear on focus with no useful rows inside.

Mixing framework themes with Theme.Material3 often renders the dropdown background as a flat white panel even when the form above uses brand colours. Pairing AutoCompleteTextView with a thoughtful adapter turns what looks like a small widget into one of the higher-leverage pieces of UI in any form-heavy app, and the real win shows up once users start typing.