Blog post
In the constantly evolving world of software development, implementing new technologies can often lead us down uncharted paths and dead ends. In this blog post, I’ll share my journey of implementing email and password autofill - the missing puzzle piece of Jetpack Compose.
The significance of autofill
It's not just about streamlining the login process; it's about enhancing user experience, maintaining security, and balancing convenience and privacy. This isn't a luxury; in today's fast-paced digital landscape, users should be able to log into their apps with a single tap.
The realization that Compose doesn't offer full-fledged support for this indispensable feature prompted me to explore alternatives. Eventually, I discovered a workaround involving the reliable, not-forgotten old legend - EditText.
Incorporating EditText into Jetpack Compose
Now, let's roll up our sleeves, step back into the past, and resurrect the XML layout for our password input field. This code snippet marks the beginning of our coding adventure:
Attribute definitions
The layout shown above is the foundation that we'll soon wrap within our Composable, but before we delve into that, let's ensure a crystal-clear understanding of its components. While many of these attributes may seem familiar and self-explanatory, let's dissect a few that are crucial:
- autofillHints="password" - This attribute is the key to a seamless autofill. By explicitly specifying ‘password’ as the autofill hint, we signal the system that this EditText is intended for password input, ensuring flawless integration with autofill features.
- background="@null" - Setting the background to ‘@null’ is significant, as it will eliminate any default UI. This enables us to customize the UI in Compose fully, and this EditText will just be here to handle input from the user.
- theme="@style/EditTextStyle" - The only way to customize the cursor and selection color in the EditText is by defining a custom style:
APIs for better DevX
Our next step is creating the Composable function for our password input field. However, let’s take one final detour 😅, and I promise this is the final step in our preparation for the input field Composable.
It's crucial to emphasize the importance of designing a developer-friendly API for our Composable. A well-crafted API shields developers from digging into unnecessary implementation details. Even though the old XML View system follows an imperative structure, our Composable wrapper should follow the Jetpack Compose declarative syntax. This ensures a seamless developer experience and hides the XML wizardry under the hood.
The names for our Composables hold significant weight. Therefore, I opted for the descriptive and intuitive names PasswordTextField and EmailTextField. The names I picked should resonate well with seasoned Compose developers familiar with BasicTextField. And that’s exactly the API we want to use. To maintain a seamless developer experience, we are adopting a BasicTextField API, utilizing parameters like value and onValueChange.
Of course, we should never forget the Modifier parameter, which should be included in nearly all Composables. This ensures developers can make the necessary adjustments effortlessly.
Usage of the Composable wrapper
Taking all these considerations into account, the usage of our Composables will look something like this:
Of course, in the example above, we store the current state of fields within the Composable. However, it's always recommended to implement more advanced state management techniques, such as storing it in a StateFlow in a ViewModel.
Initial implementation
Now, finally, let's unveil the implementation of the PasswordInputField. Initially, we will focus on the bare-bones implementation, where we inflate the previously presented XML and add a rounded border around the input field.
Keep in mind that in this blog post, I’m presenting only the snippets of code related to passwords. However, once you implement the password, simply apply the same logic to the email code.
Tailoring input fields with Compose
Now, let’s go on to enhance our PasswordInputField. First, let’s add placeholder text to guide users in entering their password.
As mentioned earlier, and just like we did with the rounded corners above, all UI logic that’s not related to user input will be implemented using Compose. This would be the Text Composable for placeholder text when the value is blank:
Adding a toggle
If we execute this code now, users could input their password. However, a password field often includes a toggle button for switching between hidden and visible password states. So, let’s incorporate the toggle functionality.
Although the toggle button itself will be implemented in Compose, we must also synchronize the password text with the underlying EditText View. Fortunately, AndroidView offers a perfect solution. The update lambda we can pass to the AndroidView gets invoked each time the PasswordInputField is recomposed, as opposed to the factory lambda which is invoked only on the first composition. Thus, the update lambda is the ideal spot for handling this task.
There is also a nifty little workaround in the solution – since the input field cursor resets whenever a transformation method is applied to hide or reveal the password, a manual restoration of the cursor is necessary.
Adjusting for Autofill API
We’re nearly done, but we still need to address a minor issue. The issue lies in the fact that the Autofill API was originally designed for the old View system and isn't specifically intended for Compose.
The prompt for saving the credentials will be triggered after the current activity is finished. Of course, there are legacy reasons for that - when the Autofill API was designed, it was expected that each screen would be a separate Activity. Therefore, a login Activity would always finish, and a new activity would be started after a successful login. Fortunately, the API that triggers the saving of credentials is exposed, enabling us to forcefully initiate the storage of current inputs. This allows us to adapt to Compose's single-activity architecture.
Another related issue occurs when a user manually closes the application while there’s some input in the email and password input fields. You can already guess what is going to happen; and the answer is yes, the app will close, and the prompt for saving the credentials will appear 😁. To avoid this inconvenience and ensure a smoother user experience, we address this problem by clearing the input fields when the app is closed:
Then, we manually trigger the storing of credentials after a successful login. As mentioned above, this is the API exposed that allows us to trigger the storing of current inputs:
And we’re done! Ta-da!
And that wraps it up! We’ve successfully navigated through the essential steps to implement email and password autofill with Jetpack Compose. (Well, at least for the password input field.)
The complete demo project, which also includes the email logic and the entire implementation, is available on the Github.
This is the final result 🎉
Thank you for your time and attention, and I hope that this blog proves to be a useful time-saver for you, my fellow developers. Happy coding!