简体   繁体   中英

Variable coming back null after just being set properly - Flutter

No matter what I do, my variable _authorizedUser is coming back null.

I'm demoing biometric authentication for users, once user enters their username, if it matches one in the local database and biometric auth is a success, then user will automatically be logged in. This is currently hardcoded for username and password...

I can't get this to work in any configuration, there must be something wrong I haven't discovered. No matter what I do authorizedUser comes back null, even after confirming with log that it IS NOT NULL. Somehow it is bouncing back to null & I am getting null error. Previously I had _authorizedUser as a late variable but that was even worse. I think this may be an issue for Github but I wanted to make sure first.

Weirdly enough, if I login the old fashioned way entering username & password with button press, calling the same login method it works as intended. But as soon as I call login from inside my biometric authentication method, it's a no go. Am I using Either wrong or something? Any help greatly appreciated:)

enum NotifierState { initial, loading, loaded }

class AuthenticationEndpoints extends ChangeNotifier {
  // Authorized user
  ParseUser? _authorizedUser;
  ParseUser? get authorizedUser => _authorizedUser;
  Future<void> _setAuthorizedUser(ParseUser? user) async {
    _authorizedUser = user;
    log(_authorizedUser.toString());                      /// This logs proper object...
    notifyListeners();
  }

  /// Login user
  ///
  /// returns either Failure or Success
  /// Failure => [DialogError]
  /// Success => [_authorizedUser] = [user] & Navigate [HomeScreen]
  Future<void> login(BuildContext context, String username, String password) async {
    Either<Failure, ParseUser> user = await AuthenticationServices.login(username, password);
    user.fold(
      (failure) {
        showDialog(context: context, builder: (context) => DialogError(failure: Failure(failure.message)));
      },
      (user) {                        /// Correctly returns user object from server
        _setAuthorizedUser(user);        
        Get.to(const HomeScreen());
      },
    );
  }

  /// Biometric authentication
  ///
  /// returns either Failure or true
  /// Failure => [DialogError]
  /// bool => [true] (authentication successful)
  Future<void> biometricAuthentication(BuildContext context) async {
    Either<Failure, bool> biometricAuthenticationAttempt =
        await AuthenticationServices.biometricAuthentication();
    biometricAuthenticationAttempt.fold(
      (failure) =>
          showDialog(context: context, builder: (context) => DialogError(failure: Failure(failure.message))),
      (authenticated) async {
        await login(context, '0010', '0010');
      },
    );
  }

...

}

Widget using variable


final authenticationEndpoints = ChangeNotifierProvider((ref) => AuthenticationEndpoints());

class Welcome extends ConsumerWidget {
  const Welcome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    String? user = ref.watch(authenticationEndpoints).authorizedUser?.username;
    log(user.toString());                     /// This logs NULL but should not :(
    return Scaffold(
      body: SizedBox(
        width: double.maxFinite,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          children: [
            const Text('WELCOME'),
            (user == null) ? const Text('') : Text(user),
            const SizedBox(height: 50.0),
            ElevatedButton(
                onPressed: () async {
                  await ref.read(authenticationEndpoints).logout(context);
                },
                child: const Text('LOGOUT')),
          ],
        ),
      ),
    );
  }
}


class AuthenticationServices {
  static final _localAuth = LocalAuthentication();

  /// Login user
  ///
  static Future<Either<Failure, ParseUser>> login(String username, String password) async {
    try {
      // Cloud call - login
      final ParseResponse loginAttempt = await AuthenticationCloudCode.login(username, password);
      // Decrypt user object
      final dynamic decryptedUser = await CryptographicServices.fromServerDecryption(
        loginAttempt.result['data'],
        loginAttempt.result['keys'],
      );
      // Create user object from decrypted data
      final user = ParseUser(decryptedUser['username'], decryptedUser['password'], decryptedUser['email'])
        ..set('sessionToken', decryptedUser['sessionToken'])
        ..set('objectId', decryptedUser['objectId'])
        ..set('className', decryptedUser['className'])
        ..set('name', decryptedUser['name'])
        ..set('createdAt', decryptedUser['createdAt'])
        ..set('updatedAt', decryptedUser['updatedAt'])
        ..set('publicKey', decryptedUser['publicKey'])
        ..setACL(decryptedUser['ACL']);
      // Assign user session id
      ParseCoreData().setSessionId(user['sessionToken']!);
      return Right(user);
    } on SocketException {
      return Left(Failure('Internet connection issue'));
    } on ParseError catch (error) {
      return Left(Failure(error.message));
    } on TimeoutException {
      return Left(Failure('Request took too long'));
    }
  }

