1. Code
  2. Mobile Development
  3. Android Development

How to Use Android O's Autofill Framework

Scroll to top

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:

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.

Target android devices dialogTarget android devices dialogTarget 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:

1
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.

1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout 
3
    xmlns:android="https://schemas.android.com/apk/res/android"
4
    android:layout_width="match_parent"
5
    android:layout_height="match_parent"
6
    android:orientation="vertical"
7
    android:padding="16dp">
8
9
    <android.support.design.widget.TextInputLayout
10
        android:layout_width="match_parent"
11
        android:layout_height="wrap_content">
12
        <EditText
13
            android:layout_width="match_parent"
14
            android:layout_height="wrap_content"
15
            android:id="@+id/primary"
16
            android:hint="Your primary email address"
17
            android:inputType="textEmailAddress"/>
18
    </android.support.design.widget.TextInputLayout>
19
20
    <android.support.design.widget.TextInputLayout
21
        android:layout_width="match_parent"
22
        android:layout_height="wrap_content">
23
        <EditText
24
            android:layout_width="match_parent"
25
            android:layout_height="wrap_content"
26
            android:id="@+id/secondary"
27
            android:hint="Your other email address"
28
            android:inputType="textEmailAddress"/>
29
    </android.support.design.widget.TextInputLayout>
30
31
    <Button
32
        android:layout_width="match_parent"
33
        android:layout_height="wrap_content"
34
        android:id="@+id/save_button"
35
        style="@style/Widget.AppCompat.Button.Colored"
36
        android:text="Save"
37
        android:onClick="saveEmailAddresses"/>
38
39
</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.

1
public void saveEmailAddresses(View view) {
2
    // More code will be added here

3
}

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:

1
SharedPreferences.Editor editor = 
2
        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.

1
String primaryEmailAddress = 
2
                ((EditText)findViewById(R.id.primary))
3
                            .getText().toString();
4
                            
5
String secondaryEmailAddress = 
6
                ((EditText)findViewById(R.id.secondary))
7
                            .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.

1
editor.putString("PRIMARY_EMAIL", primaryEmailAddress);
2
editor.putString("SECONDARY_EMAIL", secondaryEmailAddress);
3
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.

1
<?xml version="1.0" encoding="utf-8"?>
2
<autofill-service
3
    xmlns:android="http://schemas.android.com/apk/res/android"
4
    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.

Settings activity runningSettings activity runningSettings activity running

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.

Create new class dialogCreate new class dialogCreate new class dialog

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.

1
@Override
2
public void onFillRequest(AssistStructure assistStructure, 
3
                          Bundle bundle,
4
                          CancellationSignal cancellationSignal,
5
                          FillCallback fillCallback) {
6
7
    // More code goes here

8
9
}

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:

View hierarchy of default mail appView hierarchy of default mail appView hierarchy of default mail app

Naturally, to analyze all nodes of a tree, you need a recursive method. Let's create one now:

1
void identifyEmailFields(AssistStructure.ViewNode node, 
2
                     List<AssistStructure.ViewNode> emailFields) {
3
    // More code goes here

4
}

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:

1
if(node.getClassName().contains("EditText")) {
2
    String viewId = node.getIdEntry();
3
    if(viewId!=null && (viewId.contains("email") 
4
                        || viewId.contains("username"))) {
5
        emailFields.add(node);
6
        return;
7
    }
8
}

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:

1
for(int i=0; i<node.getChildCount();i++) {
2
    identifyEmailFields(node.getChildAt(i), emailFields);
3
}

At this point, we can call the identifyEmailFields() method inside the onFillRequest() method and pass the root node of the view hierarchy to it.

1
// Create an empty list

2
List<AssistStructure.ViewNode> emailFields = new ArrayList<>();
3
4
// Populate the list

5
identifyEmailFields(assistStructure
6
                    .getWindowNodeAt(0)
7
                    .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:

1
if(emailFields.size() == 0)
2
    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:

1
<?xml version="1.0" encoding="utf-8"?>
2
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
3
    android:layout_width="wrap_content"
4
    android:layout_height="wrap_content"
5
    android:id="@+id/email_suggestion_item"
6
    android:textSize="18sp"
7
    android:textStyle="bold"
8
    android:padding="5dp">
9
</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.

1
RemoteViews rvPrimaryEmail = 
2
            new RemoteViews(getPackageName(), 
3
                            R.layout.email_suggestion);
4
                            
5
RemoteViews rvSecondaryEmail = 
6
            new RemoteViews(getPackageName(),
7
                            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.

1
// Load the email addresses from preferences

2
SharedPreferences sharedPreferences = 
3
                getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE);
4
5
String primaryEmail = 
6
                sharedPreferences.getString("PRIMARY_EMAIL", "");
7
String secondaryEmail = 
8
                sharedPreferences.getString("SECONDARY_EMAIL", "");
9
10
// Update remote TextViews

11
rvPrimaryEmail.setTextViewText(R.id.email_suggestion_item, 
12
                               primaryEmail);
13
rvSecondaryEmail.setTextViewText(R.id.email_suggestion_item,
14
                                 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:

1
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:

1
Dataset primaryEmailDataSet = 
2
            new Dataset.Builder(rvPrimaryEmail)
3
                        .setValue(
4
                            emailField.getAutoFillId(),
5
                            AutoFillValue.forText(primaryEmail)
6
                        ).build();
7
8
Dataset secondaryEmailDataSet = 
9
            new Dataset.Builder(rvSecondaryEmail)
10
                        .setValue(
11
                            emailField.getAutoFillId(),
12
                            AutoFillValue.forText(secondaryEmail)
13
                        ).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.

1
FillResponse response = new FillResponse.Builder()
2
                            .addDataset(primaryEmailDataSet)
3
                            .addDataset(secondaryEmailDataSet)
4
                            .build();
5
6
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:

1
<service android:name=".EmailAddressFiller"
2
    android:permission="android.permission.BIND_AUTO_FILL">
3
    <meta-data android:name="android.autofill"
4
        android:resource="@xml/email_address_filler"/>
5
    <intent-filter>
6
        <action android:name="android.service.autofill.AutoFillService"/>
7
    </intent-filter>
8
</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.

Autofill app selection screenAutofill app selection screenAutofill app selection screen

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:

Autofill suggestions displayed for two popular appsAutofill suggestions displayed for two popular appsAutofill suggestions displayed for two popular apps

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!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.