restoreSession method
Restores a session when the database no longer contains a user row.
Secure credentials take precedence over legacy old TAT credentials.
hadStoredLogin remains true when stored data is unusable or cannot be
verified because of a transient failure, allowing startup to skip the
intro.
Never throws and never blocks longer than _restoreLoginTimeout: this
runs before runApp, so a storage failure, a malformed portal response,
or a stalled connection must degrade to "open the login screen" instead
of leaving the app without any UI.
Implementation
Future<({User? user, bool hadStoredLogin})> restoreSession() async {
var hadStoredLogin = false;
String? username;
String? password;
try {
final secureUsername = await _secureStorage.read(key: _usernameKey);
final securePassword = await _secureStorage.read(key: _passwordKey);
if (secureUsername != null || securePassword != null) {
hadStoredLogin = true;
if (secureUsername?.isNotEmpty == true &&
securePassword?.isNotEmpty == true) {
username = secureUsername;
password = securePassword;
} else {
await _clearSecureCredentials();
}
}
} catch (error) {
log('Failed to read stored credentials: $error', name: 'AuthRepository');
}
if (username == null || password == null) {
try {
final preferences = await SharedPreferences.getInstance();
final legacyUserData = preferences.get(_legacyUserDataKey);
if (legacyUserData != null) {
hadStoredLogin = true;
try {
final decoded = switch (legacyUserData) {
final String value => jsonDecode(value),
_ => null,
};
if (decoded
case {
'account': final String account,
'password': final String legacyPassword,
}
when account.isNotEmpty && legacyPassword.isNotEmpty) {
username = account;
password = legacyPassword;
} else {
await _clearLegacyCredentials(preferences);
}
} on FormatException {
await _clearLegacyCredentials(preferences);
}
}
} catch (error) {
log(
'Failed to read legacy credentials: $error',
name: 'AuthRepository',
);
}
}
if (username == null || password == null) {
if (hadStoredLogin) {
_onSessionDestroyed(const LoginException(.credentialsMissing));
}
return (user: null, hadStoredLogin: hadStoredLogin);
}
try {
final user = await login(
username,
password,
).timeout(_restoreLoginTimeout);
return (user: user, hadStoredLogin: true);
} on LoginException catch (exception) {
await _clearCredentials();
_onSessionDestroyed(exception);
return (user: null, hadStoredLogin: true);
} catch (error) {
// Everything else is treated as transient — a network failure, a stalled
// connection, or a captive portal answering `login.do` with HTML that
// fails to parse. Keep the credentials and let the user retry from the
// login screen rather than aborting startup.
log('Failed to restore session: $error', name: 'AuthRepository');
return (user: null, hadStoredLogin: true);
}
}