将 Provider 作用于多个实例

问题描述

我有一个 isLoading StateNotifierProvider 可以在我的按钮被点击时添加一个加载指示器,直到异步方法完成。

但是,当我同时显示两个按钮时,它们都会在只有一个被点击时显示指示器。

如何将 StateNotifierProvider 的范围限定为每个按钮的一个实例,而不是所有按钮的一个实例?

按钮:

class AsyncSubmitButton extends ConsumerWidget {
  AsyncSubmitButton(
      {required this.text,required this.onpressed})
      : super(key: key);

  final String text;
  final Future<void> Function() onpressed;

  @override
  Widget build(BuildContext context,ScopedReader watch) {
    final isLoading = watch(isLoadingProvider);
    final form = ReactiveForm.of(context)!;
    return ElevatedButton(
        onpressed: form.invalid
            ? null
            : () => context.read(isLoadingProvider.notifier).pressed(onpressed),child: isLoading
            ? const CircularProgressIndicator()
            : Text(text),);
  }
}

供应商:

final isLoadingProvider =
    StateNotifierProvider<AsyncSubmitButtonLoadingStateNotifier,bool>((ref) {
  return AsyncSubmitButtonLoadingStateNotifier(isLoading: false);
});

class AsyncSubmitButtonLoadingStateNotifier extends StateNotifier<bool> {
  AsyncSubmitButtonLoadingStateNotifier({required bool isLoading})
      : super(isLoading);

  dynamic pressed(Future<void> Function() onpressedFunction) async {
    state = true;
    try {
      await onpressedFunction();
    } catch (error) {
      state = false;
    } finally {
      state = false;
    }
  }
}

解决方法

正如 this answerthis answer 建议的那样,使用 family modifier。这个问题是链接答案中问题的重复,但我发现这个用例没有得到很好的记录(仅在 SO/GitHub 答案中),我认为这个答案更完整,所以想把它留在这里。

提供者的状态总是全局共享的。但是您可以使用 id 修饰符通过传入的 family 访问该状态,然后在读取提供程序时比较 id 以查看它是否是相应的实例。请记住在使用 .autoDispose 时使用 family 以防止内存泄漏。我认为它可能会创建一个对提供程序的新引用,并为每次读取使用不同的传入参数创建一个新的引用,除非使用 autoDispose 修饰符,否则永远不会处理提供程序。

所以问题中的代码变成了:

按钮:

class AsyncSubmitButton extends ConsumerWidget {
  AsyncSubmitButton(
      {required this.text,this.id = 'global',required this.onPressed})
      : super(key: key);

  final String text;
  final Future<void> Function() onPressed;
  final String id;

  @override
  Widget build(BuildContext context,ScopedReader watch) {
    final isLoading = watch(isLoadingProvider(id)) &&
        id == context.read(isLoadingProvider(id).notifier).id;
    final form = ReactiveForm.of(context)!;
    return ElevatedButton(
        onPressed: form.invalid
            ? null
            : () => context.read(isLoadingProvider.notifier).pressed(onPressed),child: isLoading
            ? const CircularProgressIndicator()
            : Text(text),);
  }
}

供应商:

final isLoadingProvider = StateNotifierProvider.family
    .autoDispose<AsyncSubmitButtonLoadingStateNotifier,bool,String>(
        (ref,id) {
  return AsyncSubmitButtonLoadingStateNotifier(isLoading: false,id: id);
});

class AsyncSubmitButtonLoadingStateNotifier extends StateNotifier<bool> {
  AsyncSubmitButtonLoadingStateNotifier(
      {required bool isLoading,required this.id})
      : super(isLoading);

  String id;

  dynamic onPressed(Future<void> Function() onPressedFunction) async {
    state = true;
    try {
      await onPressedFunction();
    } catch (error) {
      state = false;
    } finally {
      state = false;
    }
  }
}

在小部件中,如果您不关心它的唯一性,只需不要传递 id,它就会默认。如果您确实在意,因为您有 2 个独特的按钮使用它等,请指定 id...

child: AsyncSubmitButton(text: 'Sign Out',id: 'signOutButton',onPressed: vm.signOut)