Riverpod,在外部 BuildContext 和 Provider 中读取状态

Riverpod, reading state in outside BuildContext and Provider

我正在努力弄清楚为什么这不起作用(与说明它应该起作用的文档相反)。

我有这样的供应商

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:putin_flutter_client/api/client.dart';
import 'package:putin_flutter_client/api/storage.dart';

final userProvider = StateNotifierProvider((_) => UserNotifier());

class UserNotifier extends StateNotifier<UserState> {
  UserNotifier() : super(UserState());

  set username(String username) {
    state = UserState(username: username, password: state.password, jwt: state.jwt);
    secureStorageWrite('username', username);
  }

  set password(String password) {
    state = UserState(username: state.username, password: password, jwt: state.jwt);
    secureStorageWrite('password', password);
  }

  set jwt(String jwt) {
    state = UserState(username: state.username, password: state.password, jwt: jwt);
    Client.jwt = jwt;
    secureStorageWrite('jwt', jwt);
  }

  String get jwt {
    return state.jwt;
  }

  Future<void> initState() async {
    final user = await UserState.load();
    state.username = user.username;
    state.password = user.password;
    state.jwt = user.jwt;
  }
}

class UserState {
  String username;
  String password;
  String jwt;

  UserState({
    this.username,
    this.password,
    this.jwt,
  });

  static Future<UserState> load() async {
    return UserState(
      username: await secureStorageRead('username'),
      password: await secureStorageRead('password'),
      jwt: await secureStorageRead('jwt'),
    );
  }
}

最终深入到某些小部件中,像这样的东西会更新状态

// usilizing the setter on the provider to update the state...
user.jwt = data['token'];

现在在我管理 http 客户端的代码的其他部分。这显然无法访问 BuildContext 等,因此我执行以下操作以从存储的状态中检索 jwt 值。

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:putin_flutter_client/state/user.dart';

class Client extends http.BaseClient {
  final http.Client _client = http.Client();

  Future<http.StreamedResponse> send(http.BaseRequest request) {
    // Get the container as per riverpod documentation
    final container = ProviderContainer();
    // Access the value through the getter on the provider
    final jwt = container.read(userProvider).jwt;

    request.headers['user-agent'] = 'myclient::v1.0.0';
    request.headers['Content-Type'] = 'application/json';
    if (jwt != null) {
      request.headers['X-Auth-Token'] = jwt;
    }
    return _client.send(request);
  }
}

这始终为 null,而 UserState 几乎为空(所有成员均为 null)。

riverpod documentation 中它说这应该有效

test('counter starts at 0', () {
  final container = ProviderContainer();

  StateController<int> counter = container.read(counterProvider);
  expect(counter.state, 0);
});

谁能帮我找出上面示例中的问题所在?

ProviderContainer() 用于在 Dart 中使用 RiverPod。 Flutter 中的等价物是 ProviderScope(),但它需要通过小部件上下文链访问,类似于提供程序包。

ProviderContainer() 创建您的提供者的新实例,它不会获得实际状态。 您需要像这样使您的客户端依赖于用户状态:

final clientProvider = Provider<Client>((ref){
    return Client(ref.watch(userProvider.state))
});
class Client extends http.BaseClient {
  Client(this._userState);
  final UserState _userState;
  final http.Client _client = http.Client();

  Future<http.StreamedResponse> send(http.BaseRequest request) {
    
    final jwt = _userState.jwt;

    request.headers['user-agent'] = 'myclient::v1.0.0';
    request.headers['Content-Type'] = 'application/json';
    if (jwt != null) {
      request.headers['X-Auth-Token'] = jwt;
    }
    return _client.send(request);
  }
}

当您的用户状态发生变化时,客户端将使用新值重新实例化

如果您不想每次都重新实例化,请改用读取方法:

final clientProvider = Provider<Client>((ref){
    return Client(ref.read)
});
class Client extends http.BaseClient {
  Client(this._reader);
  final Reader _reader;
  final http.Client _client = http.Client();

  Future<http.StreamedResponse> send(http.BaseRequest request) {
    
    final jwt = _reader(userProvider.state).jwt;

    request.headers['user-agent'] = 'myclient::v1.0.0';
    request.headers['Content-Type'] = 'application/json';
    if (jwt != null) {
      request.headers['X-Auth-Token'] = jwt;
    }
    return _client.send(request);
  }
}

正如@moulte 指出的那样(非常感谢)可以通过在外部实例化并通过 UncontrolledProviderScope 将其注入到小部件范围来访问作为全局变量且独立于上下文的提供程序。重要的是要记住在应用程序终止之前处理全局提供程序,否则它永远不会真正终止。 这是一个示例代码

/// file /state/container.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
final container = ProviderContainer();

/// file /main.dart
void main() async {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyApp createState() => _MyApp();
}

class _MyApp extends State<MyApp> {

  @override
  void dispose() {
    super.dispose();
    // disposing the globally self managed container.
    container.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return UncontrolledProviderScope(container: container,
      child: MaterialApp(
      // The usual widget tree
    );
  }
}

/// Somewhere in a file that is not aware of the BuildContext
/// here's how client.dart accesses the provider
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:putin_flutter_client/state/container.dart';
import 'package:putin_flutter_client/state/user.dart';


class Client extends http.BaseClient {
  final http.Client _client = http.Client();

  Future<http.StreamedResponse> send(http.BaseRequest request) {
    // Simply accessing the global container and calling the .read function
    var jwt = container.read(userProvider.state).jwt;
    request.headers['user-agent'] = 'putin_flutter::v1.0.0';
    request.headers['Content-Type'] = 'application/json';
    if (jwt != null) {
      request.headers['X-Auth-Token'] = jwt;
    }
    return _client.send(request);
  }
}