stacktrace2234
stacktrace2234

Reputation: 1500

Flutter: How to synchronize synchron static method with asynchron non-static method?

When I start the app, it should check if it is possible to use biometric authentication (fingerprint/face id). I have a class that checks this and the login page need the result of it. I have the following code:

class LocalAuthenticationUtil with ChangeNotifier {
  static LocalAuthentication _auth = LocalAuthentication();
  static List<BiometricType> biometricTypes;
  static bool haveBiometrics = true;
  bool _biometricAuthenticated = true;

  static LocalAuthenticationUtil _instance;

  static LocalAuthenticationUtil getInstance() {
    if (_instance == null) {
      _instance = LocalAuthenticationUtil();
      print("GetInstance CanCheckBiometrics before");
      _instance._canCheckBiometrics();
      print("GetInstance CanCheckBiometrics after");
      if (haveBiometrics) {
        _instance.addListener(() {
          _instance.authenticate();
        });
        _instance.authenticate();
      }
    }

    return _instance;
  }

  Future<void> _canCheckBiometrics() async {
    print("CanCheckBiometrics before");
    haveBiometrics = await _auth.canCheckBiometrics;
    print("CanCheckBiometrics after");
    if (haveBiometrics) {
      biometricTypes = await _auth.getAvailableBiometrics();
    }
  }

  set biometricAuthenticated(bool value) {
    if (_biometricAuthenticated != value) {
      _biometricAuthenticated = value;
      notifyListeners();
    }
  }

When the code runs this is the result:

I/flutter (23495): GetInstance CanCheckBiometrics before
I/flutter (23495): CanCheckBiometrics before
I/flutter (23495): GetInstance CanCheckBiometrics after
I/flutter (23495): CanCheckBiometrics after

While the order that I want to happen is:

I/flutter (23495): GetInstance CanCheckBiometrics before
I/flutter (23495): CanCheckBiometrics before
I/flutter (23495): CanCheckBiometrics after
I/flutter (23495): GetInstance CanCheckBiometrics after

Upvotes: 1

Views: 268

Answers (2)

Gpack
Gpack

Reputation: 2193

cameron1024 is right. What you need to do is to create a StatefulWidget which will redirect the user once the check is completed.

class AuthWidget extends StatefulWidget {
  AuthWidget({Key key}) : super(key: key);

  @override
  _AuthWidgetState createState() => _AuthWidgetState();
}

class _AuthWidgetState extends State<AuthWidget> {
  @override
  void initState() { 
    super.initState();
    checkBiometrics(); // perform the check asynchronously and then use Navigator.of(context).push/replace
  }
  @override
  Widget build(BuildContext context) {
    // Display a loader while checking
    return CircularProgressIndicator();
  }
}

Upvotes: 0

cameron1024
cameron1024

Reputation: 10136

You're not awaiting _instance._canCheckBiometrics();

Dart executes synchonously until it hits an await, at which point the function immediately returns, but "remembers where it was", so it can continue where it left off when the awaited Future completes:

Here, when you call _instance._canCheckBiometrics(), it immediately runs the first print statement, then hits the await _auth.canCheckBiometrics and immediately returns a Future representing the result of _instance._canCheckBiometrics().

Simply replace _instance._canCheckBiometrics() with await _instance._canCheckBiometrics() and it should work.

BTW, you can create an analysis_options.yaml file to customise your linter warnings for your project. One in particular, called unawaited_futures warns you when you have a Future-returning function in an async context that doesn't have an await. This is usually an error, but you can suppress it manually if you're certain. This rule often helps catch bugs like this. To use the linter, check out: https://dart.dev/guides/language/analysis-options#enabling-linter-rules

Upvotes: 1

Related Questions