【问题标题】:Initializing riverpod Provider with a custom ChangeNotifier使用自定义 ChangeNotifier 初始化riverpod Provider
【发布时间】:2020-12-02 14:44:23
【问题描述】:

我只是在试用新的river_pod,颤振状态管理库。我的目标很简单。主页中的GestureDetector 监听垂直拖动并相应地更新动画控制器。我想在别的地方听这个动画。我已经编写了以下代码,并且它按预期工作。但我不觉得我正在以正确的方式初始化提供程序。

// a custom notifier class
class AnimationNotifier extends ChangeNotifier {
  final AnimationController _animationController;

  AnimationNotifier(this._animationController) {
    _animationController.addListener(_onAnimationControllerChanged);
  }

  void forward() => _animationController.forward();
  void reverse() => _animationController.reverse();

  void _onAnimationControllerChanged() {
    notifyListeners();
  }

  @override
  void dispose() {
    _animationController.removeListener(_onAnimationControllerChanged);
    super.dispose();
  }

  double get value => _animationController.value;
}

// provider variable, (not initialized here)
var animationProvider;

// main Widget
class GestureControlledAnimationDemo extends StatefulWidget {
  @override
  _GestureControlledAnimationDemoState createState() =>
      _GestureControlledAnimationDemoState();
}

class _GestureControlledAnimationDemoState
    extends State<GestureControlledAnimationDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  double get maxHeight => 420.0;

   @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    // provider is initialized here
    animationProvider = ChangeNotifierProvider((_) {
      return AnimationNotifier(_controller);
    });
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CustomScaffold(
      title: 'GestureControlled',
      body: GestureDetector(
        onVerticalDragUpdate: _handleDragUpdate,
        onVerticalDragEnd: _handleDragEnd,
        child: Container(
          color: Colors.red,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  'Yo',
                  style: TextStyle(color: Colors.white),
                ),
                NotifierTest(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  void _handleDragUpdate(DragUpdateDetails details) {
    _controller.value -= details.primaryDelta / maxHeight;
  }

  void _handleDragEnd(DragEndDetails details) {
    if (_controller.isAnimating ||
        _controller.status == AnimationStatus.completed) return;

    final double flingVelocity =
        details.velocity.pixelsPerSecond.dy / maxHeight;
    if (flingVelocity < 0.0) {
      _controller.fling(velocity: max(2.0, -flingVelocity));
    } else if (flingVelocity > 0.0) {
      _controller.fling(velocity: min(-2.0, -flingVelocity));
    } else {
      _controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
    }
  }
}

// Widget which uses the provider
class NotifierTest extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final animationNotifier = useProvider(animationProvider);
    double count = animationNotifier.value * 1000.0;
    return Container(
      child: Text(
        '${count.floor()}',
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

由于创建AnimationNotifier 的实例需要一个动画控制器实例,这只能在_controller 初始化之后完成。所以在initState() 中,我已经初始化了_controlleranimationProvider。这是使用riverpodProvider的正确方法吗? 如果没有,可以进行哪些修改?

【问题讨论】:

    标签: flutter flutter-provider state-management riverpod flutter-hooks


    【解决方案1】:

    首先我强烈推荐使用钩子——它会显着减少代码的样板,例如,你的类声明将变成:

    
    class GestureControlledAnimationDemo extends HookWidget {
      double get maxHeight => 420.0;
    
      @override
      Widget build(BuildContext context) {
        final _controller = useAnimationController(duration: Duration(seconds: 1));
        ...
      }
    

    这消除了对 initState、dispose 等的需要。

    其次,您不一定要在类中创建非静态提供程序。相反,您可以在全局范围内创建它,或者在这种情况下,将其添加为自定义通知程序的静态成员是有意义的。

    class AnimationNotifier extends ChangeNotifier {
      ...
      static final provider = ChangeNotifierProvider((_) {
        return AnimationNotifier(controller);
      });
    }
    

    但是等等,我们在这个范围内没有任何名为controller 的变量,那么我们如何获得访问权限呢?我们可以为 AnimationController 创建一个提供程序,或者我们可以将您的提供程序转换为 family,以便我们可以接受 AnimationController 作为参数。我将与家人一起展示这种方法:

    class AnimationNotifier extends ChangeNotifier {
      ...
      static final provider = ChangeNotifierProvider.autoDispose.family<AnimationNotifier, AnimationController>((_, AnimationController controller) {
        return AnimationNotifier(controller);
      });
    }
    

    我添加了 autoDispose,因为您可能希望在不再需要控制器时将其丢弃。现在,我们使用提供者:

    
    class GestureControlledAnimationDemo extends HookWidget {
      double get maxHeight => 420.0;
    
      @override
      Widget build(BuildContext context) {
        final controller = useAnimationController(duration: Duration(seconds: 1));
        final provider = useProvider(AnimationNotifier.provider(controller));
        ...
      }
    

    如果您确实使用了钩子,请确保将您的 Riverpod 依赖项更改为 hooks_riverpod

    编辑:

    对于您的用例,您可能会将当前控制器存储在 StateProvider 中,然后从 ChangeNotifierProvider 中读取它,而不是使用系列。

    final controllerProvider = StateProvider<AnimationController>((_) => null);
    
    class AnimationNotifier extends ChangeNotifier {
      ...
      static final provider = ChangeNotifierProvider.autoDispose<AnimationNotifier>((ref) {
        final controller = ref.read(controllerProvider)?.state;
        return AnimationNotifier(controller);
      });
    }
    
    
    class GestureControlledAnimationDemo extends HookWidget {
      double get maxHeight => 420.0;
    
      @override
      Widget build(BuildContext context) {
        final controller = useAnimationController(duration: Duration(seconds: 1));
    
        final currentController = useProvider(controllerProvider);
        currentController.state = controller;
        
        final notifier = useProvider(AnimationNotifier.provider);
        ...
      }
    

    这应该可行。请注意,当 Riverpod 0.6.0 发布时,您还可以自动处置 StateProvider。

    【讨论】:

    • 感谢您提供此信息。我的一个疑问是,如何在另一个小部件中使用具有相同控制器的提供程序?在问题代码中,最后您可以看到一个使用提供程序的 NotifierTest 小部件。如果您能对此提供更多解释,那将非常有帮助。
    • 啊,对不起。我错过了您问题中的一些代码。我会更新我的答案。
    猜你喜欢
    • 2021-03-17
    • 2021-01-24
    • 1970-01-01
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-05
    相关资源
    最近更新 更多