Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FutureProvider the ref.onCancel() is called but ref.onDispose() is not #3323

Closed
MxD-js opened this issue Feb 6, 2024 · 7 comments
Closed
Assignees
Labels
bug Something isn't working question Further information is requested

Comments

@MxD-js
Copy link

MxD-js commented Feb 6, 2024

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

@MxD-js MxD-js added bug Something isn't working needs triage labels Feb 6, 2024
@MxD-js MxD-js changed the title FutureProvider the ref.onCancel() is called by ref.onDispose() is not FutureProvider the ref.onCancel() is called but ref.onDispose() is not Feb 6, 2024
@rrousselGit
Copy link
Owner

Please make a minimal reproducible example. I cannot run this code

@rrousselGit rrousselGit added question Further information is requested and removed needs triage labels Feb 7, 2024
@MxD-js
Copy link
Author

MxD-js commented Feb 7, 2024

@MxD-js
Copy link
Author

MxD-js commented Feb 10, 2024

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?

@rrousselGit
Copy link
Owner

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

@MxD-js
Copy link
Author

MxD-js commented Feb 10, 2024

Thanks @rrousselGit I'll reach out on discord.

@snapsl
Copy link

snapsl commented Feb 19, 2024

Does it help to move onDispose before the async/await part?

@rrousselGit
Copy link
Owner

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants