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.