Nowadays, Android apps have more and more features, and also, more and more data to show to the user. Even with the best User Experience (UX), with so many data, it may become hard for users to find what they are looking for.
One common way to make this is easier, is to use a search bar! And what is even better than a search bar — you ask? —
A search bar with an auto complete!
Stay with me, if you want to learn how to get a full customizable auto complete functionality to your search bar.
And… We will be using the most recent Android UI Toolkit, Jetpack Compose. :]
The project is on my GitHub, feel free to check. (Good option if you are lazy! Stay here if you are curious to learn 😁)
As of pre-requisites, you’ll need Android Studio Canary. Currently, Jetpack Compose only works in these Android Studio versions. Use the last version, and you should be ok.
If you had downloaded the project, build and run the app.
You’ll see this.
You only need a TextField, and it can be however you want, it doesn’t matter.
Only the AutoComplete is missing, so let’s jump into it.
The… AutoComplete 😰
As I told you above, I have mentioned a “full customizable auto complete functionality”.
What I mean by this is:
- You can decide how you want each single item in the AutoComplete box to look like;
- You can customize the AutoComplete box;
- You can use any type you want as list of AutoComplete items;
- You can define how and when to filter the items.
The design may be simple. But pay a lot of attention to the implementation, because it is not that simple.
The Behaviors 👌
When using AutoComplete, you expect to filter items based on a query.
This means we will need to teach AutoComplete how to filter them…
Every item we want to use must have an explanation of how to filter itself.
We can achieve this using Interfaces. AutoCompleteEntity is an AutoComplete item.
This means that every item we want to work with must extend AutoCompleteEntity, and “explain” how to filter itself.
ValueAutoCompleteEntity, is a variation of AutoCompleteEntity. The difference, is that it also contains the value. Later you’ll understand why AutoCompleteEntity is not enough.
The Heart 💓
By heart, I mean the AutoComplete state, you were right!
Here we need to think about a lot of things.
What kind of things do we want to keep saved on each recomposition?
- To let AutoComplete keep track of the filtered items, we need to keep them saved for sure;
- We also need to know if the user is searching
- We also want the AutoComplete box to be customizable at any time.
Talking about Developer Experience, how do we want access these properties?
In my own opinion, the best way would be to focus all these changes in one scope.
AutoCompleteScope will be our scope. To separate the concerns, I have created the AutoCompleteDesignScope separately.
So, the goal here is allow us to change any of these properties in a scoped lambda, where we define our TextField content.
How to store this state?
AutoCompleteState is being used to keep track of the state.
It implements the AutoCompleteScope mentioned before.
All variables that have “by mutableStateOf(<type>)” will keep its state tracked over recomposition.
On the usage place, you will be able to call “onItemSelected”, which will define what to do whenever an item is being selected. It consists of a lambda function, with the item as an argument.
Do you remember this?
As you know, we want to work with objects of AutoCompleteEntity type.
The problem is if want to work with any type that you cannot and an implementation, as a list of Strings.
The workaround was to create this ValueAutoCompleteEntity, which is also an AutoCompleteEntity, but includes a value.
To turn any type into the necessary type, we need to add this extension function:
We go through each item of our list, and map it to a ValueAutoCompleteEntity. The “filter” parameter, “explains” how to filter an item of this type.
Here you have an usage example:
The Layout 🎨
The layout is the simplest part.
We want to take any kind of items, since they implement AutoCompleteEntity.
- items ➡️ The list of the items that we want to work with in AutoComplete box;
- itemContent ➡️ This is each AutoComplete item Composable component, with the correspondent item available;
- content ➡️ This is where you will be placing your TextField and applying any changes you want within AutoCompleteScope.
To keep track of the AutoCompleteState, we will be using the remember function.
To draw your content and make AutoCompleteScope available, it must called as follows:
The AutoComplete box itself, is a LazyColumn that will present all the items with the “itemContent” layout.
To fill the items in the LazyColumn, we are making use of the “filteredItems” field in AutoCompleteState, which will keep updated according to the filter.
To draw the AutoComplete box, the LazyColumn uses the following Modifier function:
This Modifier function receives as a parameter the current AutoCompleteDesignScope which is being tracked in AutoCompleteState, and applies everything accordingly.
Where to go from here?
If you want to check the code in a more cleaner way, feel free to check my GitHub. There are also two usage examples and instrumentation tests.
If you want to extend your knowledge on Jetpack Compose, check the following resources:
If you enjoyed it, and if this helped you, you can also consider paying me a coffee. :]