Commit c272c662bd42c755c8ebf73b721a3a64bac78bed
1 parent
46385ad0
fix: 崩溃修复
Showing
10 changed files
with
199 additions
and
90 deletions
lib/app/splash_page.dart
... | ... | @@ -140,19 +140,22 @@ class _TransitionViewState extends State<TransitionView> { |
140 | 140 | actions: <Widget>[ |
141 | 141 | TextButton( |
142 | 142 | child: const Text('退出'), |
143 | - onPressed: () => { | |
144 | - // 关闭对话框并重试 | |
145 | - Navigator.of(context).pop(), | |
146 | - AppConfigHelper.exitApp(), | |
147 | - completer?.complete(), | |
148 | - }), | |
143 | + onPressed: () { | |
144 | + // 关闭对话框并退出应用 | |
145 | + Navigator.of(context).pop(); | |
146 | + AppConfigHelper.exitApp(); | |
147 | + // 只有在 completer 还没有完成时才完成它 | |
148 | + if (completer != null && !completer.isCompleted) { | |
149 | + completer.complete(); | |
150 | + } | |
151 | + }), | |
149 | 152 | TextButton( |
150 | 153 | child: const Text('重试'), |
151 | 154 | onPressed: () async { |
152 | 155 | // 关闭对话框并重试 |
153 | 156 | Navigator.of(context).pop(); |
154 | 157 | await fetchNecessaryData(userToken, completer: completer); |
155 | - completer?.complete(); | |
158 | + // 不需要再次调用 completer.complete(),因为 fetchNecessaryData 内部已经处理了 | |
156 | 159 | }, |
157 | 160 | ), |
158 | 161 | ], | ... | ... |
lib/common/permission/permissionRequestPage.dart
... | ... | @@ -30,6 +30,7 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> |
30 | 30 | bool _isGoSetting = false; |
31 | 31 | late final List<String> msgList; |
32 | 32 | bool _isDialogShowing = false; |
33 | + bool _isRequestingPermission = false; | |
33 | 34 | |
34 | 35 | @override |
35 | 36 | void initState() { |
... | ... | @@ -107,6 +108,11 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> |
107 | 108 | |
108 | 109 | /// 校验权限 |
109 | 110 | void _handlePermission(List<Permission> permissions) async { |
111 | + if (_isRequestingPermission) { | |
112 | + Log.d('_handlePermission already in progress, skipping'); | |
113 | + return; | |
114 | + } | |
115 | + | |
110 | 116 | ///一个新待申请权限列表 |
111 | 117 | List<Permission> intentPermissionList = []; |
112 | 118 | |
... | ... | @@ -129,37 +135,51 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> |
129 | 135 | |
130 | 136 | ///实际触发请求权限 |
131 | 137 | Future<void> _requestPermission(List<Permission> permissions) async { |
132 | - Log.d('_requestPermission permissions=$permissions'); | |
133 | - MapEntry<Permission, PermissionStatus>? statusEntry = | |
134 | - await requestPermissionsInner(permissions); | |
135 | - if (statusEntry == null) { | |
136 | - ///都手动同意授予了 | |
137 | - _popPage(true); | |
138 | + if (_isRequestingPermission) { | |
139 | + Log.d('_requestPermission already in progress, skipping'); | |
138 | 140 | return; |
139 | 141 | } |
142 | + | |
143 | + _isRequestingPermission = true; | |
144 | + Log.d('_requestPermission permissions=$permissions'); | |
145 | + | |
146 | + try { | |
147 | + MapEntry<Permission, PermissionStatus>? statusEntry = | |
148 | + await requestPermissionsInner(permissions); | |
149 | + if (statusEntry == null) { | |
150 | + ///都手动同意授予了 | |
151 | + _popPage(true); | |
152 | + return; | |
153 | + } | |
140 | 154 | |
141 | - Permission permission = statusEntry.key; | |
142 | - PermissionStatus status = statusEntry.value; | |
155 | + Permission permission = statusEntry.key; | |
156 | + PermissionStatus status = statusEntry.value; | |
143 | 157 | |
144 | - // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`) | |
145 | - if (status.isDenied) { | |
146 | - showAlert( | |
147 | - permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); | |
148 | - } | |
149 | - // 权限已被永久拒绝 | |
150 | - /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。 | |
151 | - /// 在 iOS 上:如果用户拒绝访问所请求的功能。 | |
152 | - if (status.isPermanentlyDenied) { | |
153 | - _isGoSetting = true; | |
154 | - showAlert( | |
155 | - permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); | |
156 | - } | |
157 | - // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) | |
158 | - // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS) | |
159 | - if (status.isLimited || status.isRestricted) { | |
160 | - if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; | |
161 | - showAlert( | |
162 | - permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); | |
158 | + // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`) | |
159 | + if (status.isDenied) { | |
160 | + showAlert( | |
161 | + permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); | |
162 | + } | |
163 | + // 权限已被永久拒绝 | |
164 | + /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。 | |
165 | + /// 在 iOS 上:如果用户拒绝访问所请求的功能。 | |
166 | + if (status.isPermanentlyDenied) { | |
167 | + _isGoSetting = true; | |
168 | + showAlert( | |
169 | + permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); | |
170 | + } | |
171 | + // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) | |
172 | + // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS) | |
173 | + if (status.isLimited || status.isRestricted) { | |
174 | + if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; | |
175 | + showAlert( | |
176 | + permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); | |
177 | + } | |
178 | + } catch (e) { | |
179 | + Log.d('_requestPermission error: $e'); | |
180 | + _popPage(false); | |
181 | + } finally { | |
182 | + _isRequestingPermission = false; | |
163 | 183 | } |
164 | 184 | } |
165 | 185 | ... | ... |
lib/common/permission/permissionRequester.dart
... | ... | @@ -35,12 +35,13 @@ Future<bool> requestPermissions( |
35 | 35 | if (allGranted) { |
36 | 36 | return true; |
37 | 37 | } else { |
38 | - return await Navigator.of(context).push(PageRouteBuilder( | |
38 | + final result = await Navigator.of(context).push(PageRouteBuilder( | |
39 | 39 | opaque: false, |
40 | 40 | pageBuilder: ((context, animation, secondaryAnimation) { |
41 | 41 | return PermissionRequestPage(permissions, permissionNames, permissionDesc, |
42 | 42 | isRequiredPermission: isRequiredPermission); |
43 | 43 | }))); |
44 | + return result ?? false; | |
44 | 45 | } |
45 | 46 | } |
46 | 47 | ... | ... |
lib/common/request/api_response/api_response_entity.dart
lib/pages/home/widgets/ShakeImage.dart
... | ... | @@ -20,7 +20,7 @@ class _ShakeImageState extends State<ShakeImage> |
20 | 20 | with SingleTickerProviderStateMixin, WidgetsBindingObserver, RouteAware { |
21 | 21 | late AnimationController _controller; |
22 | 22 | late Animation<double> _animation; |
23 | - late Timer _timer; | |
23 | + Timer? _timer; | |
24 | 24 | late final AppLifecycleListener appLifecycleListener; |
25 | 25 | static const TAG = 'ShakeImage'; |
26 | 26 | |
... | ... | @@ -43,7 +43,11 @@ class _ShakeImageState extends State<ShakeImage> |
43 | 43 | ); |
44 | 44 | |
45 | 45 | super.initState(); |
46 | - WidgetsBinding.instance.addObserver(this); | |
46 | + try { | |
47 | + WidgetsBinding.instance.addObserver(this); | |
48 | + } catch (e) { | |
49 | + printLog('Add observer error: $e'); | |
50 | + } | |
47 | 51 | WidgetsBinding.instance.addPostFrameCallback((_) { |
48 | 52 | final route = ModalRoute.of(context); |
49 | 53 | if (route is PageRoute) { |
... | ... | @@ -100,16 +104,32 @@ class _ShakeImageState extends State<ShakeImage> |
100 | 104 | |
101 | 105 | void _startAnimation() { |
102 | 106 | Timer(const Duration(seconds: 1), () { |
103 | - _controller.forward(from: 0.0); | |
104 | - _timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) { | |
105 | - _controller.forward(from: 0.0); | |
106 | - }); | |
107 | + // 检查widget是否还存在且controller是否有效 | |
108 | + if (mounted) { | |
109 | + try { | |
110 | + _controller.forward(from: 0.0); | |
111 | + _timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) { | |
112 | + // 每次执行前都检查widget状态和controller状态 | |
113 | + if (mounted) { | |
114 | + try { | |
115 | + _controller.forward(from: 0.0); | |
116 | + } catch (e) { | |
117 | + printLog('Timer callback error: $e'); | |
118 | + timer.cancel(); | |
119 | + } | |
120 | + } | |
121 | + }); | |
122 | + } catch (e) { | |
123 | + printLog('Initial animation error: $e'); | |
124 | + } | |
125 | + } | |
107 | 126 | }); |
108 | 127 | printLog('_startAnimation'); |
109 | 128 | } |
110 | 129 | |
111 | 130 | void _stopAnimation() { |
112 | - _timer.cancel(); | |
131 | + _timer?.cancel(); | |
132 | + _timer = null; | |
113 | 133 | printLog('_stopAnimation'); |
114 | 134 | } |
115 | 135 | |
... | ... | @@ -162,9 +182,21 @@ class _ShakeImageState extends State<ShakeImage> |
162 | 182 | @override |
163 | 183 | void didChangeAppLifecycleState(AppLifecycleState state) { |
164 | 184 | if (state == AppLifecycleState.paused) { |
165 | - _controller.stop(); | |
185 | + if (mounted) { | |
186 | + try { | |
187 | + _controller.stop(); | |
188 | + } catch (e) { | |
189 | + printLog('Stop animation error: $e'); | |
190 | + } | |
191 | + } | |
166 | 192 | } else if (state == AppLifecycleState.resumed) { |
167 | - _controller.forward(); | |
193 | + if (mounted) { | |
194 | + try { | |
195 | + _controller.forward(); | |
196 | + } catch (e) { | |
197 | + printLog('Resume animation error: $e'); | |
198 | + } | |
199 | + } | |
168 | 200 | } |
169 | 201 | } |
170 | 202 | |
... | ... | @@ -172,9 +204,13 @@ class _ShakeImageState extends State<ShakeImage> |
172 | 204 | void dispose() { |
173 | 205 | printLog('dispose'); |
174 | 206 | customerRouteObserver.unsubscribe(this); |
175 | - WidgetsBinding.instance.removeObserver(this); | |
207 | + try { | |
208 | + WidgetsBinding.instance.removeObserver(this); | |
209 | + } catch (e) { | |
210 | + printLog('Remove observer error: $e'); | |
211 | + } | |
176 | 212 | _controller.dispose(); |
177 | - _timer.cancel(); | |
213 | + _timer?.cancel(); | |
178 | 214 | super.dispose(); |
179 | 215 | } |
180 | 216 | ... | ... |
lib/pages/reading/bloc/reading_bloc.dart
... | ... | @@ -293,9 +293,13 @@ class ReadingPageBloc |
293 | 293 | } |
294 | 294 | |
295 | 295 | Future<void> _playAudio(String? audioUrl) async { |
296 | - if (audioUrl!.isNotEmpty) { | |
297 | - await audioPlayer.play(UrlSource(audioUrl), | |
298 | - balance: 0.0, ctx: AudioContext()); | |
296 | + if (audioUrl != null && audioUrl.isNotEmpty) { | |
297 | + try { | |
298 | + await audioPlayer.play(UrlSource(audioUrl), | |
299 | + balance: 0.0, ctx: AudioContext()); | |
300 | + } catch (e) { | |
301 | + Log.d('_playAudio error: $e'); | |
302 | + } | |
299 | 303 | } |
300 | 304 | } |
301 | 305 | |
... | ... | @@ -321,10 +325,10 @@ class ReadingPageBloc |
321 | 325 | } |
322 | 326 | List<SingsoundResultDetailEntity> resultDetails; |
323 | 327 | if (currentPageData()?.resultDetails != null) { |
324 | - resultDetails = currentPageData()!.resultDetails!; | |
328 | + resultDetails = currentPageData()?.resultDetails ?? []; | |
325 | 329 | } else { |
326 | 330 | List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+')); |
327 | - resultDetails = wordList! | |
331 | + resultDetails = (wordList ?? []) | |
328 | 332 | .map( |
329 | 333 | (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0)) |
330 | 334 | .toList(); | ... | ... |
lib/pages/section/section_page.dart
... | ... | @@ -85,13 +85,17 @@ class _SectionPageView extends StatelessWidget { |
85 | 85 | 'courseLessonId': courseLessonId, |
86 | 86 | 'isTopic': true |
87 | 87 | }).then((value) { |
88 | - if (value != null) { | |
88 | + if (value != null && !bloc.isClosed) { | |
89 | 89 | Map<String, dynamic> dataMap = |
90 | 90 | value as Map<String, dynamic>; |
91 | - bloc.add(RequestEndClassEvent( | |
92 | - dataMap['courseLessonId']!, dataMap['isCompleted'], | |
93 | - currentTime: dataMap['currentTime'], | |
94 | - autoNextSection: dataMap['nextSection'])); | |
91 | + try { | |
92 | + bloc.add(RequestEndClassEvent( | |
93 | + dataMap['courseLessonId']!, dataMap['isCompleted'], | |
94 | + currentTime: dataMap['currentTime'], | |
95 | + autoNextSection: dataMap['nextSection'])); | |
96 | + } catch (e) { | |
97 | + print('BLoC add event error: $e'); | |
98 | + } | |
95 | 99 | } |
96 | 100 | AudioPlayerUtil.getInstance() |
97 | 101 | .playAudio(AudioPlayerUtilType.countWithMe); |
... | ... | @@ -111,15 +115,19 @@ class _SectionPageView extends StatelessWidget { |
111 | 115 | 'courseLessonId': courseLessonId, |
112 | 116 | 'moduleColor': ModuleCache.instance.getCurrentThemeColor() |
113 | 117 | }).then((value) { |
114 | - if (value != null) { | |
118 | + if (value != null && !bloc.isClosed) { | |
115 | 119 | Map<String, dynamic> dataMap = |
116 | 120 | value as Map<String, dynamic>; |
117 | - bloc.add(RequestEndClassEvent( | |
118 | - dataMap['courseLessonId']!, | |
119 | - dataMap['isCompleted'], | |
120 | - currentStep: dataMap['currentStep'], | |
121 | - autoNextSection: dataMap['nextSection'], | |
122 | - )); | |
121 | + try { | |
122 | + bloc.add(RequestEndClassEvent( | |
123 | + dataMap['courseLessonId']!, | |
124 | + dataMap['isCompleted'], | |
125 | + currentStep: dataMap['currentStep'], | |
126 | + autoNextSection: dataMap['nextSection'], | |
127 | + )); | |
128 | + } catch (e) { | |
129 | + print('BLoC add event error: $e'); | |
130 | + } | |
123 | 131 | AudioPlayerUtil.getInstance() |
124 | 132 | .playAudio(AudioPlayerUtilType.countWithMe); |
125 | 133 | } |
... | ... | @@ -138,13 +146,17 @@ class _SectionPageView extends StatelessWidget { |
138 | 146 | 'courseLessonId': courseLessonId, |
139 | 147 | 'moduleColor': ModuleCache.instance.getCurrentThemeColor() |
140 | 148 | }).then((value) { |
141 | - if (value != null) { | |
149 | + if (value != null && !bloc.isClosed) { | |
142 | 150 | Map<String, dynamic> dataMap = |
143 | 151 | value as Map<String, dynamic>; |
144 | - bloc.add(RequestEndClassEvent( | |
145 | - dataMap['courseLessonId']!, dataMap['isCompleted'], | |
146 | - currentStep: dataMap['currentStep'], | |
147 | - autoNextSection: dataMap['nextSection'])); | |
152 | + try { | |
153 | + bloc.add(RequestEndClassEvent( | |
154 | + dataMap['courseLessonId']!, dataMap['isCompleted'], | |
155 | + currentStep: dataMap['currentStep'], | |
156 | + autoNextSection: dataMap['nextSection'])); | |
157 | + } catch (e) { | |
158 | + print('BLoC add event error: $e'); | |
159 | + } | |
148 | 160 | } |
149 | 161 | AudioPlayerUtil.getInstance() |
150 | 162 | .playAudio(AudioPlayerUtilType.countWithMe); |
... | ... | @@ -183,7 +195,9 @@ class _SectionPageView extends StatelessWidget { |
183 | 195 | itemCount: bloc.unlockPageCount(), |
184 | 196 | controller: bloc.pageController, |
185 | 197 | onPageChanged: (int index) { |
186 | - bloc.add(CurrentUnitIndexChangeEvent(index)); | |
198 | + if (!bloc.isClosed) { | |
199 | + bloc.add(CurrentUnitIndexChangeEvent(index)); | |
200 | + } | |
187 | 201 | }, |
188 | 202 | itemBuilder: (context, index) { |
189 | 203 | return ScrollConfiguration( | ... | ... |
lib/pages/unit/view.dart
... | ... | @@ -78,7 +78,7 @@ class UnitPage extends StatelessWidget { |
78 | 78 | if (value != null) { |
79 | 79 | Map<String, dynamic> dataMap = |
80 | 80 | value as Map<String, dynamic>; |
81 | - bool needRefresh = dataMap['needRefresh']; | |
81 | + bool needRefresh = dataMap['needRefresh'] ?? false; | |
82 | 82 | if (needRefresh) { |
83 | 83 | bloc.add(RequestUnitDataEvent( |
84 | 84 | courseModuleEntity?.id)); | ... | ... |
lib/pages/video/lookvideo/widgets/video_widget.dart
... | ... | @@ -81,7 +81,9 @@ class _VideoWidgetState extends State<VideoWidget> { |
81 | 81 | 'nextSection': widget.isTopic |
82 | 82 | }); |
83 | 83 | } as VoidCallback, againSectionTap: (() { |
84 | - lookVideoBloc.add(SectionAgainEvent()); | |
84 | + if (mounted && !lookVideoBloc.isClosed) { | |
85 | + lookVideoBloc.add(SectionAgainEvent()); | |
86 | + } | |
85 | 87 | }), context: context); |
86 | 88 | } |
87 | 89 | } | ... | ... |
lib/utils/audio_player_util.dart
... | ... | @@ -30,7 +30,7 @@ enum AudioPlayerUtilType { |
30 | 30 | |
31 | 31 | class AudioPlayerUtil extends WidgetsBindingObserver { |
32 | 32 | static AudioPlayerUtil? _instance; |
33 | - late AudioPlayer _audioPlayer; | |
33 | + AudioPlayer? _audioPlayer; | |
34 | 34 | late AudioPlayerUtilType currentType; |
35 | 35 | bool _wasPlaying = false; |
36 | 36 | static const TAG = "AudioPlayerUtil"; |
... | ... | @@ -43,7 +43,7 @@ class AudioPlayerUtil extends WidgetsBindingObserver { |
43 | 43 | if (!BasicConfig.isEnvProd()) { |
44 | 44 | AudioLogger.logLevel = AudioLogLevel.info; |
45 | 45 | } |
46 | - _audioPlayer.onPlayerStateChanged.listen((event) async { | |
46 | + _audioPlayer?.onPlayerStateChanged.listen((event) async { | |
47 | 47 | Log.d("$TAG onPlayerStateChanged $event _wasPlaying=$_wasPlaying"); |
48 | 48 | if (event == PlayerState.completed) { |
49 | 49 | // 播放结束再次播放 |
... | ... | @@ -70,42 +70,66 @@ class AudioPlayerUtil extends WidgetsBindingObserver { |
70 | 70 | // 播放音频 |
71 | 71 | Future<void> playAudio(AudioPlayerUtilType type) async { |
72 | 72 | Log.d('$TAG playAudio begin $type'); |
73 | + if (_audioPlayer == null) { | |
74 | + Log.d('$TAG playAudio _audioPlayer is null, creating new one'); | |
75 | + _audioPlayer = AudioPlayer(); | |
76 | + } | |
73 | 77 | currentType = type; |
74 | 78 | String path = type.path; |
75 | - await _audioPlayer.play(AssetSource(path.assetMp3), volume: 0.5); | |
76 | - await _audioPlayer.onPlayerComplete.first; | |
79 | + try { | |
80 | + await _audioPlayer!.play(AssetSource(path.assetMp3), volume: 0.5); | |
81 | + await _audioPlayer!.onPlayerComplete.first; | |
82 | + } catch (e) { | |
83 | + Log.d('$TAG playAudio error: $e'); | |
84 | + } | |
77 | 85 | Log.d('$TAG playAudio end $type'); |
78 | 86 | } |
79 | 87 | |
80 | 88 | // stop |
81 | 89 | Future<void> stop() async { |
82 | - Log.d("$TAG stop _audioPlayer.state=${_audioPlayer.state}"); | |
83 | - if (_audioPlayer.state == PlayerState.playing) { | |
84 | - await _audioPlayer.stop(); | |
90 | + if (_audioPlayer == null) return; | |
91 | + Log.d("$TAG stop _audioPlayer.state=${_audioPlayer!.state}"); | |
92 | + try { | |
93 | + if (_audioPlayer!.state == PlayerState.playing) { | |
94 | + await _audioPlayer!.stop(); | |
95 | + } | |
96 | + } catch (e) { | |
97 | + Log.d("$TAG stop error: $e"); | |
85 | 98 | } |
86 | 99 | } |
87 | 100 | |
88 | 101 | // pause |
89 | 102 | Future<void> pause() async { |
90 | - Log.d("$TAG pause _audioPlayer.state=${_audioPlayer.state}"); | |
91 | - if (_audioPlayer.state == PlayerState.playing) { | |
92 | - await _audioPlayer.pause(); | |
103 | + if (_audioPlayer == null) return; | |
104 | + Log.d("$TAG pause _audioPlayer.state=${_audioPlayer!.state}"); | |
105 | + try { | |
106 | + if (_audioPlayer!.state == PlayerState.playing) { | |
107 | + await _audioPlayer!.pause(); | |
108 | + } | |
109 | + } catch (e) { | |
110 | + Log.d("$TAG pause error: $e"); | |
93 | 111 | } |
94 | 112 | } |
95 | 113 | |
96 | 114 | // resume |
97 | 115 | Future<void> resume() async { |
98 | - Log.d("$TAG resume _audioPlayer.state=${_audioPlayer.state}"); | |
99 | - if (_audioPlayer.state == PlayerState.paused) { | |
100 | - await _audioPlayer.resume(); | |
116 | + if (_audioPlayer == null) return; | |
117 | + Log.d("$TAG resume _audioPlayer.state=${_audioPlayer!.state}"); | |
118 | + try { | |
119 | + if (_audioPlayer!.state == PlayerState.paused) { | |
120 | + await _audioPlayer!.resume(); | |
121 | + } | |
122 | + } catch (e) { | |
123 | + Log.d("$TAG resume error: $e"); | |
101 | 124 | } |
102 | 125 | } |
103 | 126 | |
104 | 127 | @override |
105 | 128 | void didChangeAppLifecycleState(AppLifecycleState state) async { |
106 | - Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer.state}"); | |
129 | + if (_audioPlayer == null) return; | |
130 | + Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer!.state}"); | |
107 | 131 | if (state == AppLifecycleState.paused) { |
108 | - if (_audioPlayer.state == PlayerState.playing) { | |
132 | + if (_audioPlayer!.state == PlayerState.playing) { | |
109 | 133 | _wasPlaying = true; |
110 | 134 | await pause(); |
111 | 135 | }; |
... | ... | @@ -118,12 +142,15 @@ class AudioPlayerUtil extends WidgetsBindingObserver { |
118 | 142 | } |
119 | 143 | |
120 | 144 | bool isPlaying() { |
121 | - return _audioPlayer.state == PlayerState.playing; | |
145 | + return _audioPlayer?.state == PlayerState.playing; | |
122 | 146 | } |
123 | 147 | |
124 | 148 | void dispose() { |
125 | - Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer.state}"); | |
126 | - _audioPlayer.dispose(); | |
149 | + if (_audioPlayer != null) { | |
150 | + Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer!.state}"); | |
151 | + _audioPlayer!.dispose(); | |
152 | + _audioPlayer = null; | |
153 | + } | |
127 | 154 | WidgetsBinding.instance.removeObserver(this); |
128 | 155 | } |
129 | 156 | } | ... | ... |