iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 27
1
Mobile Development

iOS Developer Learning Flutter系列 第 27

iOS Developer Learning Flutter. Lesson26 Biometric

生物辨識使用local_auth

Today Preview

1. 安裝好後第一步首先還是加權限

iOS, 只有Face ID要加

<key>NSFaceIDUsageDescription</key>
<string>Why is my app authenticating using face id?</string>

Android這邊

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.app">
  <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<manifest>

2. 寫code分為三部分

都是透過LocalAuthentication()這個實例

判斷裝置是否支援Biometric

canEvaluatePolicy☘️☘️☘️

  Future<void> _checkBiometrics() async {
    bool canCheckBiometrics;
    canCheckBiometrics = await _localAuth.canCheckBiometrics;

    if (!mounted) return;

    setState(() {
      _canEvaluatePolicy = canCheckBiometrics ? "是" : "否";
    });
  }

判斷是否啟用Biometric

LABiometryType☘️☘️☘️
注意一下 這邊是回傳一個陣列 不是某個類型⚠️⚠️⚠️
所以代表Andorid好像有裝置可以同時支援(難保以後iPhone不會)

  Future<void> _getAvailableBiometrics() async {
    List<BiometricType> availableBiometrics;
    availableBiometrics = await _localAuth.getAvailableBiometrics();

    if (!mounted) return;

    setState(() {
      if (availableBiometrics.isEmpty) {
        _biometryType = noEnrolledWording;
      } else {
        switch (availableBiometrics.first) {
          case BiometricType.face:
            _biometryType = "點我驗證Face ID";
            break;
          case BiometricType.fingerprint:
            _biometryType = "點我驗證Touch ID";
            break;
          default:
            _biometryType = noEnrolledWording;
            break;
        }
      }
    });
  }

驗證Biometric

evaluatePolicy☘️☘️☘️

  1. 如果是Face ID的話, 第一次驗證這邊就會要求權限
  2. 看起來只會回傳驗證通過或拒絕, 不會拿到拒絕原因(LAError)⚠️⚠️⚠️
  3. PlatformException不是拒絕原因, 而是系統相關的例外, 目前有六種: PasscodeNotSet, NotEnrolled, NotAvailable, OtherOperatingSystem, LockedOut and PermanentlyLockedOut
  4. useErrorDialogs
    若為false, 才會出現PlatformException;
    若為ture(預設), 則驗證失敗會Alert

    點了會進app的設定頁, 滿方便的
  5. 若useErrorDialogs為true, 且有給iOSAuthStrings的話, 可以客製文案
  6. stickyAuth
    若為ture, 驗證中App進背景不會回傳失敗(例如接到電話), 回到App時繼續驗證(黏住了)
    若為false(預設), App進背景即失敗
  Future<void> _authenticate() async {
    print("驗證中");
    bool authenticated = false;

    try {
      authenticated = await _localAuth.authenticateWithBiometrics(
          localizedReason: 'Scan your fingerprint to authenticate',
          stickyAuth: true,
          useErrorDialogs: true,
          iOSAuthStrings: IOSAuthMessages(
              lockOut: "鎖",
              goToSettingsButton: "設定",
              goToSettingsDescription: "請設定",
              cancelButton: "算了"
          )
      );
    } on PlatformException catch (e) {
      print("例外");
      print(e);
    }

    if (!mounted) return;

    final result = authenticated ? "驗證成功" : "驗證失敗";
    scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(result)));

  }

3. 其他

3.1

上面第2節中的三個function中可以看到
官方用了個小技巧mounted
來判斷await是否回來了

3.2

如果是Android的話
可以透過_localAuth.stopAuthentication()來中斷驗證

4. 完碼

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/auth_strings.dart';
import 'package:local_auth/local_auth.dart';

class LessonPageLocalAuthentication extends StatefulWidget {
  @override
  _LessonPageLocalAuthenticationState createState() => _LessonPageLocalAuthenticationState();
}

class _LessonPageLocalAuthenticationState extends State<LessonPageLocalAuthentication> {

  final noEnrolledWording = "未啟用生物辨識";

  final scaffoldKey = GlobalKey<ScaffoldState>();
  final LocalAuthentication _localAuth = LocalAuthentication();
  String _canEvaluatePolicy = "";
  String _biometryType = "";

  Future<void> _checkBiometrics() async {
    bool canCheckBiometrics;
    canCheckBiometrics = await _localAuth.canCheckBiometrics;

    if (!mounted) return;

    setState(() {
      _canEvaluatePolicy = canCheckBiometrics ? "是" : "否";
    });
  }

  Future<void> _getAvailableBiometrics() async {
    List<BiometricType> availableBiometrics;
    availableBiometrics = await _localAuth.getAvailableBiometrics();

    if (!mounted) return;

    setState(() {
      if (availableBiometrics.isEmpty) {
        _biometryType = noEnrolledWording;
      } else {
        switch (availableBiometrics.first) {
          case BiometricType.face:
            _biometryType = "點我驗證Face ID";
            break;
          case BiometricType.fingerprint:
            _biometryType = "點我驗證Touch ID";
            break;
          default:
            _biometryType = noEnrolledWording;
            break;
        }
      }
    });
  }

  Future<void> _authenticate() async {
    print("驗證中");
    bool authenticated = false;

    try {
      authenticated = await _localAuth.authenticateWithBiometrics(
          localizedReason: 'Scan your fingerprint to authenticate',
          stickyAuth: true,
          useErrorDialogs: true,
          iOSAuthStrings: IOSAuthMessages(
              lockOut: "鎖",
              goToSettingsButton: "設定",
              goToSettingsDescription: "請設定",
              cancelButton: "算了"
          )
      );
    } on PlatformException catch (e) {
      print("例外");
      print(e);
    }

    if (!mounted) return;

    final result = authenticated ? "驗證成功" : "驗證失敗";
    scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(result)));

  }

  @override
  void initState() {
    super.initState();
    _checkBiometrics();
    _getAvailableBiometrics();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        key: scaffoldKey,
        appBar: AppBar(
          title: Text("Local Authentication"),
        ),
        body: Container(
            alignment: Alignment.center,
            child: Column(
              children: [
                SizedBox(height: 100),
                Text("您的裝置是否支援生物辨識:$_canEvaluatePolicy"),
                OutlineButton(
                    child: Text(_biometryType),
                    onPressed: _biometryType == noEnrolledWording ? null : _authenticate
//                    onPressed: _authenticate
                )
              ],
            )
        )
    );
  }
}

本集內容Android版請見:iOS Developer Learning Android. Lesson 21

下集預告:Map

最後提供一下github.com/mark33699/IDLF


上一篇
iOS Developer Learning Flutter. Lesson25 Push Notification
下一篇
iOS Developer Learning Flutter. Lesson27 Map + Location
系列文
iOS Developer Learning Flutter30

尚未有邦友留言

立即登入留言