【问题标题】:How to handle the authentication token and and GraphQL client with Riverpod?如何使用 Riverpod 处理身份验证令牌和 GraphQL 客户端?
【发布时间】:2021-08-18 12:54:10
【问题描述】:

我们正在使用 GraphQL 将我们的后端与我们的移动应用程序连接起来。我们使用 Riverpod 来处理全局状态、依赖注入等。

我们正在使用提供程序来处理 GraphQL 客户端,一开始没有任何身份验证标头,而用户未进行身份验证(这将处理未经身份验证的请求,如登录或注册),在用户通过身份验证后,提供令牌的令牌提供程序已更新,它必须更新作为应用程序每个存储库用户的客户端提供程序:

final StateProvider<String> tokenProvider =
    StateProvider<String>((_) => '', name: "Token Provider");
final graphQLClientProvider = Provider<GraphQLClient>(
  (ref) {
    final String token = ref.watch(tokenProvider).state;

    final Link _link = HttpLink(
        'http://192.168.1.36:1337/graphql',
        defaultHeaders: {
          if (token != '') 'Authorization': 'Bearer $token',
        });

    return GraphQLClient(
      cache: GraphQLCache(),
      link: _link,
    );
  },
  name: "GraphQL Client Provider",
);

这里有太多问题:

  1. 更新令牌后,客户端发生了变化,由此导致更新令牌的函数无法正常完成:
[ +141 ms] E/flutter ( 8361): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Bad state: Tried to use AuthNotifier after `dispose` was called.
[        ] E/flutter ( 8361): 
[        ] E/flutter ( 8361): Consider checking `mounted`.
[        ] E/flutter ( 8361): 
[        ] E/flutter ( 8361): #0      StateNotifier._debugIsMounted.<anonymous closure> (package:state_notifier/state_notifier.dart:128:9)
[        ] E/flutter ( 8361): #1      StateNotifier._debugIsMounted (package:state_notifier/state_notifier.dart:135:6)
[        ] E/flutter ( 8361): #2      StateNotifier.state= (package:state_notifier/state_notifier.dart:155:12)
[        ] E/flutter ( 8361): #3      AuthNotifier.signIn (package:thesis_cancer/features/auth/application/auth.notifier.dart:84:7)
[        ] E/flutter ( 8361): <asynchronous suspension>
[        ] E/flutter ( 8361): #4      _LoginCardState._submit (package:flutter_login/src/widgets/auth_card.dart:503:15)
[        ] E/flutter ( 8361): <asynchronous suspension>
[        ] E/flutter ( 8361): 

这意味着当令牌更新时,接收存储库的身份验证通知器取决于提供的客户端被更新/更改(遵循链):

final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.read(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);

final authNotifierProvider = StateNotifierProvider<AuthNotifier, AuthState>(
  (ref) => AuthNotifier(
    authRepository: ref.watch(authRepositoryProvider),
    dataStore: ref.watch(dataStoreRepositoryProvider),
    profileRepository: ref.watch(profileRepositoryProvider),
    tokenController: ref.watch(tokenProvider.notifier),
    userController: ref.watch(userEntityProvider.notifier),
  ),
  name: "Authentication Notifier Provider",
);

条件中断的通知函数(if):

Future<String?> signIn({
    required String username,
    required String password,
  }) async {
    try {
      final Map<String, dynamic> rawUser = await authRepository.signIn(
          identifier: username, password: password) as Map<String, dynamic>;
      final User sessionUser = User.fromJson(rawUser);
      if (sessionUser.confirmed != false) {
        tokenController.state = sessionUser.token!; <-- Bug begins here
      }
      await dataStore.writeUserProfile(sessionUser);
      userController.state = sessionUser;
      state = const AuthState.loggedIn();
    } on LogInFailureByBadRequest {
      return "E-posta veya şifre geçersiz.";
    } on LogInFailure catch (error) {
      return error.toString();
    }
  }
  1. 虽然更新令牌时客户端似乎已更改,但客户端本身并未反映更改(它不包括令牌的身份验证标头),因此会破坏 GraphQL 客户端:

