Reputation: 33
I'm just trying out the new river_pod, flutter state management library. My goal here is simple. GestureDetector
in the main page listens to vertical drags and updates the animation controller accordingly. And I'd like to listen to this animation somewhere else. I have written the following code, and it's working as expected. But I don't feel like I'm initializing the provider in the right way.
// 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),
),
);
}
}
Since an animation controller instance is required to create an instance of AnimationNotifier
, this can be done only after _controller
initialization. So in the initState()
, I've initialized both _controller
and animationProvider
. Is this the right way to use riverpod Provider
?
If not, what modifications can be made?
Upvotes: 0
Views: 7276
Reputation: 5970
First off I would highly recommend using hooks - it would reduce the boilerplate of your code significantly, for example, your class declaration will turn into:
class GestureControlledAnimationDemo extends HookWidget {
double get maxHeight => 420.0;
@override
Widget build(BuildContext context) {
final _controller = useAnimationController(duration: Duration(seconds: 1));
...
}
This removes the need for initState, dispose, etc.
Second, you don't necessarily want to create non-static providers inside classes. Instead, you could create it in global scope, or in this case, it makes sense to add as a static member on your custom notifier.
class AnimationNotifier extends ChangeNotifier {
...
static final provider = ChangeNotifierProvider((_) {
return AnimationNotifier(controller);
});
}
But wait, we don't have any variable named controller
in this scope, so how do we get access? We can create a provider for an AnimationController, or we can turn your provider into a family so we can accept an AnimationController as a parameter. I will demonstrate the approach with families:
class AnimationNotifier extends ChangeNotifier {
...
static final provider = ChangeNotifierProvider.autoDispose.family<AnimationNotifier, AnimationController>((_, AnimationController controller) {
return AnimationNotifier(controller);
});
}
I added autoDispose as you likely want your controllers disposed when they are no longer needed. Now, we use the provider:
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));
...
}
If you do use hooks, make sure you change your riverpod dependency to hooks_riverpod.
EDIT:
It looks like for your use case you could potentially store the current controller in a StateProvider, then read it from the ChangeNotifierProvider instead of using families.
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);
...
}
This should work. Note that when Riverpod 0.6.0 is released, you can also autodispose the StateProvider.
Upvotes: 3