riverpod
riverpod copied to clipboard
FutureProvider the ref.onCancel() is called but ref.onDispose() is not
Describe the bug If a I tap on a item from a ListView and pressed the back button the widget is disposed as expected and the provider ref.onRemoveListener is called then ref.onCancel is called also expected but not ref.onDispose is never called and long after the screen is disposed of the video player plays the video in the background and ref.onDispose is never called to dispose the player.
However when I let the the future provider complete and the data is present in the widget page then dispose functions as expected when the user presses the back button.
This issue comes up because when the user clicks on a video item from a ListView and taps back out immediate after the provider is still in a loading state and even though ref.onCancel was called, ref.onDispose is never called to dispose the video controllers and async functions complete and video starts playing on a unmounted and disposed of widget.
(Pardon all the print statements, just trying to figure out what is going on)
To Reproduce
riverpod
// ignore: unsupported_provider_value
FutureOr<ChewieController> videoPlayerItemController(
VideoPlayerItemControllerRef ref,
{required String videoId,
required String timeStamp,
String? videoIsLive}) async {
// Youtube explode
final ytxPlode = YoutubeExplode();
final VideoPlayerController playerController;
final ChewieController chewieController;
ref.onAddListener(() {
print("Listener Added....");
});
ref.onRemoveListener(() {
print("Listener Removed....");
});
ref.onCancel(() {
print("Canceling....");
});
ref.onResume(() {
print("Resuming....");
});
final islive = _checkIfLive(videoIsLive);
// get videoFromYtExplode:
final uri = await _getYTMetadata(videoId, ytxPlode, islive);
// Video Player Controller - Flutter
playerController = VideoPlayerController.networkUrl(uri);
await playerController.initialize();
chewieController = ChewieController(
autoInitialize: true,
isLive: islive,
videoPlayerController: playerController,
startAt: Duration(seconds: int.parse(timeStamp)),
autoPlay: true,
);
// Clean up
ref.onDispose(() {
print("STARTED DISPOSING VIDEO PLAYER!!!!");
playerController.dispose();
chewieController.dispose();
ytxPlode.close();
print("FINISHED DISPOSING VIDEO PLAYER!!!!");
});
return chewieController;
}
and my widget for the video player
class VideoPlayerItem extends HookConsumerWidget {
const VideoPlayerItem(
{required this.title,
required this.id,
this.isLive,
this.timeStamp,
Key? key})
: super(key: key);
final String title;
final String id;
final String? timeStamp;
final String? isLive;
@override
Widget build(BuildContext context, WidgetRef ref) {
final videoController = ref.watch(videoPlayerItemControllerProvider(
videoId: id,
timeStamp: timeStamp ?? 0.toString(),
videoIsLive: isLive));
final tappedItem = useState<int>(0);
// Paging Controller using useMemoized to hold the complex object
// in memory (same as initState for a stateful widget)
final moreFromThisServicePagingController =
useMemoized<PagingController<int, ArchiveVideoItem>>(
() => PagingController(firstPageKey: 0));
useEffect(() {
moreFromThisServicePagingController
.addPageRequestListener((pageKey) => ref.read(
fetchMoreFromThisServiceControllerProvider(
pageKey: pageKey,
pagingController: moreFromThisServicePagingController,
videoId: id),
));
return () {
print("USEEFFEECT STARTED DISPOSING WIDGET");
moreFromThisServicePagingController.dispose();
print("USEEFFEECT FINISHED DISPOSING WIDGET ");
};
}, const []);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: videoController.when(
data: (vController) => Column(
children: [
AspectRatio(
aspectRatio: 16 / 9, child: Chewie(controller: vController)),
Visibility(
visible: isLive == "false" ? false : true,
child: const Padding(
padding: EdgeInsets.all(10),
child: Row(children: [
Text("more from the service"),
]),
),
),
Visibility(
visible: isLive == "false" ? false : true,
child: Expanded(
child: PagedListView<int, ArchiveVideoItem>(
pagingController: moreFromThisServicePagingController,
builderDelegate: PagedChildBuilderDelegate(
itemBuilder: (context, item, index) {
return Container(
decoration: index == tappedItem.value
? BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Colors.redAccent, width: 1))
: null,
child: MoreItemListTile(
video: item,
onPressed: () {
tappedItem.value = index;
vController.seekTo(Duration(
seconds: int.parse(item.youTubeTimeStamp)));
}),
);
},
newPageProgressIndicatorBuilder: (context) =>
const VideoListTileShimmer(),
firstPageProgressIndicatorBuilder: (context) {
return Column(
children: List.generate(
10, (index) => const VideoListTileShimmer()),
);
},
),
),
),
)
],
),
error: (error, stackTrace) => Center(
child: Text("error occured $error"),
),
loading: () => const Column(
children: [
SizedBox(height: 90),
Center(child: CircularProgressIndicator())
],
),
),
);
}
}
Terminal output for when it's working correctly.
flutter: Listener Added....
<<<<<-------- at this point the video is playing and correctly working.
flutter: EXITING VIDEO PLAYER PAGE
flutter: Listener Removed....
flutter: Canceling....
flutter: USEEFFEECT STARTED DISPOSING WIDGET
flutter: USEEFFEECT FINISHED DISPOSING WIDGET
flutter: STARTED DISPOSING VIDEO PLAYER!!!!
flutter: FINISHED DISPOSING VIDEO PLAYER!!!!
but when I quickly access the video player page and then press the back button this is the terminal output.
flutter: Listener Added....
flutter: EXITING VIDEO PLAYER PAGE
flutter: Listener Removed....
flutter: Canceling....
flutter: USEEFFEECT STARTED DISPOSING WIDGET
flutter: USEEFFEECT FINISHED DISPOSING WIDGET
but no ref.onDispose is never called.
Expected behavior Expect that ref.onDispose is called
Please make a minimal reproducible example. I cannot run this code
Sure, Here it is. https://github.com/MxD-js/riverpod_repro_issue_3323_example
Hi Guys, I'm following up on this, I've been bashing my head to figure this out.. Any insight into what could be causing this?
I'm working on other stuff at the moment. I have yet to investigate this.
If it's urgent for you, feel free to ask for help on Discord. Otherwise you may have to wait
Thanks @rrousselGit I'll reach out on discord.
Does it help to move onDispose before the async/await part?
Oh sorry I didn't see your answer. Yes, the placement of onDispose is the issue, as you found out yourself.
The documentation has already been updated, so I'll close this