From 0ebc618672ffed4e405f21647055211130f2edf3 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Fri, 2 Aug 2024 19:53:25 +0800 Subject: [PATCH] feat:解决异步场景下setState泄漏问题(setState() called after dispose(): _SpeakerWidgetState#47f43(lifecycle state: defunct, not mounted, ticker inactive)) --- lib/common/widgets/cheer_reward_widget.dart | 29 ++++++++--------------------- lib/common/widgets/record_playback_widget.dart | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------- lib/common/widgets/recorder_widget.dart | 10 ++++++---- lib/common/widgets/speaker_widget.dart | 19 +++++++++++-------- lib/common/widgets/star_reward_widget.dart | 12 +++++++----- 5 files changed, 94 insertions(+), 95 deletions(-) diff --git a/lib/common/widgets/cheer_reward_widget.dart b/lib/common/widgets/cheer_reward_widget.dart index 6281397..d967917 100644 --- a/lib/common/widgets/cheer_reward_widget.dart +++ b/lib/common/widgets/cheer_reward_widget.dart @@ -46,16 +46,10 @@ class _CheerRewardWidgetState extends State } void _loadComposition() { - // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load(); - // setState(() { - // _composition = composition; - // _controller.duration = _composition.duration; - // }); - _futureComposition = _loadLottieComposition(); if (widget.isPlaying) { - _startAnimation(); + _startAnimation(); } } @@ -69,10 +63,12 @@ class _CheerRewardWidgetState extends State Log.d("$TAG _futureComposition.then duration=${composition.duration}"); _controller.duration = composition.duration; _controller.forward().whenCompleteOrCancel(() { - setState(() { - _isVisible = false; - }); - widget.onAnimationEnd(); // 调用外部回调函数 + if (mounted) { + setState(() { + _isVisible = false; + }); + widget.onAnimationEnd(); // 调用外部回调函数 + } }); }); } @@ -108,16 +104,7 @@ class _CheerRewardWidgetState extends State } else { return const SizedBox.shrink(); } - }) - - // child: Lottie( - // composition: _composition, - // controller: _controller, - // renderCache: RenderCache.raster, - // width: widget.width, - // height: widget.height, - // ), - ), + })), ); } } diff --git a/lib/common/widgets/record_playback_widget.dart b/lib/common/widgets/record_playback_widget.dart index 018eff3..233df88 100644 --- a/lib/common/widgets/record_playback_widget.dart +++ b/lib/common/widgets/record_playback_widget.dart @@ -5,14 +5,14 @@ import 'package:wow_english/common/widgets/throttledGesture_gesture_detector.dar import '../../utils/log_util.dart'; /// 录音组件 -class _RecorderPlaybackWidget extends StatefulWidget { +class RecorderPlaybackWidget extends StatefulWidget { final bool isClickable; final bool isPlaying; final VoidCallback? onTap; final double width; final double height; - const _RecorderPlaybackWidget({ + const RecorderPlaybackWidget({ Key? key, required this.isClickable, required this.isPlaying, @@ -22,14 +22,13 @@ class _RecorderPlaybackWidget extends StatefulWidget { }) : super(key: key); @override - _RecorderPlaybackWidgetState createState() => - _RecorderPlaybackWidgetState(); + _RecorderPlaybackWidgetState createState() => _RecorderPlaybackWidgetState(); } -class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> +class _RecorderPlaybackWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; - late final LottieComposition _composition; + late final Future _futureComposition; static const String TAG = "RecorderPlaybackWidget"; bool _isPlaying = false; @@ -39,47 +38,63 @@ class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> super.initState(); _controller = AnimationController(vsync: this); _loadComposition(); + if (widget.isPlaying) { + _playAnimation(); + } _controller.addListener(() { // Log.d("$TAG addListener _controller=${_controller.status}"); }); } - Future _loadComposition() async { - final composition = - await AssetLottie('assets/lotties/recorder_back.zip').load(); - setState(() { - _composition = composition; - _controller.duration = _composition.duration; - }); - - if (widget.isPlaying) { + @override + void didUpdateWidget(RecorderPlaybackWidget oldWidget) { + super.didUpdateWidget(oldWidget); + Log.d( + "$TAG ${identityHashCode(this)} didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying"); + if (widget.isPlaying && !_isPlaying) { + setState(() { + _isPlaying = true; + }); _playAnimation(); + } else if (!widget.isPlaying && _isPlaying) { + _stopAnimation(); + } + + if (!_isPlaying) { + _displayAnimation(widget.isClickable); } } + Future _loadComposition() async { + _futureComposition = AssetLottie('assets/lotties/recorder_back.zip').load(); + } + void _playAnimation() { _controller.reset(); - _controller.repeat( - min: 2 / _composition.endFrame, - max: 22 / _composition.endFrame, - ); + _futureComposition.then((composition) { + _controller.duration = composition.duration; + _controller.repeat( + min: 2 / composition.endFrame, + max: 20 / composition.endFrame, + ); + }); } void _stopAnimation() { _controller.stop(); - _resetAnimation(); - } - - void _resetAnimation() { - setState(() { - _isPlaying = false; - _controller.value = 1; - // _controller.repeat( - // min: 1 / _composition.endFrame, - // max: 1 / _composition.endFrame, - // ); - }); + if (mounted) { + setState(() { + _isPlaying = false; + _futureComposition.then((composition) { + _controller.value = 1 / composition.endFrame; + }); + // _controller.repeat( + // min: 1 / _composition.endFrame, + // max: 1 / _composition.endFrame, + // ); + }); + } } void _displayAnimation(bool clickable) { @@ -99,25 +114,6 @@ class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> } @override - void didUpdateWidget(_RecorderPlaybackWidget oldWidget) { - super.didUpdateWidget(oldWidget); - Log.d( - "$TAG didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying"); - if (widget.isPlaying && !_isPlaying) { - setState(() { - _isPlaying = true; - }); - _playAnimation(); - } else if (!widget.isPlaying && _isPlaying) { - _stopAnimation(); - } - - if (!_isPlaying) { - _displayAnimation(widget.isClickable); - } - } - - @override void dispose() { _controller.dispose(); super.dispose(); @@ -129,13 +125,22 @@ class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> onTap: widget.isClickable ? widget.onTap : null, child: Opacity( opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度 - child: Lottie( - composition: _composition, - controller: _controller, - renderCache: RenderCache.raster, - width: widget.width, - height: widget.height, - ), + child: FutureBuilder( + future: _futureComposition, + builder: (context, snapshot) { + if (snapshot.hasData) { + final composition = snapshot.data!; + return Lottie( + composition: composition, + controller: _controller, + renderCache: RenderCache.raster, + width: widget.width, + height: widget.height, + ); + } else { + return const SizedBox.shrink(); + } + }), ), ); } diff --git a/lib/common/widgets/recorder_widget.dart b/lib/common/widgets/recorder_widget.dart index 287a7d5..a52beb2 100644 --- a/lib/common/widgets/recorder_widget.dart +++ b/lib/common/widgets/recorder_widget.dart @@ -95,10 +95,12 @@ class _RecorderWidgetState extends State } void _resetAnimation() { - setState(() { - _isPlaying = false; - _controller.value = 0; - }); + if (mounted) { + setState(() { + _isPlaying = false; + _controller.value = 0; + }); + } } @override diff --git a/lib/common/widgets/speaker_widget.dart b/lib/common/widgets/speaker_widget.dart index 80f3690..46a3434 100644 --- a/lib/common/widgets/speaker_widget.dart +++ b/lib/common/widgets/speaker_widget.dart @@ -73,9 +73,11 @@ class _SpeakerWidgetState extends State Log.d( "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}"); _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) { - setState(() { - _currentFrame = ((_currentFrame + 1) % 3); - }); + if (mounted) { + setState(() { + _currentFrame = ((_currentFrame + 1) % 3); + }); + } }); } @@ -86,11 +88,12 @@ class _SpeakerWidgetState extends State _controller.stop(); _controller.reset(); - setState(() { - _isPlaying = false; - // _controller.value = 0; - _currentFrame = 0; - }); + if (mounted) { + setState(() { + _isPlaying = false; + _currentFrame = 0; + }); + } } @override diff --git a/lib/common/widgets/star_reward_widget.dart b/lib/common/widgets/star_reward_widget.dart index 5b73766..67700f2 100644 --- a/lib/common/widgets/star_reward_widget.dart +++ b/lib/common/widgets/star_reward_widget.dart @@ -55,7 +55,7 @@ class _StarRewardWidgetState extends State _futureComposition = _loadLottieComposition(); if (widget.isPlaying) { - _startAnimation(); + _startAnimation(); } } @@ -69,10 +69,12 @@ class _StarRewardWidgetState extends State Log.d("$TAG _futureComposition.then duration=${composition.duration}"); _controller.duration = composition.duration; _controller.forward().whenCompleteOrCancel(() { - setState(() { - _isVisible = false; - }); - widget.onAnimationEnd(); // 调用外部回调函数 + if (mounted) { + setState(() { + _isVisible = false; + }); + widget.onAnimationEnd(); // 调用外部回调函数 + } }); }); } -- libgit2 0.22.2