Инициализация поставщика Riverpod с помощью настраиваемого ChangeNotifier

Я просто пробую новую библиотеку управления состояниями river_pod, flutter. Моя цель здесь проста.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(), Я инициализировал оба _controller а также animationProvider. Это правильный способ использования RiverpodProvider? Если нет, то какие изменения можно внести?

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 или превратить вашего поставщика в семейство, чтобы мы могли принимать 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.

Другие вопросы по тегам