16

When my stateless widget built I play some sounds in sequence order by using this code:

await _audioPlayer.play(contentPath1, isLocal: true);
await Future.delayed(Duration(seconds: 4));
await _audioPlayer.play(contentPath2, isLocal: true);
await Future.delayed(Duration(seconds: 4));
await _audioPlayer.play(contentPath3, isLocal: true);

when the user closes the current widget before finish playing the sounds, The sounds still work even after closing the current route by using this code:

Navigator.pop(context);

my workaround is to use a boolean variable to indicate if the closing action has done.

Playing sound code:

await _audioPlayer.play(contentPath1, isLocal: true);
if (closed) return;
await Future.delayed(Duration(seconds: 4));
if (closed) return;
await _audioPlayer.play(contentPath2, isLocal: true);
if (closed) return;
await Future.delayed(Duration(seconds: 4));
if (closed) return;
await _audioPlayer.play(contentPath3, isLocal: true);

Closing the current widget:

closed = true;
_audioPlayer.stop();

Are there a better way to stop the async methods if my widget closed?

Flutter IO Dev
  • 1,759
  • 5
  • 15
  • 20
  • 1
    the dispose is a method from State , so you should use StatefulWidget – diegoveloper Oct 21 '18 at 17:29
  • I have changed the widget to stateful widget and override 'dispose' method to change the 'closed' value and it works, but's this solution reduce they need to change 'closed' value from the closing buttons, but I'm looking for a way to avoid declaring 'closed' variable and do 'if' checking after all the future calls. I need a way to cancel all the future calls.@diegoveloper – Flutter IO Dev Oct 21 '18 at 18:16

2 Answers2

22

If you change your widget to a StatefulWidget then you can have a function like the following:

void _playSounds() {
  await _audioPlayer.play(contentPath1, isLocal: true);
  await Future.delayed(Duration(seconds: 4));
  if (!mounted) return;

  await _audioPlayer.play(contentPath2, isLocal: true);
  await Future.delayed(Duration(seconds: 4));
  if (!mounted) return;

  await _audioPlayer.play(contentPath3, isLocal: true);
}

and then in the dispose method just dispose of the player:

@override
void dispose() {
  _audioPlayer?.dispose();
  super.dispose();
}
Kirollos Morkos
  • 2,503
  • 16
  • 22
  • When I disposing of _audioPlayer in 'dispose' method, in case I'm closing the current page with `Navigator.pop(context);` every thing works good, but when I'm pushing a new route on the top of the current route (that contain the play sound method) the dispose method not called, do you have any suggestion to deal with this case please? – Flutter IO Dev Oct 21 '18 at 19:31
  • 1
    By default, a `MaterialPageRoute` will not dispose when you push a route on top of it. If you want it to dispose when it's not the top level route, you have to set `maintainState` to false when creating the route that is playing audio. See the [docs](https://docs.flutter.io/flutter/material/MaterialPageRoute-class.html) for more info. – Kirollos Morkos Oct 21 '18 at 19:52
  • 14
    Is there any way to do the same in `StatelessWidget`? – Slick Slime Apr 19 '20 at 10:48
  • the call to `super.dispose();` should be the last line of the method. [If you override this, make sure to end your method with a call to super.dispose().](https://api.flutter.dev/flutter/widgets/State/dispose.html) – g2server Dec 17 '20 at 00:58
0

The mounted getter is currently merged in master channel but it still not available in the stable channel.

https://github.com/flutter/flutter/pull/111619

In the mean time, we have 2 option

  1. Use Stateful Widget directly (as accepted answer)
  2. Implement a Stateful Widget wrapper with builder method to pass down the mounted getter also in a wrapper.

Wrapper widget

import 'package:flutter/material.dart';

class PrimitiveWrapper<T> {
  T value;

  PrimitiveWrapper(this.value);
}

class MountedWrapper extends StatefulWidget {
  final Widget Function(BuildContext context, PrimitiveWrapper<bool> mounted) builder;

  const MountedWrapper({
    required this.builder,
    super.key,
  });

  @override
  State<MountedWrapper> createState() => _MountedWrapperState();
}

class _MountedWrapperState extends State<MountedWrapper> {
  @override
  Widget build(BuildContext context) {
    return widget.builder.call(context, PrimitiveWrapper(mounted));
  }
}

Usage

class SubmitBtn extends StatelessWidget {

  ...

  Future<void> onSubmit(WidgetRef ref, BuildContext context, PrimitiveWrapper<bool> mounted) async {
    ...

    await topicRepo.add(topic);

    if (!mounted.value) {
      return;
    }

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Created topic'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MountedWrapper(
      builder: (context, mounted) {
        return Consumer(
          builder: (context, ref, child) {
            return ElevatedButton(
              onPressed: () {
                onSubmit(ref, context, mounted);
              },
              child: const Text('Create'),
            );
          },
        );
      },
    );
  }
}
Mohan
  • 329
  • 4
  • 8