  /// Local authentication
  ///
  /// Biometric authentication attempt
  /// Failure => error.message
  /// bool => true (successfully authenticated)
  static Future<Either<Failure, bool>> biometricAuthentication() async {
    try {
      final bool biometricsAvailable = await _localAuth.canCheckBiometrics;
      if (biometricsAvailable) {
        List<BiometricType> availableBiometrics = await _localAuth.getAvailableBiometrics();
        if (availableBiometrics.contains(BiometricType.face)) {
          final localAuthResult = await _localAuth.authenticate(
            biometricOnly: true,
            iOSAuthStrings: const IOSAuthMessages(
              cancelButton: 'CANCEL',
              goToSettingsButton: 'SETTINGS',
              goToSettingsDescription: 'You must setup FaceID',
              lockOut: 'Re-enable FaceID to use biometric authentication',
            ),
            androidAuthStrings: const AndroidAuthMessages(
              cancelButton: 'CANCEL',
              goToSettingsButton: 'SETTINGS',
              goToSettingsDescription: 'You must setup FaceID',
              signInTitle: 'Face ID Required',
            ),
            localizedReason: 'Use face ID to login',
            useErrorDialogs: true,
            stickyAuth: true,
          );
          return Right(localAuthResult);
        } else if (availableBiometrics.contains(BiometricType.fingerprint)) {
          final localAuthResult = await _localAuth.authenticate(
            biometricOnly: true,
            iOSAuthStrings: const IOSAuthMessages(
              cancelButton: 'CANCEL',
              goToSettingsButton: 'SETTINGS',
              goToSettingsDescription: 'You must setup FingerprintID',
              lockOut: 'Re-enable FingerprintID to use biometric authentication',
            ),
            androidAuthStrings: const AndroidAuthMessages(
              cancelButton: 'CANCEL',
              goToSettingsButton: 'SETTINGS',
              goToSettingsDescription: 'You must setup FingerprintID',
              signInTitle: 'Use FingerprintID to sign in',
            ),
            localizedReason: 'Use biometric login',
            useErrorDialogs: true,
            stickyAuth: true,
          );
          return Right(localAuthResult);
        } else {
          return Left(Failure('Biometric authentication type not available'));
        }
      } else {
        return Left(Failure('Biometric authentication not available on your device'));
      }
    } on PlatformException catch (error) {
      return Left(Failure(error.message!));
    }
  }

...

}

Logs

[log] {"className":"_User","objectId":"IfXR5pexoB","createdAt":"2022-02-12T06:25:58.633Z","updatedAt":"2022-02-12T06:25:58.633Z","username":"0010","email":"0010@0010.com","sessionToken":"r:edc1cb11618aa97a5c44b837a55d1a85","name":"0010","publicKey":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl5yvZa3nhA9QCUey159hp92a9FFxf1sxap2tVxkikBATQDcXWKcJdkm+vUaVuLPvA6273XqWgV8AVlyIbf+KafWmr8kVXQ9HiJRVMUJb9dg+SKyUbZcoHs23RjUynSCoSKrbaLI1raaI5MorD/dxW2TkL/3UbnIr34rLywrs3Arbx8VD25yVbkA3qp+/N+6sQzK6eb9S8/FDWGn3GOYodMzbPVTDAX6AVwC8dONGrbBmZTm64ZIrLl69nMnRjoOy+MQWnT3dkniJSe5qe3tWXIxQu26lVPAubuY8T3a6KzqCvGa5MQtgA0dsfE6ud9LLtxXTWMVmSSg2yzf6AViZswIDAQAB","ACL":{"*":{"read":true},"IfXR5pexoB":{"read":true,"write":true}}}

[log] null

I just solved this. Pretty much a shot in the dark with one hand tied behind my back, I tried something out of randomness & desperation.

Solution:

I have set _authorizedUser to static
static ParseUser? _authorizedUser;

On deeper inspection I realize why static works is because somehow, somewhere along the line a second instance was made for _authorizedUser. I haven't a clue how and don't really care as having a static variable seems safer anyway. Hope this could help someone else.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM