Flutter自动填充服务插件flutter_autofill_service的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter自动填充服务插件flutter_autofill_service的使用

集成 Flutter 与 Android 平台的自动填充服务。

特性

  • 初始自动填充匹配结果受身份验证步骤的限制(用户未请求且您的代码未授权的情况下不会自动显示用户数据)。
  • 可以返回最多10个匹配结果到相关应用(在示例应用中手动完成,但您可能希望自动化此过程)。
  • 提供一个“选择不同的”选项结果,允许用户返回到您的应用选择之前未匹配的应用或网站的不同结果。
  • 支持保存新提供的数据。
  • 示例应用展示了所有主要功能。

使用

查看示例应用以了解 API,并特别注意该项目中的 <code>AndroidManifest.xml</code> 文件。该文件是您可以配置字符串和可绘制资源覆盖以自定义集成到项目中的地方。

除了明显的虚假响应和按钮外,示例应用和实际应用之间有一个显著的区别:

如果您引用的元数据中的可绘制资源在您的应用的其他地方未被引用,AGP 将默认将其排除在发布 APK/AAB 构建之外作为其资源缩减过程的一部分。这是因为该资源仅在运行时通过字符串名称加载。要解决这个问题,创建(或修改)<code>android/app/src/main/res/raw/keep.xml</code> 文件,其中包含一个如下的 <code>resources</code> 元素:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@drawable/ic_example_drawable_file_name_1_*,@drawable/ic_example_drawable_file_name_2_*"
 />

如果您想在真实世界的应用程序中看到这一点的演示,请查看 Kee Vault 应用——Kee Vault

计划

  • 响应任何现有或新 Android 版本的匹配代码算法中的不准确性。我们不确定当前行为是否完美,但认为可以改进一些启发式方法。
  • 调查 Android 11+ 的 IME 集成支持(我们已经开始这项工作,但目前存在一个 Android 错误,阻止了 IME 与身份验证步骤一起使用,因此不确定是否值得继续进行这种向用户展示 Android 匹配结果的替代方法)。
  • 查明为什么 Android 自动填充示例建议我们以特殊方式处理聚焦的元素(我们遵循他们的指导并附加“(聚焦)”到可见标题,但在实际使用中很少看到这一点,我们不知道是否有任何显著的意义或我们是否应该做额外的工作)。
  • 帮助库消费者确定用户发起的保存请求是否已经处理(目前,消费者需要自行跟踪这一点,并且涉及故意重复动作的一些边缘情况可能会导致一些混淆)。
  • 看看我们是否可以在公共 Flutter 库网站上发布它。

仅支持 Android,但也许有一天可以包括桌面或 Web 支持,如果您有任何想法,请打开一个问题或拉取请求。iOS(至少截至 v15)不支持通过 Dart/Flutter 插件进行自动填充(必须是一个本地扩展),因此没有计划为此平台提供支持。

贡献

请根据需要打开问题、讨论或 PR。我们不期望会有大量的活动,所以至少一开始我们会保持一切非正式。但是,请注意 CODE_OF_CONDUCT.md——非正式的方法并不意味着我们可以容忍偏见或滥用。

此项目受到并包含了一些来自 hpoul 的 autofill_service 包的代码的启发。

完整示例

以下是完整的示例代码,展示了如何使用 flutter_autofill_service 插件:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_autofill_service/flutter_autofill_service.dart';
import 'package:logging/logging.dart';
import 'package:logging_appenders/logging_appenders.dart';

final _logger = Logger('main');

void main() {
  Logger.root.level = Level.ALL;
  PrintAppender().attachToLogger(Logger.root);
  _logger.info('Initialized logger.');
  runApp(const MyApp(false));
}

void autofillEntryPoint() {
  Logger.root.level = Level.ALL;
  PrintAppender().attachToLogger(Logger.root);
  _logger.info('Initialized logger.');
  runApp(const MyApp(true));
}

class MyApp extends StatefulWidget {
  const MyApp(this.launchedByAutofillService);
  final bool launchedByAutofillService;

  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  bool? _hasEnabledAutofillServices;
  AutofillMetadata? _autofillMetadata;
  bool? _fillRequestedAutomatic;
  bool? _fillRequestedInteractive;
  bool? _saveRequested;
  AutofillPreferences? _preferences;

  [@override](/user/override)
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _updateStatus();
  }

  // 平台消息是异步的,所以我们初始化在一个异步方法中。
  Future<void> _updateStatus() async {
    _hasEnabledAutofillServices = await AutofillService().hasEnabledAutofillServices;
    _autofillMetadata = await AutofillService().getAutofillMetadata();
    _saveRequested = _autofillMetadata?.saveInfo != null;
    _fillRequestedAutomatic = await AutofillService().fillRequestedAutomatic;
    _fillRequestedInteractive = await AutofillService().fillRequestedInteractive;
    _preferences = await AutofillService().getPreferences();
    setState(() {});
  }

  [@override](/user/override)
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  [@override](/user/override)
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      await _updateStatus();
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    _logger.info(
        'Building AppState. defaultRouteName:${WidgetsBinding.instance.window.defaultRouteName}');
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('插件示例应用'),
        ),
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text(widget.launchedByAutofillService
                  ? '自动填充启动'
                  : '标准启动'),
              Text(
                  '\nhasEnabledAutofillServices: $_hasEnabledAutofillServices\n'),
              Text('fillRequestedAutomatic: $_fillRequestedAutomatic\n'),
              Text('fillRequestedInteractive: $_fillRequestedInteractive\n'),
              Text('SuppliedAutofillMetadata: $_autofillMetadata\n'),
              Text('启用保存: ${_preferences?.enableSaving}\n'),
              ElevatedButton(
                child: const Text('切换启用保存设置'),
                onPressed: () async {
                  await AutofillService().setPreferences(AutofillPreferences(
                    enableDebug: _preferences!.enableDebug,
                    enableSaving: !_preferences!.enableSaving,
                  ));
                  await _updateStatus();
                },
              ),
              ElevatedButton(
                child: const Text('请求设置自动填充服务'),
                onPressed: () async {
                  _logger.fine('开始请求。');
                  final response = await AutofillService().requestSetAutofillService();
                  _logger.fine('请求完成 $response');
                  await _updateStatus();
                },
              ),
              ElevatedButton(
                child: const Text('模拟自动填充结果'),
                onPressed: () async {
                  _logger.fine('开始请求。');
                  final response = await AutofillService().resultWithDatasets([
                    PwDataset(
                      label: '用户和密码 1',
                      username: 'dummyUsername1',
                      password: 'dpwd1',
                    ),
                    PwDataset(
                      label: '用户和密码 2',
                      username: 'dummyUsername2',
                      password: 'dpwd2',
                    ),
                    PwDataset(
                      label: '只有用户名',
                      username: 'dummyUsername2',
                      password: '',
                    ),
                    PwDataset(
                      label: '只有密码',
                      username: '',
                      password: 'dpwd2',
                    ),
                  ]);
                  _logger.fine('resultWithDatasets $response');
                  await _updateStatus();
                },
              ),
              ElevatedButton(
                child: const Text('模拟交互式自动填充结果'),
                onPressed: () async {
                  _logger.fine('开始请求。');
                  final response = await AutofillService().resultWithDataset(
                    label: '这是标签 3',
                    username: 'dummyUsername3',
                    password: 'dpwd3',
                  );
                  _logger.fine('resultWithDatasets $response');
                  await _updateStatus();
                },
              ),
              Visibility(
                visible: _saveRequested ?? false,
                child: ElevatedButton(
                  child: const Text('模拟保存操作'),
                  onPressed: () async {
                    _logger.fine('TODO: 保存提供的数据。');
                    await AutofillService().onSaveComplete();
                    _logger.fine('保存完成');
                    await _updateStatus();
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter自动填充服务插件flutter_autofill_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自动填充服务插件flutter_autofill_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_autofill_service插件的一个详细代码案例。这个插件允许你在Flutter应用中启用自动填充服务,特别是在处理敏感信息(如密码、信用卡信息等)时非常有用。

步骤 1: 添加依赖

首先,在你的pubspec.yaml文件中添加flutter_autofill_service的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_autofill_service: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

步骤 2: 配置AndroidManifest.xml

在Android的AndroidManifest.xml文件中,你需要添加一些权限来确保自动填充服务能够正常工作。同时,你也需要声明一个服务来启用自动填充。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <!-- 添加必要的权限 -->
    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />

    <!-- 声明自动填充服务 -->
    <service
        android:name=".YourAutofillService"
        android:permission="android.permission.BIND_AUTOFILL_SERVICE">
        <intent-filter>
            <action android:name="android.service.autofill.AutofillService" />
        </intent-filter>

        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/autofill_service_config" />
    </service>

    <!-- 其他配置 -->

</manifest>

你还需要在res/xml/目录下创建一个名为autofill_service_config.xml的文件来配置自动填充服务:

<!-- res/xml/autofill_service_config.xml -->
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged|typeViewClicked"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canRetrieveWindowContent="true"
    android:canRequestTouchExplorationMode="false"
    android:canRequestEnhancedWebAccessibility="true"
    android:notificationTimeout="100"
    android:description="@string/autofill_service_description"
    android:settingsActivity="com.example.yourapp.SettingsActivity"/>

步骤 3: 创建自动填充服务类

接下来,你需要创建一个Java或Kotlin类来扩展AutofillService。这里以Kotlin为例:

// YourAutofillService.kt
package com.example.yourapp

import android.accessibilityservice.AccessibilityService
import android.content.Context
import android.os.Build
import android.service.autofill.AutofillService
import android.service.autofill.FillRequest
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.widget.EditText

class YourAutofillService : AutofillService() {

    override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val dataset = request.dataset
            for (id in dataset.ids) {
                val autofillValue = AutofillValue.forText("default_value") // 你可以根据需求动态生成值
                callback.onFill(id, autofillValue)
            }
        }
        callback.onSuccess()
    }

    override fun onCancel() {
        // 处理取消请求
    }
}

步骤 4: 在Flutter中使用插件

最后,在你的Flutter代码中,你可以使用flutter_autofill_service插件来检查自动填充服务的状态或触发某些操作。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:flutter_autofill_service/flutter_autofill_service.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Autofill Service Demo'),
        ),
        body: Center(
          child: AutofillServiceButton(),
        ),
      ),
    );
  }
}

class AutofillServiceButton extends StatefulWidget {
  @override
  _AutofillServiceButtonState createState() => _AutofillServiceButtonState();
}

class _AutofillServiceButtonState extends State<AutofillServiceButton> {
  String _autofillStatus = "Checking...";

  @override
  void initState() {
    super.initState();
    _checkAutofillServiceStatus();
  }

  Future<void> _checkAutofillServiceStatus() async {
    bool isAvailable = await FlutterAutofillService.isAvailable();
    setState(() {
      _autofillStatus = isAvailable ? "Autofill Service Available" : "Autofill Service Not Available";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(_autofillStatus),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () {
            // 触发某些与自动填充相关的操作
            // 例如,你可以在这里调用一个方法来填充表单字段
          },
          child: Text('Trigger Autofill'),
        ),
      ],
    );
  }
}

请注意,FlutterAutofillService.isAvailable()只是一个示例方法,实际插件的API可能会有所不同。你应该查阅插件的官方文档来获取最准确的信息。

这个代码案例提供了一个基本的框架,展示了如何在Flutter应用中集成和使用flutter_autofill_service插件。你可以根据具体需求进行进一步的自定义和扩展。

回到顶部