Commit 0ebc618672ffed4e405f21647055211130f2edf3

Authored by 吴启风
1 parent b81332ca

feat:解决异步场景下setState泄漏问题(setState() called after dispose(): _SpeakerWidgetState#…

…47f43(lifecycle state: defunct, not mounted, ticker inactive))
lib/common/widgets/cheer_reward_widget.dart
@@ -46,16 +46,10 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget> @@ -46,16 +46,10 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget>
46 } 46 }
47 47
48 void _loadComposition() { 48 void _loadComposition() {
49 - // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load();  
50 - // setState(() {  
51 - // _composition = composition;  
52 - // _controller.duration = _composition.duration;  
53 - // });  
54 -  
55 _futureComposition = _loadLottieComposition(); 49 _futureComposition = _loadLottieComposition();
56 50
57 if (widget.isPlaying) { 51 if (widget.isPlaying) {
58 - _startAnimation(); 52 + _startAnimation();
59 } 53 }
60 } 54 }
61 55
@@ -69,10 +63,12 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget> @@ -69,10 +63,12 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget>
69 Log.d("$TAG _futureComposition.then duration=${composition.duration}"); 63 Log.d("$TAG _futureComposition.then duration=${composition.duration}");
70 _controller.duration = composition.duration; 64 _controller.duration = composition.duration;
71 _controller.forward().whenCompleteOrCancel(() { 65 _controller.forward().whenCompleteOrCancel(() {
72 - setState(() {  
73 - _isVisible = false;  
74 - });  
75 - widget.onAnimationEnd(); // 调用外部回调函数 66 + if (mounted) {
  67 + setState(() {
  68 + _isVisible = false;
  69 + });
  70 + widget.onAnimationEnd(); // 调用外部回调函数
  71 + }
76 }); 72 });
77 }); 73 });
78 } 74 }
@@ -108,16 +104,7 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget> @@ -108,16 +104,7 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget>
108 } else { 104 } else {
109 return const SizedBox.shrink(); 105 return const SizedBox.shrink();
110 } 106 }
111 - })  
112 -  
113 - // child: Lottie(  
114 - // composition: _composition,  
115 - // controller: _controller,  
116 - // renderCache: RenderCache.raster,  
117 - // width: widget.width,  
118 - // height: widget.height,  
119 - // ),  
120 - ), 107 + })),
121 ); 108 );
122 } 109 }
123 } 110 }
lib/common/widgets/record_playback_widget.dart
@@ -5,14 +5,14 @@ import 'package:wow_english/common/widgets/throttledGesture_gesture_detector.dar @@ -5,14 +5,14 @@ import 'package:wow_english/common/widgets/throttledGesture_gesture_detector.dar
5 import '../../utils/log_util.dart'; 5 import '../../utils/log_util.dart';
6 6
7 /// 录音组件 7 /// 录音组件
8 -class _RecorderPlaybackWidget extends StatefulWidget { 8 +class RecorderPlaybackWidget extends StatefulWidget {
9 final bool isClickable; 9 final bool isClickable;
10 final bool isPlaying; 10 final bool isPlaying;
11 final VoidCallback? onTap; 11 final VoidCallback? onTap;
12 final double width; 12 final double width;
13 final double height; 13 final double height;
14 14
15 - const _RecorderPlaybackWidget({ 15 + const RecorderPlaybackWidget({
16 Key? key, 16 Key? key,
17 required this.isClickable, 17 required this.isClickable,
18 required this.isPlaying, 18 required this.isPlaying,
@@ -22,14 +22,13 @@ class _RecorderPlaybackWidget extends StatefulWidget { @@ -22,14 +22,13 @@ class _RecorderPlaybackWidget extends StatefulWidget {
22 }) : super(key: key); 22 }) : super(key: key);
23 23
24 @override 24 @override
25 - _RecorderPlaybackWidgetState createState() =>  
26 - _RecorderPlaybackWidgetState(); 25 + _RecorderPlaybackWidgetState createState() => _RecorderPlaybackWidgetState();
27 } 26 }
28 27
29 -class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget> 28 +class _RecorderPlaybackWidgetState extends State<RecorderPlaybackWidget>
30 with SingleTickerProviderStateMixin { 29 with SingleTickerProviderStateMixin {
31 late final AnimationController _controller; 30 late final AnimationController _controller;
32 - late final LottieComposition _composition; 31 + late final Future<LottieComposition> _futureComposition;
33 static const String TAG = "RecorderPlaybackWidget"; 32 static const String TAG = "RecorderPlaybackWidget";
34 33
35 bool _isPlaying = false; 34 bool _isPlaying = false;
@@ -39,47 +38,63 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt; @@ -39,47 +38,63 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt;
39 super.initState(); 38 super.initState();
40 _controller = AnimationController(vsync: this); 39 _controller = AnimationController(vsync: this);
41 _loadComposition(); 40 _loadComposition();
  41 + if (widget.isPlaying) {
  42 + _playAnimation();
  43 + }
42 44
43 _controller.addListener(() { 45 _controller.addListener(() {
44 // Log.d("$TAG addListener _controller=${_controller.status}"); 46 // Log.d("$TAG addListener _controller=${_controller.status}");
45 }); 47 });
46 } 48 }
47 49
48 - Future<void> _loadComposition() async {  
49 - final composition =  
50 - await AssetLottie('assets/lotties/recorder_back.zip').load();  
51 - setState(() {  
52 - _composition = composition;  
53 - _controller.duration = _composition.duration;  
54 - });  
55 -  
56 - if (widget.isPlaying) { 50 + @override
  51 + void didUpdateWidget(RecorderPlaybackWidget oldWidget) {
  52 + super.didUpdateWidget(oldWidget);
  53 + Log.d(
  54 + "$TAG ${identityHashCode(this)} didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying");
  55 + if (widget.isPlaying && !_isPlaying) {
  56 + setState(() {
  57 + _isPlaying = true;
  58 + });
57 _playAnimation(); 59 _playAnimation();
  60 + } else if (!widget.isPlaying && _isPlaying) {
  61 + _stopAnimation();
  62 + }
  63 +
  64 + if (!_isPlaying) {
  65 + _displayAnimation(widget.isClickable);
58 } 66 }
59 } 67 }
60 68
  69 + Future<void> _loadComposition() async {
  70 + _futureComposition = AssetLottie('assets/lotties/recorder_back.zip').load();
  71 + }
  72 +
61 void _playAnimation() { 73 void _playAnimation() {
62 _controller.reset(); 74 _controller.reset();
63 - _controller.repeat(  
64 - min: 2 / _composition.endFrame,  
65 - max: 22 / _composition.endFrame,  
66 - ); 75 + _futureComposition.then((composition) {
  76 + _controller.duration = composition.duration;
  77 + _controller.repeat(
  78 + min: 2 / composition.endFrame,
  79 + max: 20 / composition.endFrame,
  80 + );
  81 + });
67 } 82 }
68 83
69 void _stopAnimation() { 84 void _stopAnimation() {
70 _controller.stop(); 85 _controller.stop();
71 - _resetAnimation();  
72 - }  
73 -  
74 - void _resetAnimation() {  
75 - setState(() {  
76 - _isPlaying = false;  
77 - _controller.value = 1;  
78 - // _controller.repeat(  
79 - // min: 1 / _composition.endFrame,  
80 - // max: 1 / _composition.endFrame,  
81 - // );  
82 - }); 86 + if (mounted) {
  87 + setState(() {
  88 + _isPlaying = false;
  89 + _futureComposition.then((composition) {
  90 + _controller.value = 1 / composition.endFrame;
  91 + });
  92 + // _controller.repeat(
  93 + // min: 1 / _composition.endFrame,
  94 + // max: 1 / _composition.endFrame,
  95 + // );
  96 + });
  97 + }
83 } 98 }
84 99
85 void _displayAnimation(bool clickable) { 100 void _displayAnimation(bool clickable) {
@@ -99,25 +114,6 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt; @@ -99,25 +114,6 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt;
99 } 114 }
100 115
101 @override 116 @override
102 - void didUpdateWidget(_RecorderPlaybackWidget oldWidget) {  
103 - super.didUpdateWidget(oldWidget);  
104 - Log.d(  
105 - "$TAG didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying");  
106 - if (widget.isPlaying && !_isPlaying) {  
107 - setState(() {  
108 - _isPlaying = true;  
109 - });  
110 - _playAnimation();  
111 - } else if (!widget.isPlaying && _isPlaying) {  
112 - _stopAnimation();  
113 - }  
114 -  
115 - if (!_isPlaying) {  
116 - _displayAnimation(widget.isClickable);  
117 - }  
118 - }  
119 -  
120 - @override  
121 void dispose() { 117 void dispose() {
122 _controller.dispose(); 118 _controller.dispose();
123 super.dispose(); 119 super.dispose();
@@ -129,13 +125,22 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt; @@ -129,13 +125,22 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt;
129 onTap: widget.isClickable ? widget.onTap : null, 125 onTap: widget.isClickable ? widget.onTap : null,
130 child: Opacity( 126 child: Opacity(
131 opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度 127 opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度
132 - child: Lottie(  
133 - composition: _composition,  
134 - controller: _controller,  
135 - renderCache: RenderCache.raster,  
136 - width: widget.width,  
137 - height: widget.height,  
138 - ), 128 + child: FutureBuilder<LottieComposition>(
  129 + future: _futureComposition,
  130 + builder: (context, snapshot) {
  131 + if (snapshot.hasData) {
  132 + final composition = snapshot.data!;
  133 + return Lottie(
  134 + composition: composition,
  135 + controller: _controller,
  136 + renderCache: RenderCache.raster,
  137 + width: widget.width,
  138 + height: widget.height,
  139 + );
  140 + } else {
  141 + return const SizedBox.shrink();
  142 + }
  143 + }),
139 ), 144 ),
140 ); 145 );
141 } 146 }
lib/common/widgets/recorder_widget.dart
@@ -95,10 +95,12 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt; @@ -95,10 +95,12 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
95 } 95 }
96 96
97 void _resetAnimation() { 97 void _resetAnimation() {
98 - setState(() {  
99 - _isPlaying = false;  
100 - _controller.value = 0;  
101 - }); 98 + if (mounted) {
  99 + setState(() {
  100 + _isPlaying = false;
  101 + _controller.value = 0;
  102 + });
  103 + }
102 } 104 }
103 105
104 @override 106 @override
lib/common/widgets/speaker_widget.dart
@@ -73,9 +73,11 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt; @@ -73,9 +73,11 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
73 Log.d( 73 Log.d(
74 "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}"); 74 "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}");
75 _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) { 75 _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) {
76 - setState(() {  
77 - _currentFrame = ((_currentFrame + 1) % 3);  
78 - }); 76 + if (mounted) {
  77 + setState(() {
  78 + _currentFrame = ((_currentFrame + 1) % 3);
  79 + });
  80 + }
79 }); 81 });
80 } 82 }
81 83
@@ -86,11 +88,12 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt; @@ -86,11 +88,12 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
86 _controller.stop(); 88 _controller.stop();
87 _controller.reset(); 89 _controller.reset();
88 90
89 - setState(() {  
90 - _isPlaying = false;  
91 - // _controller.value = 0;  
92 - _currentFrame = 0;  
93 - }); 91 + if (mounted) {
  92 + setState(() {
  93 + _isPlaying = false;
  94 + _currentFrame = 0;
  95 + });
  96 + }
