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 46 }
47 47  
48 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 49 _futureComposition = _loadLottieComposition();
56 50  
57 51 if (widget.isPlaying) {
58   - _startAnimation();
  52 + _startAnimation();
59 53 }
60 54 }
61 55  
... ... @@ -69,10 +63,12 @@ class _CheerRewardWidgetState extends State<CheerRewardWidget>
69 63 Log.d("$TAG _futureComposition.then duration=${composition.duration}");
70 64 _controller.duration = composition.duration;
71 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 104 } else {
109 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 5 import '../../utils/log_util.dart';
6 6  
7 7 /// 录音组件
8   -class _RecorderPlaybackWidget extends StatefulWidget {
  8 +class RecorderPlaybackWidget extends StatefulWidget {
9 9 final bool isClickable;
10 10 final bool isPlaying;
11 11 final VoidCallback? onTap;
12 12 final double width;
13 13 final double height;
14 14  
15   - const _RecorderPlaybackWidget({
  15 + const RecorderPlaybackWidget({
16 16 Key? key,
17 17 required this.isClickable,
18 18 required this.isPlaying,
... ... @@ -22,14 +22,13 @@ class _RecorderPlaybackWidget extends StatefulWidget {
22 22 }) : super(key: key);
23 23  
24 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 29 with SingleTickerProviderStateMixin {
31 30 late final AnimationController _controller;
32   - late final LottieComposition _composition;
  31 + late final Future<LottieComposition> _futureComposition;
33 32 static const String TAG = "RecorderPlaybackWidget";
34 33  
35 34 bool _isPlaying = false;
... ... @@ -39,47 +38,63 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt;
39 38 super.initState();
40 39 _controller = AnimationController(vsync: this);
41 40 _loadComposition();
  41 + if (widget.isPlaying) {
  42 + _playAnimation();
  43 + }
42 44  
43 45 _controller.addListener(() {
44 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 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 73 void _playAnimation() {
62 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 84 void _stopAnimation() {
70 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 100 void _displayAnimation(bool clickable) {
... ... @@ -99,25 +114,6 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt;
99 114 }
100 115  
101 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 117 void dispose() {
122 118 _controller.dispose();
123 119 super.dispose();
... ... @@ -129,13 +125,22 @@ class _RecorderPlaybackWidgetState extends State&lt;_RecorderPlaybackWidget&gt;
129 125 onTap: widget.isClickable ? widget.onTap : null,
130 126 child: Opacity(
131 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 95 }
96 96  
97 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 106 @override
... ...
lib/common/widgets/speaker_widget.dart
... ... @@ -73,9 +73,11 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
73 73 Log.d(
74 74 "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}");
75 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 88 _controller.stop();
87 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 99 @override
... ...
lib/common/widgets/star_reward_widget.dart
... ... @@ -55,7 +55,7 @@ class _StarRewardWidgetState extends State&lt;StarRewardWidget&gt;
55 55 _futureComposition = _loadLottieComposition();
56 56  
57 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 69 Log.d("$TAG _futureComposition.then duration=${composition.duration}");
70 70 _controller.duration = composition.duration;
71 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 }
... ...