Auto form fill, often shortened to just autofill, is a feature browsers have supported for years now. Most of us use it all the time. I, for one, find it indispensable during tasks such as filling out a registration form or completing a checkout process.
The latest release of Android, Android O, brings similar functionality to Android apps. In other words, Android can now help users fill out forms that belong to all the apps they have installed on their devices. This was a much-awaited feature because typing with a virtual keyboard on a small screen tends to be quite a hassle.
As an app developer, you can use the new Autofill Framework to create your own custom autofill service, a service that decides how to populate an app’s input fields. In this tutorial, I’ll show you how.
Prerequisites
To be able to follow this tutorial, you’ll need:
- Android Studio 2.4 Preview 7 or higher
- An emulator or device running Android O or higher
1. Create a New Project
Fire up Android Studio and create a new project with an empty activity. You must, of course, remember to choose Android 7+ in the Target Android Devices dialog.
This project will need a few widgets that belong to the Design Support Library, so open the app
module’s build.gradle file and add the following compile
dependency to it:
compile 'com.android.support:design:26.+'
Lastly, press the Sync Now button to update the project.
2. Create a Settings Activity
In this tutorial, we’ll be creating an app containing a very simple autofill service that targets only those input fields where the user is expected to type in an email address. Because almost every other app on Google Play today asks for an email address, this service will be quite useful.
Our service obviously needs to know what the user’s email addresses are. Therefore, let us now build an activity where the user can type in and save two email addresses.
Step 1: Define the Layout
As you might expect, the layout of the activity will contain two EditText
widgets where the user can type in his or her email addresses. If you want it to adhere to the guidelines of Material Design, placing the EditText
widgets inside TextInputLayout
containers is a good idea.
Additionally, the layout must have a Button
widget the user can press to save the email addresses.
You are free to place the widgets anywhere you want. Nevertheless, for now, I suggest you place them all inside a LinearLayout
whose orientation is vertical.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/primary" android:hint="Your primary email address" android:inputType="textEmailAddress"/> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/secondary" android:hint="Your other email address" android:inputType="textEmailAddress"/> </android.support.design.widget.TextInputLayout> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/save_button" style="@style/Widget.AppCompat.Button.Colored" android:text="Save" android:onClick="saveEmailAddresses"/> </LinearLayout>
In the above code, you can see that the Button
widget has an onClick
attribute pointing to a method. Click on the yellow light bulb beside this attribute in Android Studio to generate a stub for it in the associated Activity
class.
public void saveEmailAddresses(View view) { // More code will be added here }
Step 2: Save the Email Addresses
We’ll be using a shared preferences file called EMAIL_STORAGE to save our data. You can use the getSharedPreferences()
method of your Activity
class to access the file. Additionally, to be able to write to the file, you must call its edit()
method, which generates a SharedPreferences.Editor
object.
Accordingly, add the following code inside the saveEmailAddresses()
method:
SharedPreferences.Editor editor = getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE).edit();
To fetch the email addresses the user has typed into the EditText
widgets, you’ll have to first get references to them using the findViewById()
method, and then call their getText()
methods.
String primaryEmailAddress = ((EditText)findViewById(R.id.primary)) .getText().toString(); String secondaryEmailAddress = ((EditText)findViewById(R.id.secondary)) .getText().toString();
At this point, you can call the putString()
method of the editor to add the email addresses to the preferences file as two key value pairs. After you do so, don’t forget to call the commit()
method to make your changes permanent.
editor.putString("PRIMARY_EMAIL", primaryEmailAddress); editor.putString("SECONDARY_EMAIL", secondaryEmailAddress); editor.commit();
Step 3: Create a Meta-Data File
The settings activity we created in the previous step is currently just an ordinary activity. To let the Android platform know that it is a settings activity for an autofill service, we must create a meta-data XML file saying so.
Create a new XML file called email_address_filler.xml in the project’s res/xml folder. Inside it, add an <autofill-service>
tag and set the value of its settingsActivity
attribute to the name of your Activity
class.
<?xml version="1.0" encoding="utf-8"?> <autofill-service xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.tutsplus.simplefill.MainActivity"/>
You can now run the app, type in two email addresses, and press the Save button to save them.
3. Create an Autofill Service
Any class that extends the abstract AutoFillService
class can serve as an autofill service. So start by creating a new Java class with File > New > Java Class. In the dialog that pops up, name the class EmailAddressFiller and make sure that you set the value of the Superclass field to AutoFillService
.
Android Studio will now prompt you to generate stubs for two abstract methods: onSaveRequest()
and onFillRequest()
. In this tutorial, we’ll be focusing only the onFillRequest()
method, which is automatically called whenever the user opens an activity—of any app—containing input fields.
@Override public void onFillRequest(AssistStructure assistStructure, Bundle bundle, CancellationSignal cancellationSignal, FillCallback fillCallback) { // More code goes here }
Step 1: Analyze View Hierarchies
An autofill service needs to analyze an app’s user interface and identify input fields it can fill. That’s why the onFillRequest()
method receives an AssistStructure
object, which contains details about all the widgets that are currently visible on the screen. More precisely, it contains a tree of ViewNode
objects.
If you’ve never seen such a tree, I suggest you use the uiautomatorviewer tool, which is part of the Android SDK, to analyze the layout hierarchies of a few apps. For example, here’s what the layout hierarchy of Android’s default mail app looks like:
Naturally, to analyze all nodes of a tree, you need a recursive method. Let’s create one now:
void identifyEmailFields(AssistStructure.ViewNode node, List<AssistStructure.ViewNode> emailFields) { // More code goes here }
As you can see, this method has a ViewNode
and a List
as its parameters. We’ll be using the List
to store all the input fields that expect email addresses.
You might now be wondering how you can programmatically tell if an input field expects an email address. Well, there’s really no foolproof approach you can follow. For now, we’re going to assume that all app developers always give meaningful resource IDs to their input fields. Based on that assumption, we can simply pick all input fields whose resource IDs contain strings such as “email” and “username”.
Accordingly, add the following code to the method:
if(node.getClassName().contains("EditText")) { String viewId = node.getIdEntry(); if(viewId!=null && (viewId.contains("email") || viewId.contains("username"))) { emailFields.add(node); return; } }
Next, whenever we encounter a ViewNode
object that contains more ViewNode
objects, we must recursively call the identifyEmailFields()
method to analyze all its children. The following code shows you how:
for(int i=0; i<node.getChildCount();i++) { identifyEmailFields(node.getChildAt(i), emailFields); }
At this point, we can call the identifyEmailFields()
method inside the onFillRequest()
method and pass the root node of the view hierarchy to it.
// Create an empty list List<AssistStructure.ViewNode> emailFields = new ArrayList<>(); // Populate the list identifyEmailFields(assistStructure .getWindowNodeAt(0) .getRootViewNode(), emailFields);
If our service is unable to identify any input fields for emails, it should do nothing. Therefore, add the following code to it:
if(emailFields.size() == 0) return;
Step 2: Create and Populate Remote Views
If our service does identify an input field it can fill, it must populate a drop-down list that will be shown below the input field. Doing so, however, is not straightforward because neither the input field nor the drop-down list belongs to our app.
To populate the drop-down list, we must use RemoteViews
objects. As its name suggests, a RemoteViews
object is a collection of views that can be displayed in another app.
To initialize a RemoteViews
object, you’ll need a layout XML file. Let’s create one now called email_suggestion.xml. For now, it can contain just one TextView
widget to display an email address.
Accordingly, add the following code to email_suggestion.xml:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/email_suggestion_item" android:textSize="18sp" android:textStyle="bold" android:padding="5dp"> </TextView>
You can now go back to the onFillRequest()
method and create two RemoteViews
objects: one for the primary email, and another for the secondary.
RemoteViews rvPrimaryEmail = new RemoteViews(getPackageName(), R.layout.email_suggestion); RemoteViews rvSecondaryEmail = new RemoteViews(getPackageName(), R.layout.email_suggestion);
The TextView
widgets inside the RemoteViews
objects must display the two email addresses we stored in a shared preferences file earlier. To open the file, use the getSharedPreferences()
method again. Once it’s opened, you can use its getString()
method to fetch both the email addresses.
Finally, to set the contents of the remote TextView
widgets, you must use the setTextViewText()
method.
// Load the email addresses from preferences SharedPreferences sharedPreferences = getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE); String primaryEmail = sharedPreferences.getString("PRIMARY_EMAIL", ""); String secondaryEmail = sharedPreferences.getString("SECONDARY_EMAIL", ""); // Update remote TextViews rvPrimaryEmail.setTextViewText(R.id.email_suggestion_item, primaryEmail); rvSecondaryEmail.setTextViewText(R.id.email_suggestion_item, secondaryEmail);
Step 3: Create Data Sets
We can now use the remote views to create autofill data sets that can be sent to any app. To keep this tutorial from getting too long, we’ll be creating data sets only for the first email input field we encounter. The following code shows how to pick only the first email input field:
AssistStructure.ViewNode emailField = emailFields.get(0);
An autofill data set is nothing but an instance of the Dataset
class, and can be built using the Dataset.Builder
class.
When the user selects one of the email addresses our service shows in the drop-down list, it must set the contents of the associated input field using the setValue()
method of the Dataset.Builder
class. However, you cannot pass a ViewNode
object to the setValue()
method. It actually expects an autofill identifier, which must be obtained by calling the getAutoFillId()
method of the ViewNode
object.
Additionally, to specify the text that must be written into the input field, you must use the AutoFillValue.forText()
method. The following code shows you how:
Dataset primaryEmailDataSet = new Dataset.Builder(rvPrimaryEmail) .setValue( emailField.getAutoFillId(), AutoFillValue.forText(primaryEmail) ).build(); Dataset secondaryEmailDataSet = new Dataset.Builder(rvSecondaryEmail) .setValue( emailField.getAutoFillId(), AutoFillValue.forText(secondaryEmail) ).build();
Before you send the data sets to an app, you must add them to a FillResponse
object, which can be built using the FillResponse.Builder
class. Call its addDataset()
method twice to add both the data sets.
Once the FillResponse
object is ready, pass it as an argument to the onSuccess()
method of the FillCallback
object, which is one of the parameters of the onFillRequest()
method.
FillResponse response = new FillResponse.Builder() .addDataset(primaryEmailDataSet) .addDataset(secondaryEmailDataSet) .build(); fillCallback.onSuccess(response);
Step 4: Update the Manifest
Like all services, the autofill service too must be declared in the project’s AndroidManifest.xml file. While doing so, you must make sure that it is protected by the android.permission.BIND_AUTO_FILL
permission.
This service also needs an <intent-filter>
tag that allows it to respond to the android.service.autofill.AutoFillService
action, and a <meta-data>
tag that points to the meta-data XML file we created in an earlier step.
Accordingly, add the following lines to your manifest file:
<service android:name=".EmailAddressFiller" android:permission="android.permission.BIND_AUTO_FILL"> <meta-data android:name="android.autofill" android:resource="@xml/email_address_filler"/> <intent-filter> <action android:name="android.service.autofill.AutoFillService"/> </intent-filter> </service>
Our autofill service and app are now ready. Build the project and install the app on your device.
4. Activate and Use the Autofill Service
To activate the autofill service, open your device’s Settings app and navigate to Apps & Notifications > Advanced > Default apps > Autofill app. In the next screen, select your app from the list of available autofill apps.
You can now open any app that asks for an email address to see your autofill service in action. For example, here’s what you’d see on the login screens of Instagram and Pinterest:
Conclusion
You now know how to create and use a custom autofill service for Android. Feel free to extend it to support other common fields, such as first name or phone number. You can also try identifying input fields using other attributes, such as labels and hints.
To learn more about the Autofill Framework, do refer to its official documentation. And in the meantime, check out some of our other posts about Android O and Android app development!
Auto form fill, often shortened to just autofill, is a feature browsers have supported for years now. Most of us use it all the time. I, for one, find it indispensable during tasks such as filling out a registration form or completing a checkout process.
The latest release of Android, Android O, brings similar functionality to Android apps. In other words, Android can now help users fill out forms that belong to all the apps they have installed on their devices. This was a much-awaited feature because typing with a virtual keyboard on a small screen tends to be quite a hassle.
As an app developer, you can use the new Autofill Framework to create your own custom autofill service, a service that decides how to populate an app’s input fields. In this tutorial, I’ll show you how.
Prerequisites
To be able to follow this tutorial, you’ll need:
- Android Studio 2.4 Preview 7 or higher
- An emulator or device running Android O or higher
1. Create a New Project
Fire up Android Studio and create a new project with an empty activity. You must, of course, remember to choose Android 7+ in the Target Android Devices dialog.
This project will need a few widgets that belong to the Design Support Library, so open the app
module’s build.gradle file and add the following compile
dependency to it:
compile 'com.android.support:design:26.+'
Lastly, press the Sync Now button to update the project.
2. Create a Settings Activity
In this tutorial, we’ll be creating an app containing a very simple autofill service that targets only those input fields where the user is expected to type in an email address. Because almost every other app on Google Play today asks for an email address, this service will be quite useful.
Our service obviously needs to know what the user’s email addresses are. Therefore, let us now build an activity where the user can type in and save two email addresses.
Step 1: Define the Layout
As you might expect, the layout of the activity will contain two EditText
widgets where the user can type in his or her email addresses. If you want it to adhere to the guidelines of Material Design, placing the EditText
widgets inside TextInputLayout
containers is a good idea.
Additionally, the layout must have a Button
widget the user can press to save the email addresses.
You are free to place the widgets anywhere you want. Nevertheless, for now, I suggest you place them all inside a LinearLayout
whose orientation is vertical.
In the above code, you can see that the Button
widget has an onClick
attribute pointing to a method. Click on the yellow light bulb beside this attribute in Android Studio to generate a stub for it in the associated Activity
class.
public void saveEmailAddresses(View view) { // More code will be added here }
Step 2: Save the Email Addresses
We’ll be using a shared preferences file called EMAIL_STORAGE to save our data. You can use the getSharedPreferences()
method of your Activity
class to access the file. Additionally, to be able to write to the file, you must call its edit()
method, which generates a SharedPreferences.Editor
object.
Accordingly, add the following code inside the saveEmailAddresses()
method:
SharedPreferences.Editor editor = getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE).edit();
To fetch the email addresses the user has typed into the EditText
widgets, you’ll have to first get references to them using the findViewById()
method, and then call their getText()
methods.
String primaryEmailAddress = ((EditText)findViewById(R.id.primary)) .getText().toString(); String secondaryEmailAddress = ((EditText)findViewById(R.id.secondary)) .getText().toString();
At this point, you can call the putString()
method of the editor to add the email addresses to the preferences file as two key value pairs. After you do so, don’t forget to call the commit()
method to make your changes permanent.
editor.putString("PRIMARY_EMAIL", primaryEmailAddress); editor.putString("SECONDARY_EMAIL", secondaryEmailAddress); editor.commit();
Step 3: Create a Meta-Data File
The settings activity we created in the previous step is currently just an ordinary activity. To let the Android platform know that it is a settings activity for an autofill service, we must create a meta-data XML file saying so.
Create a new XML file called email_address_filler.xml in the project’s res/xml folder. Inside it, add an
tag and set the value of its settingsActivity
attribute to the name of your Activity
class.
You can now run the app, type in two email addresses, and press the Save button to save them.
3. Create an Autofill Service
Any class that extends the abstract AutoFillService
class can serve as an autofill service. So start by creating a new Java class with File > New > Java Class. In the dialog that pops up, name the class EmailAddressFiller and make sure that you set the value of the Superclass field to AutoFillService
.
Android Studio will now prompt you to generate stubs for two abstract methods: onSaveRequest()
and onFillRequest()
. In this tutorial, we’ll be focusing only the onFillRequest()
method, which is automatically called whenever the user opens an activity—of any app—containing input fields.
@Override public void onFillRequest(AssistStructure assistStructure, Bundle bundle, CancellationSignal cancellationSignal, FillCallback fillCallback) { // More code goes here }
Step 1: Analyze View Hierarchies
An autofill service needs to analyze an app’s user interface and identify input fields it can fill. That’s why the onFillRequest()
method receives an AssistStructure
object, which contains details about all the widgets that are currently visible on the screen. More precisely, it contains a tree of ViewNode
objects.
If you’ve never seen such a tree, I suggest you use the uiautomatorviewer tool, which is part of the Android SDK, to analyze the layout hierarchies of a few apps. For example, here’s what the layout hierarchy of Android’s default mail app looks like:
Naturally, to analyze all nodes of a tree, you need a recursive method. Let’s create one now:
void identifyEmailFields(AssistStructure.ViewNode node, ListemailFields) { // More code goes here }
As you can see, this method has a ViewNode
and a List
as its parameters. We’ll be using the List
to store all the input fields that expect email addresses.
You might now be wondering how you can programmatically tell if an input field expects an email address. Well, there’s really no foolproof approach you can follow. For now, we’re going to assume that all app developers always give meaningful resource IDs to their input fields. Based on that assumption, we can simply pick all input fields whose resource IDs contain strings such as “email” and “username”.
Accordingly, add the following code to the method:
if(node.getClassName().contains("EditText")) { String viewId = node.getIdEntry(); if(viewId!=null && (viewId.contains("email") || viewId.contains("username"))) { emailFields.add(node); return; } }
Next, whenever we encounter a ViewNode
object that contains more ViewNode
objects, we must recursively call the identifyEmailFields()
method to analyze all its children. The following code shows you how:
for(int i=0; iAt this point, we can call the
identifyEmailFields()
method inside theonFillRequest()
method and pass the root node of the view hierarchy to it.// Create an empty list ListemailFields = new ArrayList<>(); // Populate the list identifyEmailFields(assistStructure .getWindowNodeAt(0) .getRootViewNode(), emailFields); If our service is unable to identify any input fields for emails, it should do nothing. Therefore, add the following code to it:
if(emailFields.size() == 0) return;Step 2: Create and Populate Remote Views
If our service does identify an input field it can fill, it must populate a drop-down list that will be shown below the input field. Doing so, however, is not straightforward because neither the input field nor the drop-down list belongs to our app.
To populate the drop-down list, we must use
RemoteViews
objects. As its name suggests, aRemoteViews
object is a collection of views that can be displayed in another app.To initialize a
RemoteViews
object, you'll need a layout XML file. Let's create one now called email_suggestion.xml. For now, it can contain just oneTextView
widget to display an email address.Accordingly, add the following code to email_suggestion.xml:
You can now go back to the
onFillRequest()
method and create twoRemoteViews
objects: one for the primary email, and another for the secondary.RemoteViews rvPrimaryEmail = new RemoteViews(getPackageName(), R.layout.email_suggestion); RemoteViews rvSecondaryEmail = new RemoteViews(getPackageName(), R.layout.email_suggestion);The
TextView
widgets inside theRemoteViews
objects must display the two email addresses we stored in a shared preferences file earlier. To open the file, use thegetSharedPreferences()
method again. Once it's opened, you can use itsgetString()
method to fetch both the email addresses.Finally, to set the contents of the remote
TextView
widgets, you must use thesetTextViewText()
method.// Load the email addresses from preferences SharedPreferences sharedPreferences = getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE); String primaryEmail = sharedPreferences.getString("PRIMARY_EMAIL", ""); String secondaryEmail = sharedPreferences.getString("SECONDARY_EMAIL", ""); // Update remote TextViews rvPrimaryEmail.setTextViewText(R.id.email_suggestion_item, primaryEmail); rvSecondaryEmail.setTextViewText(R.id.email_suggestion_item, secondaryEmail);Step 3: Create Data Sets
We can now use the remote views to create autofill data sets that can be sent to any app. To keep this tutorial from getting too long, we'll be creating data sets only for the first email input field we encounter. The following code shows how to pick only the first email input field:
AssistStructure.ViewNode emailField = emailFields.get(0);An autofill data set is nothing but an instance of the
Dataset
class, and can be built using theDataset.Builder
class.When the user selects one of the email addresses our service shows in the drop-down list, it must set the contents of the associated input field using the
setValue()
method of theDataset.Builder
class. However, you cannot pass aViewNode
object to thesetValue()
method. It actually expects an autofill identifier, which must be obtained by calling thegetAutoFillId()
method of theViewNode
object.Additionally, to specify the text that must be written into the input field, you must use the
AutoFillValue.forText()
method. The following code shows you how:Dataset primaryEmailDataSet = new Dataset.Builder(rvPrimaryEmail) .setValue( emailField.getAutoFillId(), AutoFillValue.forText(primaryEmail) ).build(); Dataset secondaryEmailDataSet = new Dataset.Builder(rvSecondaryEmail) .setValue( emailField.getAutoFillId(), AutoFillValue.forText(secondaryEmail) ).build();Before you send the data sets to an app, you must add them to a
FillResponse
object, which can be built using theFillResponse.Builder
class. Call itsaddDataset()
method twice to add both the data sets.Once the
FillResponse
object is ready, pass it as an argument to theonSuccess()
method of theFillCallback
object, which is one of the parameters of theonFillRequest()
method.FillResponse response = new FillResponse.Builder() .addDataset(primaryEmailDataSet) .addDataset(secondaryEmailDataSet) .build(); fillCallback.onSuccess(response);Step 4: Update the Manifest
Like all services, the autofill service too must be declared in the project's AndroidManifest.xml file. While doing so, you must make sure that it is protected by the
android.permission.BIND_AUTO_FILL
permission.This service also needs an
tag that allows it to respond to the
android.service.autofill.AutoFillService
action, and atag that points to the meta-data XML file we created in an earlier step.
Accordingly, add the following lines to your manifest file:
Our autofill service and app are now ready. Build the project and install the app on your device.
4. Activate and Use the Autofill Service
To activate the autofill service, open your device's Settings app and navigate to Apps & Notifications > Advanced > Default apps > Autofill app. In the next screen, select your app from the list of available autofill apps.
You can now open any app that asks for an email address to see your autofill service in action. For example, here's what you'd see on the login screens of Instagram and Pinterest:
Conclusion
You now know how to create and use a custom autofill service for Android. Feel free to extend it to support other common fields, such as first name or phone number. You can also try identifying input fields using other attributes, such as labels and hints.
To learn more about the Autofill Framework, do refer to its official documentation. And in the meantime, check out some of our other posts about Android O and Android app development!
Android SDKGet Started With RxJava 2 for Android Android SDKQuick Tip: Working With Custom Fonts in Android O Android ThingsAndroid Things and Machine Learning