It’s official, Flutter 1.20 is here. Among all the goodies and improvements, mobile autofill gets highlighted in the tagline, and even has a gif demonstrating how slick the UX is. Unfortunately, there are not yet many examples, tutorials, or gotchas available, hence the born of this article.
From Android Developer Guide:
Filling out forms is a time-consuming and error-prone task. Users can easily get frustrated with apps that require such actions. The autofill framework improves the user experience by providing the following benefits:
Less time spent in filling fields. Autofill saves users from re-typing information.
Minimize user input errors. Typing is prone to errors, especially on mobile devices. Minimizing the need to type information also minimizes typos.
Get started with a form with NO autofill
We will start with a simple form with addresses, a phone number, and a field that should not be autofilled later.
The code is available on this repo. If you focus on any of these fields, you should see the cursor blinking, but no autofill popup.
Install Android Autofill Framework sample
You might already have some autofill services (like LastPass, Bitwarden) or you might not. Either way, it’s recommended that you don’t use them during development, but use a reference implementation of the service instead. Follow the instruction from step 1 of this CodeLabs to have the Android Autofill Framework sample setup on your device.
After the sample is installed, choose the
Debug Autofill Service . Now, go to any app written using OEM widgets (such as those written with Android SDK, React Native, or Ionic, but not Flutter nor Unity), focus on any text field, you will see the autofill service appears undesirably. Generally, we want our app to be autofill-able in certain fields only, such as
password, etc, but not others, such as
The reason autofill service appears somehow unexpectedly is that Android apps utilizing OEM widgets can infer autofill semantics from its widget ID if the developers do not explicitly code so. The inference works most of the time, but not always.
Unlike Android XML, your Flutter app does not automatically have autofill capability, even if you recompile your codebase against Flutter 1.20. In fact, there’s no equivalent of
android:importantForAutofill tag at all. The reason is that, while in Android, an element can have
android:id tag, it’s not the case with Flutter.
First autofill popup in our Flutter app
The first step is quite easy, all of the widgets composed of
EditableText or its ancestors, such as
autofillHints parameter that we can set.
Even though this param accepts a list (to be exact, iterable) of string, it’s recommended that you don’t pass in an arbitrary string, but make use of static values from
AutofillHints, a collection of commonly used autofill hints. It will help you avoid typos, and also ensure cross-platform compatibility.
Now, go back to our app and focus on the “Shipping address 1”, we’ll see autofill popup appears. Let’s tap on that, and do the same for “Shipping address 2”.
So far, to fill those 2 lines of address, the user has to tap on the autofill popup twice. It’s more laborious than ideal, and also more error-prone. If the user, say, have more than one address, for example:
11 Wall St
New York, NY 10005
1600 Pennsylvania Avenue NW
Washington, DC 20500
there’s a chance the user mistakenly choose different entries for different fields, causing corrupted data:
11 Wall St
Washington, DC 20500
AutofillGroup can be used in this situation so that the user tap only once, and the service will autofill all related fields that are children of the nearest
AutofillGroup in the widget tree.
Add autofill capability to the rest of our app
This step is quite straight-forward, and is similar to previous ones.
At this point, we can be quite happy with our app. However, the phone number is usually filled together with the shipping address. In other words, the phone number field should be in the same a
AutofillGroup with address line 1 and address line 2. But, in our widget, it’s not quite easy, because, in the middle, we have the billing address fields as well.
At this point, I was about to use
GlobalKey to get
AutofillGroupState, but it turned out that there’s a simpler way.
AutofillGroup can be nested, and the descendant fields will only be grouped together under the nearest
AutofillGroup widget in the widget tree.
We can see that credit card fields are still grouped correctly, even though it’s nested inside another
AutofillGroup meant to be for the shipping address and phone number.
Some gotchas to keep in mind
In the talk Quick Ways to Ensure App Compatibility with Android Autofill there was a warning that an anti-pattern in Android SDK, view hierarchy is created
onStart() life cycle, will break the UX of autofill by making the autofill service loopingly asking the user authentication. Luckily, we cannot have it in Flutter, as Flutter is (semi-)declarative, and it’s impossible to imperatively create/paint view on life cycle events.
However, be careful with the nested
AutofillGroup in step 4 above, if you have conditionally build widgets, and in our case,
if (!isSameAddress). Try autofill the credit card field, and then uncheck the box, we can see the value of the credit card fields gets moved to the billing address fields.
The solution is to use UniqueKey. The explanation deserves a whole article, [Flutter] Keys! What are they good for?, but the short answer is that due to the way Flutter framework do the tree-diffing for the rebuild, it cannot reliably distinguish widgets of the same type (in this case,
TextFormField) without extra help from
As mentioned in the release announcement, autofill has been one of the #1 most requested Flutter features for a while, and we finally get it, for both Android and iOS (web is on the way, and we don’t have autofill ecosystem on desktop yet). It turns out that, unlike on Android, the developers have to explicitly modify their code to support autofill. Besides, gotchas with fields and autofill in Flutter are quite different from those in Android.
The example code and diff used in this article is available at