检查在Flutter中Stateless Widget是否已被释放

16

当我的无状态小部件被构建时,我使用以下代码按顺序播放一些声音:

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);

如果用户在播放音频之前关闭当前的小部件,那么即使使用以下代码关闭了当前路由,音频仍然会继续播放:

Navigator.pop(context);

我的解决方法是使用一个布尔变量来表示关闭操作是否已完成。

播放声音的代码:

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);

关闭当前窗口小部件:

closed = true;
_audioPlayer.stop();

如果我的小部件关闭了,是否有更好的方法来停止异步方法?


1
dispose是State类中的一个方法,因此你应该使用StatefulWidget。 - diegoveloper
我已将小部件更改为有状态的小部件,并覆盖了“dispose”方法以更改“closed”值,它可以工作。但是这种解决方案减少了从关闭按钮更改“closed”值的需要,但我正在寻找一种避免声明“closed”变量并在所有未来调用之后进行“if”检查的方法。我需要一种取消所有未来调用的方法。@diegoveloper - Flutter IO Dev
2个回答

22

如果您将小部件更改为StatefulWidget,则可以拥有以下类似的函数:

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);
}

然后在dispose方法中,只需处理播放器:

@override
void dispose() {
  _audioPlayer?.dispose();
  super.dispose();
}

当我在'dispose'方法中处理_audioPlayer时,如果我使用Navigator.pop(context);关闭当前页面,一切都运行良好,但是当我在当前路由的顶部推入一个新路由(其中包含播放声音方法),dispose方法不被调用,请问您有任何建议来处理这种情况吗? - Flutter IO Dev
1
默认情况下,当您在其上推送路由时,MaterialPageRoute 不会被释放。如果您希望它在不是顶级路由时被释放,则必须在创建正在播放音频的路由时将 maintainState 设置为 false。有关更多信息,请参见文档 - Kirollos Morkos
14
StatelessWidget 中有没有相同的方法可以实现? - Slick Slime
super.dispose(); 的调用应该是方法的最后一行。 如果你重写了这个方法,请确保在方法结尾处调用 super.dispose() - g2server

0

mounted getter目前已合并到主要渠道,但尚未在稳定渠道中提供。

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

与此同时,我们有2个选择

  1. 直接使用Stateful Widget(作为被接受的答案)
  2. 实现一个带有构建方法的Stateful Widget包装器,以传递mounted获取器也在包装器中。

包装器组件

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));
  }
}

使用方法

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'),
            );
          },
        );
      },
    );
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接