【问题标题】:Riverpod, reading state in outside BuildContext and ProviderRiverpod,在 BuildContext 和 Provider 之外读取状态
【发布时间】:2021-06-07 21:09:06
【问题描述】:

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

我有一个类似这样的提供者

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);
});

有人可以帮我弄清楚我上面的例子有什么问题吗?

【问题讨论】:

    标签: flutter dart riverpod


    【解决方案1】:

    正如@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);
      }
    }
    

    【讨论】:

    • 如果我们去掉需要观察者参数的ProviderScope,我们可以不再使用观察者了吗?
    • 创建一个全局 ProviderContainer 是一个非常糟糕的做法。不要这样做
    【解决方案2】:

    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);
      }
    }
    

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

    如果您不想每次都通过 read 方法重新实例化:

    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);
      }
    }
    

    【讨论】:

    • 啊,我明白了,所以我可以在我的小部件中调用final response = await context.read(clientProvider).post(url);。在颤动中,是否有某种方式可以在小部件树之外读取类似于 context.read() 的提供程序?这将使事情变得更简单、更容易和轻量级,因为例如在这种情况下,客户端只需要在调用时读取 jwt 值。它并不真正关心得到它的每一个变化(类似于 buttonPressed 用例 - 除了小部件树/上下文之外)
    • 理论上你可以把你的 PorivderContainer 放在一个全局变量中,如果你不想创建一个提供者,它可以工作:Final container = ProviderContainer(); runApp(UncontrolledProviderScope(container:container,child:MaterialApp(home:MainView(),),),);
    • 但是,如果您不想每次都通过提供者参考的读者来重建您的客户端,我会更新答案以向您展示
    • 直接传入阅读器?好主意xD
    • 因此,如果没有在链中以某种方式传递 BuildContext,就无法实际读取提供程序值,对吧?我希望在var jwt = userProvider.state.jwt; // outside the widget tree when the caller does not know the BuildContext的方向上有所收获
    【解决方案3】:

    ProviderContainer() 用于在 Dart 中使用 RiverPod。 Flutter 中的等价物是 ProviderScope(),但这需要通过 widget 上下文链访问,类似于 provider 包。

    【讨论】:

      猜你喜欢
      • 2021-03-17
      • 2019-09-21
      • 1970-01-01
      • 2021-08-07
      • 2021-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-27
      相关资源
      最近更新 更多