94 } 97 }
95 98
96 @override 99 @override
lib/common/widgets/star_reward_widget.dart
@@ -55,7 +55,7 @@ class _StarRewardWidgetState extends State&lt;StarRewardWidget&gt; @@ -55,7 +55,7 @@ class _StarRewardWidgetState extends State&lt;StarRewardWidget&gt;
55 _futureComposition = _loadLottieComposition(); 55 _futureComposition = _loadLottieComposition();
56 56
57 if (widget.isPlaying) { 57 if (widget.isPlaying) {
58 - _startAnimation(); 58 + _startAnimation();
59 } 59 }
60 } 60 }
61 61
@@ -69,10 +69,12 @@ class _StarRewardWidgetState extends State&lt;StarRewardWidget&gt; @@ -69,10 +69,12 @@ class _StarRewardWidgetState extends State&lt;StarRewardWidget&gt;
69 Log.d("$TAG _futureComposition.then duration=${composition.duration}"); 69 Log.d("$TAG _futureComposition.then duration=${composition.duration}");
70 _controller.duration = composition.duration; 70 _controller.duration = composition.duration;
71 _controller.forward().whenCompleteOrCancel(() { 71 _controller.forward().whenCompleteOrCancel(() {
72 - setState(() {  
73 - _isVisible = false;  
74 - });  
75 - widget.onAnimationEnd(); // 调用外部回调函数 72 + if (mounted) {
  73 + setState(() {
  74 + _isVisible = false;
  75 + });
  76 + widget.onAnimationEnd(); // 调用外部回调函数
  77 + }
76 }); 78 });
77 }); 79 });
78 } 80 }