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

如何在无Context的情况下使用Provider调用token刷新方法

问题

我想在接口返回401状态码时,通过Provider调用接口刷新token,但Provider调用函数需要Context。现在在拦截器里拿不到Context,有没有不用Context就能调用该函数的方法?

拦截器代码如下:

class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    if (response.statusCode == 401) {
      // 执行token刷新,获取新token并更新到安全存储
      Provider.of<Auth>(context, listen: false).restoreAccessToken();
    }
    return false;
  }
}

我用Provider存储接口返回的变量,所以没法直接调用函数,restoreAccessToken函数代码如下:

Future<void> restoreAccessToken() async {
    final url = '${Ninecabsapi().urlHost}${Ninecabsapi().login}/$sessionId';

    var response = await http.patch(
      Uri.parse(url),
      headers: {
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': token!
      },
      body: json.encode(
        {"refresh_token": refreshtoken},
      ),
    );
    var userDetails = json.decode(response.body);

    if (response.statusCode == 401) {
      print(userDetails['messages']);
    }

    print(userDetails);
    sessionId = userDetails['data']['session_id'];
    accessToken = userDetails['data']['access_token'];
    accessTokenExpiryDate = DateTime.now().add(
      Duration(seconds: userDetails['data']['access_token_expiry']),
    );
    refreshToken = userDetails['data']['refresh_token'];
    refreshTokenExpiryDate = DateTime.now().add(
      Duration(seconds: userDetails['data']['refresh_token_expiry']),
    );

    print(userDetails);

    notifyListeners();
    final prefs = await SharedPreferences.getInstance();
    final userData = json.encode({
      'sessionId': sessionId,
      'refreshToken': refreshToken,
      'refreshExpiry': refreshTokenExpiryDate!.toIso8601String(),
      'accessToken': accessToken,
      'accessTokenExpiry': accessTokenExpiryDate!.toIso8601String()
    });

    prefs.setString('userData', userData);
    reset();
  }
解决方案

1. 全局持有Auth实例

在App初始化时创建Auth实例并保存为全局变量,拦截器里直接调用该实例的方法:

// 全局变量
Auth? globalAuthInstance;

// 在main函数或App启动时初始化
void main() {
  globalAuthInstance = Auth();
  runApp(
    ChangeNotifierProvider.value(
      value: globalAuthInstance!,
      child: MyApp(),
    ),
  );
}

// 拦截器里调用
class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    if (response.statusCode == 401) {
      await globalAuthInstance?.restoreAccessToken();
    }
    return false;
  }
}

注意要保证全局实例和Provider中使用的是同一个,避免状态不一致。

2. 使用ProviderContainer(Provider 5.0+支持)

如果用的是新版本Provider,可以通过ProviderContainer获取实例,无需Context:

// 初始化时保存ProviderContainer
late final ProviderContainer container;

void main() {
  container = ProviderContainer();
  runApp(
    UncontrolledProviderScope(
      container: container,
      child: MyApp(),
    ),
  );
}

// 拦截器里调用
class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    if (response.statusCode == 401) {
      final auth = container.read(Auth.provider);
      await auth.restoreAccessToken();
    }
    return false;
  }
}

需要先在Auth类中定义对应的provider:

class Auth extends ChangeNotifier {
  static final provider = ChangeNotifierProvider<Auth>((ref) => Auth());
  // ... 其他代码
}

3. 重构刷新逻辑为独立服务类

把token刷新的核心逻辑从Auth类抽离成独立服务,解耦状态管理和业务逻辑:

// 独立的token刷新服务
class TokenRefreshService {
  static Future<Map<String, dynamic>> refreshToken(String currentToken, String refreshToken, String sessionId) async {
    final url = '${Ninecabsapi().urlHost}${Ninecabsapi().login}/$sessionId';

    var response = await http.patch(
      Uri.parse(url),
      headers: {
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': currentToken
      },
      body: json.encode(
        {"refresh_token": refreshToken},
      ),
    );
    return json.decode(response.body);
  }
}

// 修改Auth类的restoreAccessToken方法
Future<void> restoreAccessToken() async {
  final userDetails = await TokenRefreshService.refreshToken(token!, refreshtoken, sessionId);
  
  if (response.statusCode == 401) {
    print(userDetails['messages']);
    return;
  }

  // 原有更新状态和存储的逻辑不变
  sessionId = userDetails['data']['session_id'];
  accessToken = userDetails['data']['access_token'];
  accessTokenExpiryDate = DateTime.now().add(
    Duration(seconds: userDetails['data']['access_token_expiry']),
  );
  // ... 其他状态更新代码
  notifyListeners();
  // ... 保存到SharedPreferences代码
}

// 拦截器里结合全局Auth实例调用
class ExpiredTokenRetryPolicy extends RetryPolicy {
  @override
  Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
    if (response.statusCode == 401) {
      await globalAuthInstance?.restoreAccessToken();
    }
    return false;
  }
}

这种方式让职责更清晰,也方便后续单独维护刷新逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 16:06:25