【问题标题】:Scope a Provider to multiple instances将 Provider 范围限定为多个实例
【发布时间】:2021-07-24 06:03:02
【问题描述】:

我有一个 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;
    }
  }
}

【问题讨论】:

    标签: flutter dart riverpod


    【解决方案1】:

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

    提供者的状态总是全局共享的。但是您可以通过使用family 修饰符传入id 来访问该状态,然后在读取提供程序时比较id 以查看它是否是对应的实例。使用family时记得使用.autoDispose,以防止内存泄漏。我认为它可能会使用传入的参数创建对提供程序的新引用,每次读取都使用不同的传入参数,并且除非使用 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)
    

    【讨论】:

      猜你喜欢
      • 2020-01-17
      • 2020-02-28
      • 2013-11-09
      • 1970-01-01
      • 2016-04-27
      • 1970-01-01
      • 2021-02-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多