You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何缓存授权数据(access_token)?macOS应用免重复登录实现问询

解决方案

1. 应用启动时的异步判断逻辑

由于SharedPreferences的操作是异步的,直接在home属性中判断令牌会导致初始化时机问题,我们可以用FutureBuilder来处理异步校验流程:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '你的macOS应用',
      home: FutureBuilder<String?>(
        // 异步获取缓存的access_token
        future: PreferencesProvider().getAccess(),
        builder: (context, snapshot) {
          // 初始化加载阶段,可自定义启动页(比如显示应用Logo)
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Scaffold(
              body: Center(child: CircularProgressIndicator()),
            );
          }
          // 根据令牌是否存在,跳转对应页面
          return snapshot.data != null ? const MainWidget() : const StartScreen();
        },
      ),
    );
  }
}

2. 优化PreferencesProvider的初始化逻辑

当前实现中每次调用存储/读取方法都会重新执行_initPreferences,可以修改为仅初始化一次,避免重复异步操作:

class PreferencesProvider {
  static PreferencesProvider? _instance;
  SharedPreferences? _preferences;
  Future<void>? _initFuture; // 存储初始化Future,确保只执行一次

  factory PreferencesProvider() {
    _instance ??= PreferencesProvider._();
    return _instance!;
  }

  PreferencesProvider._() {
    _initFuture = _initPreferences();
  }

  Future<void> _initPreferences() async {
    _preferences = await SharedPreferences.getInstance();
  }

  // 所有操作前先等待初始化完成
  Future<void> _ensureInitialized() async {
    await _initFuture;
  }

  Future<void> setAccess(String value) async {
    await _ensureInitialized();
    await _preferences!.setString("ACCESS_TOKEN", value);
  }
  
  Future<String?> getAccess() async {
    await _ensureInitialized();
    return _preferences!.getString("ACCESS_TOKEN");
  }

  Future<void> clearAccess() async {
    await _ensureInitialized();
    await _preferences!.remove("ACCESS_TOKEN");
  }

  Future<void> setRefresh(String value) async {
    await _ensureInitialized();
    await _preferences!.setString("REFRESH_TOKEN", value);
  }

  Future<String?> getRefresh() async {
    await _ensureInitialized();
    return _preferences!.getString("REFRESH_TOKEN");
  }

  // 补充你用到的setUserId方法(原代码中调用了但未实现)
  Future<void> setUserId(String value) async {
    await _ensureInitialized();
    await _preferences!.setString("USER_ID", value);
  }
}

3. 修正授权Cubit中的异步存储问题

原代码中链式调用异步方法不会等待操作完成,可能导致令牌未存储完成就执行后续逻辑,需要改为等待每个异步操作:

if (data['success']) {
  final prefs = PreferencesProvider();
  await prefs.clearAccess();
  await prefs.setUserId(data['userId']);
  await prefs.setAccess(data['accessToken']);
  await prefs.setRefresh(data['refreshToken']);
  _apiProvider.setToken(data["accessToken"]);
}

如果想提升效率,也可以用Future.wait并行执行存储操作:

if (data['success']) {
  final prefs = PreferencesProvider();
  await Future.wait([
    prefs.clearAccess(),
    prefs.setUserId(data['userId']),
    prefs.setAccess(data['accessToken']),
    prefs.setRefresh(data['refreshToken']),
  ]);
  _apiProvider.setToken(data["accessToken"]);
}

额外提示

如果需要处理令牌过期的情况,可以在启动时不仅判断令牌是否存在,还可以调用接口校验令牌有效性,再决定跳转页面。


内容的提问来源于stack exchange,提问作者newbie2210

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 21:45:30