the issue we opened 上的cmets 之后,我们将ProviderReference 添加到我们的通知程序(在this question 之后),但它不起作用:

 AuthNotifier(
    this._ref, {
    required this.authRepository,
    required this.profileRepository,
    required this.dataStore,
    required this.tokenController,
    required this.userController,
  }) : super(const AuthState.loading());
  final ProviderReference _ref;
  final ProfileRepository profileRepository;
  final AuthRepository authRepository;
  final DataStoreRepository dataStore;
  final StateController<User?> userController;
  final StateController<String> tokenController;

此时,我们不明白这里有什么问题。

更新

我们在 StateNotifier 及其提供者中删除了此更改。

通过读取在存储库中获取客户端并不能避免错误:

使用 Riverpod 处理授权令牌和 GraphQL 客户端的正确方法是什么?

谢谢。

【问题讨论】:

  • 您使用的是什么版本的 Riverpod?
  • 我还在用hooks_riverpod: ^0.14.0+4
  • 添加使用 authNotifier 的片段

标签: flutter dart graphql riverpod


【解决方案1】:

问题是authRepositoryProvider:

final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.read(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);

在此片段中,authRepositoryProvider 使用的是不带令牌的 GraphQLClient。更新令牌时,graphQLClientProvider 状态更新良好,但authRepositoryProvider 不是因为您使用ref.read 避免重建authRepositoryProvider 状态。这意味着authRepositoryProvider 知道graphQLClientProvider 的已处置(和卸载)状态,这就是错误消息的原因。

有两种解决方案:

  1. 使用ref.watch:
final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.watch(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);
  1. ref.read 作为参数传递:
final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.read),
  name: 'Auth Repository Provider',
);

class AuthRepository {
  AuthRepository(this.read)

  final Reader read;

  GraphQLClient get client => read(graphQLClientProvider);
}

注意使用readwatch,第一个不响应更改,但可以在任何地方使用,而第二个响应提供者的状态更新,但只能在另一个提供者构造函数中使用,或者在提供对 watchref.watch 的访问的小部件中,具体取决于 Riverpod 的版本。

另外,你的代码还有一个问题:

  final User sessionUser = User.fromJson(rawUser);
  if (sessionUser.confirmed != false) {
    // In this moment, the token is updated,
    // then the QrahpQLClient is updated,
    // then the AuthNotifier is recreated,
    // i.e the current is disposed
    // and now becomes the previous
    tokenController.state = sessionUser.token!; <-- Bug begins here
  }
  await dataStore.writeUserProfile(sessionUser);
  userController.state = sessionUser;

  // In this moment you are updating the state of a disposed notifier
  state = const AuthState.loggedIn();

为了提供解决方案,有必要定义哪些元素将重建AuthNotifer.state,而不是重新创建通知程序。

您必须考虑基于ref.read 访问另一个通知程序,并在 StateController 构造函数中手动创建订阅。

将来,当 Riverpod 1.0 发布时,使用ref.listen 会很容易解决此问题,但现在您需要重写代码。您可以在bloc-to-bloc 交流指南中获得灵感:https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication

【讨论】:

  • 好的,我尝试了第一个,但问题仍然存在,所以,我想解决方案是第二个。谢谢。
  • 第二个没有成功。
【解决方案2】:

我认为错误出在您的 AuthNotifier 中:

  if (sessionUser.confirmed != false) {
    tokenController.state = sessionUser.token!; <-- you are updating a dependencie of your actual AuthNotifier object
  }
  await dataStore.writeUserProfile(sessionUser); <-- during the await a new object is created and the actual instance is disposed so this line will throw

【讨论】:

  • 是的,这就是问题所在,那么,我们该如何解决呢?
  • 在您的 authNotifierProvider 中将 tokenController: ref.watch(tokenProvider.notifier) 替换为 tokenController: ref.read(tokenProvider.notifier)
猜你喜欢
  • 2012-09-29
  • 1970-01-01
  • 1970-01-01
  • 2019-05-23
  • 2015-11-03
  • 1970-01-01
  • 1970-01-01
  • 2015-12-13
  • 1970-01-01
相关资源
最近更新 更多