为什么要写这篇文章,第一是做了这个功能,记录一下,为后来者提供思路和解决方案;第二是因为我们走了弯路,我们探了雷,所以为后来者提点注意事项;O(∩_∩)O哈哈~
某日,前沿战地提来需求,需要在登录页面添加密码自动填充,自动保存,还可以编辑删除功能。预研一下,并尽快评估一下开发耗时。
后方地勤人员接到命令后,火速赶往pub.dev。找出了最优的库,并demo试之。发现可行(仅仅能填入密码,没覆盖保存和删除功能),反馈于前沿征地。
一、开干
至于那个库就不说了,后面发现前面没测保存和删除功能,而也没有去研究Flutter TextFiled自带的功能(PS: 其实它是自带Autofill功能的,这后面再说)
然后,直接从原生入手,想着原生可以实现的,只要把原生的View写好,功能实现,在Flutter端直接引用原生UI就行了。这倒好,这又开始学了一把Flutter调用显示原生UI的技术(后边再写一文,讲讲),但是iOS实现了,Android这边出了问题,单个显示原生EditText时可以弹出输入框,展示多个EditText时候,输入框都弹不出来。
试了好多办法,单个的EditText不行,用LinearLayout或者其他FrameLayout包裹也不行,实在找不到原因了。就开始Google,却发现了新大陆。
二、撕开迷雾,初见光明
最开始呢,其他同学先做了iOS原生方式实现了KeyChain功能,想着Android也得做原生方式实现Autofill自动填充功能。只能交给我这根独苗苗来了,但是实际过程中,发现flutter调用原生视图方式输入框起不来。只能另寻他法了。
皇天不负有心人,让我发现了新大陆,欣喜若狂~
参考链接如下:
1. 自动填充框架(Android)
2. Level up your Flutter apps with autofill
3. How to implement autofill in your Flutter app
4. 其实最有用的是这个 -> Flutter Tutorial - Autofill Services In 5 Minutes - Android & iOS
前面的1. 自动填充框架(Android)是让我们能了解下Android端的自动填充是个什么玩意儿,原生端是怎么玩的。这对于我们在Flutter端去使用和开发Autofill功能是很有帮助的。
而4. 其实最有用的是这个 -> Flutter Tutorial - Autofill Services In 5 Minutes - Android & iOS这是最有用的,这是个GitHub上的一个Example,具体从哪找的,忘了。
三、Flutter端的开发
在Flutter端如果要实现KeyChain和Autofill功能,其实很简单。Flutter早已帮你实现,我们是纯属想多O(∩_∩)O哈哈~。
3.1 如果说我们要实现账户名密码的自动填充,提示登录成功后弹出保存按钮,只需要实现下面代码:
1. AutofillGroup
2. autofillHints: [AutofillHints.username]
3. TextInput.finishAutofillContext()
Widget buildUsernameAndPassword() => AutofillGroup(
child: Column(
children: [
TextField(
controller: usernameController,
decoration: InputDecoration(labelText: 'Username'),
keyboardType: TextInputType.name,
textInputAction: TextInputAction.next,
autofillHints: [AutofillHints.username],
),
TextField(
controller: passwordController,
decoration: InputDecoration(labelText: 'Password'),
keyboardType: TextInputType.visiblePassword,
autofillHints: [AutofillHints.password],
onEditingComplete: () => TextInput.finishAutofillContext(),
),
],
),
);
这里需要注意的是:
-
- 两个TextField需要被AutofillGroup包裹,它是将AutofillClient(这里是指TextField)进行组合在一起的Widget,这些被AutofillGroup包裹的AutofillClients需要一块构建,它们将被一起填充,比如说,点了用户名,提示账户名密码后,选择后会自动将用户名和密码输入框都填充好。
-
- 看到上面代码中TextField里的autofillHints: [AutofillHints.username]和autofillHints: [AutofillHints.password]这里就和iOS、Android原生类似了。在原生中: Android是在EditText属性中加入android:autofillHints="username";而iOS则是在textField.textContentType = UITextContentTypeUsername或UITextContentTypePassword;
-
- AutofillHints除了AutofillHints.username;AutofillHints.password外还有很多,像email、Address、telephoneNumber、creditCard等等,大家可以进autofill.dart这个类里看看很多种类的填充服务。
-
- 如果说你是输入了新的用户名和密码,需要保存到KeyChain或者AutofillService中去的时候,就需要调用TextInput.finishAutofillContext(),他可以放到Sign In按钮功能登录成功后调用,另外其实它有个可选参数的static void finishAutofillContext({ bool shouldSave = true });默认shouldSave为true表示需要保存,如果不需要保存的时候则可以传shouldSave为false。
void doLogin(BuildContext context) async {
TextInput.finishAutofillContext();
Navigator.push(context, MaterialPageRoute(
builder: (context){
return Container(
color: Colors.redAccent,
);
}
)
);
}
3.2 如果说你点击输入框,弹出了用户名/密码选择器,你选了个密码,发现选错了删除后,再想从AutoFillService选择用户名/密码时,发现它弹不出来了。
那么这时候TextInput.finishAutofillContext()就派上用场,我的做法是在空白处点击,当用户名或者密码有一个为空的时候调用TextInput.finishAutofillContext(shouldSave: false);当调用这句代码的时候代表此处AutofillService结束,输入框也会取消焦点,你再次点击输入框的时候又会再次弹出用户名/密码选择列表。
Scaffold(
body: Container(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
String password = passwordController.text;
String userName = userNameController.text;
if (StringFormatUtil.isStringEmpty(userName) ||
StringFormatUtil.isStringEmpty(password)) {
TextInput.finishAutofillContext(shouldSave: false);
}
},
),
),
),
3.3 上面所说的是,当两个输入框用户名和密码输入框都在一起的时候,当用户名和密码输入框在两个页面的时候咋办?
我这里的情况是这样的:
- 第一个页面输入用户名,并Check正确后在跳转到密码页
-
将用户名带到密码页,并显示用户名,和密码输入框
-
- 针对第一个用户名页面,需要填充UserName时,还是一样玩:需要AutofillGroup包裹TextField(如果没有包裹,点击输入框也可以弹出用户名选择器,但是点击了不能自动填充),autofillHints这个属性就是老生常谈了。
Widget buildUserNameView(BuildContext context) {
return AutofillGroup(
child: TextField(
controller: usernameController,
decoration: InputDecoration(labelText: 'Username'),
keyboardType: TextInputType.name,
textInputAction: TextInputAction.next,
autofillHints: [AutofillHints.username],
),
);
}
-
- 在密码页,因为之前UserName是使用Text控件而不是TextField,如果需要实现Autofill保存功能,只能是两个TextField在上下一起,即:用户名输入框和密码输入框。
如果我们要实现这样的UI同时又满足都是TextField情况,那么只能吧UserName显示的Widget从Text改为TextField,而这个用户名的输入框,没有背景并且不能点击且autofillHints: [AutofillHints.username]。
但是实际过程中发现,如果设置了TextField属性readOnly: true或者enabled: false和autofillHints: [AutofillHints.username],会发现报错。也就是当TextField 不可点击状态和autofillHints,不能同时存在,它们互斥。找到editable_text。我们看到了报错处:会断言有readOnly就不能有autofillHints;有autofillHints就不能readOnly。
assert(
!readOnly || autofillHints == null,
"Read-only fields can't have autofill hints.",
),
这里的话我们就只能魔改,使之成为本地Widget,将这个断言注释了。
import 'package:base_widget/editable_text/text_field.dart' as MargicTextField;
userNameController.value = TextEditingValue(text: userName);
Widget buildPasswordTextFiledWidget() => AutofillGroup(
child: Column(
children: [
//这里是魔改注释了上面断言的本地TextField
MargicTextField.TextField(
controller: usernameController,
textInputAction: TextInputAction.next,
readOnly: true,
textAlign: TextAlign.center,
autofillHints: [AutofillHints.username],
),
TextField(
controller: passwordController,
decoration: InputDecoration(labelText: 'Password'),
keyboardType: TextInputType.visiblePassword,
autofillHints: [AutofillHints.password],
onEditingComplete: () => TextInput.finishAutofillContext(),
),
],
),
);
Flutter端的用法就差不多这样了。
四、Android端运行需要做的事儿
如果在Flutter端已经配置后autofillHints和TextInput.finishAutofillContext()的话,就很简单了。Android手机上需要打开AutofillService服务,因为它默认是关闭的。
路径: 设置 -> 系统 -> 语言与输入法 -> 高级 -> 输入帮助 -> 自动填充服务
藏得比较深,有些手机路径可能不绝对这样,但也差不多。这里选择一个自动填充服务就好了。功能就可以使用了。
五、iOS端的配置
iOS就比较麻烦了:
-
- 首先得将设置 -> 密码 -> 自动填充密码 开关打开
-
- 然后需要配置Web Credentials,如果没有配置的话,能取到密码,但是不能保存。
用户在Safari登录网站时,通常会在iCloud钥匙串中保存用户名和密码。随后,用户可能会打开源于同一个开发者的应用程序来访问同一个帐户。使用webcredentials,应用可以访问为网站存储的证书,无需用户重新输入用户名和密码。用户还可以在应用内创建新帐户,更新密码或删除帐户,Safari会保存并使用这些修改。
具体如何配置看看这篇文章UniversalLinks和Web Credentials配置
将Web Credentials相关文件(需命名为apple-app-site-association)写好,并由后端人员将apple-app-site-association文件上传到HTTPS Web服务器(该步骤需要将写好的apple-app-site-association文件交给服务端人员,让他们完成上传过程。)。放在服务器的根目录或.well-known子目录中。https://
另外需要在工程下做如下配置:
-
xcode打开iOS工程配置Associated Domains
六、结语
到这,KeyChain/Autofill自动填充功能就介绍完了,留个脚印,以备不时之需~