03-Flutter 鸿蒙实战 03:登录注册与本地会话持久化

03. Flutter 鸿蒙实战 03:登录注册与本地会话持久化

用 AuthController、AuthScope 和 shared_preferences_ohos 管理登录态。

登录态入口

登录态管理在 lib/features/auth 目录下,核心文件是 auth_controller.dartauth_repository.dartsession_store.dartauth_scope.dart。项目启动时,TalkingAlbumApp 创建 AuthController 并调用 initialize()。这个过程决定首屏显示登录页还是应用主页面。

dart 复制代码
_authController = AuthController(
  apiClient: _apiClient,
  sessionStore: SharedPreferencesSessionStore(),
);
_authController.initialize();

MaterialApp 的 home 是 _AuthGate,它根据状态切页面:

dart 复制代码
return switch (auth.status) {
  AuthStatus.initializing => const Scaffold(
      body: Center(child: CircularProgressIndicator()),
    ),
  AuthStatus.signedOut => const AuthPage(),
  AuthStatus.signedIn => const AppShell(initialRoute: AppRoutes.home),
};

这个结构比在登录页里手动判断更干净。登录态是应用级状态,不应该散落在各个页面里。

SessionStore 抽象

项目没有直接在 AuthController 里写 SharedPreferences,而是抽象了一个 SessionStore

dart 复制代码
abstract interface class SessionStore {
  Future<StoredSession?> read();
  Future<void> write(String token, AuthUser user);
  Future<void> clear();
}

当前实现是 SharedPreferencesSessionStore,底层使用 shared_preferences_ohos 适配鸿蒙:

dart 复制代码
class SharedPreferencesSessionStore implements SessionStore {
  static const _tokenKey = 'talking_album_token';
  static const _userKey = 'talking_album_user';

  @override
  Future<StoredSession?> read() async {
    final preferences = await SharedPreferences.getInstance();
    final token = preferences.getString(_tokenKey);
    final rawUser = preferences.getString(_userKey);
    if (token == null || rawUser == null) return null;
    try {
      final json = jsonDecode(rawUser);
      if (json is! Map<String, dynamic>) return null;
      return StoredSession(token: token, user: AuthUser.fromJson(json));
    } on FormatException {
      await clear();
      return null;
    }
  }
}

这里有一个细节:本地用户 JSON 解析失败时会 clear(),不会继续使用损坏会话。移动端本地存储并不总是可靠,升级版本、字段变化、异常写入都可能导致旧数据不能解析。

登录流程

AuthRepository.login 只负责请求接口和解析返回,AuthController.login 负责状态变更、token 写入和 UI 通知:

dart 复制代码
Future<bool> login(String account, String password) async {
  error = null;
  notifyListeners();
  try {
    final result = await _repository.login(account.trim(), password);
    StoriesRepository.clearCache();
    _apiClient.accessToken = result.token;
    user = result.user;
    await _sessionStore.write(result.token, result.user);
    status = AuthStatus.signedIn;
    notifyListeners();
    return true;
  } catch (exception) {
    error = _message(exception);
    notifyListeners();
    return false;
  }
}

登录成功后清理故事缓存是合理的。不同用户看到的故事、照片、家人关系都不一样,如果不清缓存,切账号后可能看到上个账号的数据。

启动恢复登录

initialize() 会先读本地 session,如果存在 token,就请求 /api/auth/me 校验:

dart 复制代码
Future<void> initialize() async {
  final session = await _sessionStore.read();
  if (session == null) {
    status = AuthStatus.signedOut;
    notifyListeners();
    return;
  }
  try {
    user = await _repository.me(session.token);
    await _sessionStore.write(session.token, user!);
    status = AuthStatus.signedIn;
  } catch (_) {
    await _sessionStore.clear();
    _apiClient.accessToken = null;
    StoriesRepository.clearCache();
    status = AuthStatus.signedOut;
  }
  notifyListeners();
}

这个设计避免了一个常见问题:本地有 token 但服务端已经失效,App 还误以为登录成功。这里通过 /me 做一次真实校验,失败就清理本地会话。

退出登录

退出登录不只是回到登录页,还要清理 token、用户、错误状态和业务缓存:

dart 复制代码
Future<void> logout() async {
  await _sessionStore.clear();
  _apiClient.accessToken = null;
  StoriesRepository.clearCache();
  user = null;
  error = null;
  status = AuthStatus.signedOut;
  notifyListeners();
}

在多用户应用里,退出时清业务缓存很关键。照片故事类数据带有强烈用户属性,不能跨账号残留。

扩展拆解

登录态和业务缓存的关系

登录成功后,项目会调用 StoriesRepository.clearCache()。这一步很重要,因为故事、照片、地图、家人关系都和当前账号有关。如果切换账号时不清缓存,可能出现新账号看到旧账号故事的严重问题。

dart 复制代码
final result = await _repository.login(account.trim(), password);
StoriesRepository.clearCache();
_apiClient.accessToken = result.token;
user = result.user;
await _sessionStore.write(result.token, result.user);
status = AuthStatus.signedIn;

退出登录同样要清理:

dart 复制代码
await _sessionStore.clear();
_apiClient.accessToken = null;
StoriesRepository.clearCache();
user = null;
status = AuthStatus.signedOut;

这说明登录模块不是孤立的表单功能,它会影响整个应用的数据边界。移动端项目里 token、用户信息、业务缓存必须同步管理,不能只保存 token。

注册和资料更新

注册只负责创建账号,不直接登录。资料更新、头像上传成功后,会更新当前用户并写回本地 session。这样应用重启后,昵称和头像不会回到旧状态。

dOut;

复制代码
这说明登录模块不是孤立的表单功能,它会影响整个应用的数据边界。移动端项目里 token、用户信息、业务缓存必须同步管理,不能只保存 token。

## 注册和资料更新

注册只负责创建账号,不直接登录。资料更新、头像上传成功后,会更新当前用户并写回本地 session。这样应用重启后,昵称和头像不会回到旧状态。
相关推荐
AM艾玛1 小时前
Flutter 环境搭建实录
vscode·flutter
明亮 Blog1 小时前
Wi-Fi 6/7的物联网“芯”机遇:高带宽低延时如何重塑智慧工厂的AGV物流革命
服务器·网络·数据库
子非鱼94271 小时前
01-Flutter 鸿蒙实战 01:会说话的相册项目架构拆解
flutter·华为·harmonyos
不羁的木木1 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第10篇:多设备协同——构建星闪网络实现数据同步
网络·华为·harmonyos
春卷同学2 小时前
HarmonyOS掌上记账APP开发实践第29篇:条件渲染与 ForEach — 动态 UI 的构建策略
ui·华为·harmonyos
清欢渡---2 小时前
HCIP-第五章vrrp
网络·网络安全·hcip
Inhand陈工2 小时前
当发电机装上“智慧大脑”:IG502+白鹰能源管家打造中东发电机远程监控与防盗方案
大数据·网络·物联网·阿里云·能源·边缘计算·信息与通信
春卷同学2 小时前
HarmonyOS掌上记账APP开发实践第22篇:Scroll + List 虚拟列表 — 长列表性能优化全攻略
性能优化·list·harmonyos
不羁的木木2 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第4篇:广播通信——一对多广播应用实现
华为·harmonyos