Skip to content

Commit b2de21d

Browse files
Fixed: PlayStore reported a crash for android.app.BackgroundServiceStartNotAllowedException.
* The crash occurred because the fragment was not visible on the window when attempting to stop the read-aloud service. We improved the service-stopping logic to ensure it is handled correctly when the Compose view is disposed. * Applied similar improvements to other fragments with the same pattern, ensuring all listeners, callbacks, and services are properly cleaned up at the appropriate lifecycle moment.
1 parent 7b8f544 commit b2de21d

File tree

3 files changed

+54
-6
lines changed

3 files changed

+54
-6
lines changed

app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import androidx.compose.material.icons.Icons
4343
import androidx.compose.material.icons.filled.Menu
4444
import androidx.compose.material3.ExperimentalMaterial3Api
4545
import androidx.compose.material3.SnackbarHostState
46+
import androidx.compose.runtime.DisposableEffect
4647
import androidx.compose.runtime.LaunchedEffect
4748
import androidx.compose.runtime.mutableStateOf
4849
import androidx.compose.ui.platform.ComposeView
@@ -218,6 +219,14 @@ class LocalLibraryFragment : BaseFragment(), SelectedZimFileCallback {
218219
)
219220
}
220221
DialogHost(dialogShower as AlertDialogShower)
222+
DisposableEffect(Unit) {
223+
onDispose {
224+
// Dispose UI resources when this Compose view is removed. Compose disposes
225+
// its content before Fragment.onDestroyView(), so callback and listener cleanup
226+
// should happen here.
227+
destroyViews()
228+
}
229+
}
221230
}
222231
}
223232
}
@@ -611,6 +620,10 @@ class LocalLibraryFragment : BaseFragment(), SelectedZimFileCallback {
611620

612621
override fun onDestroyView() {
613622
super.onDestroyView()
623+
destroyViews()
624+
}
625+
626+
private fun destroyViews() {
614627
actionMode = null
615628
coroutineJobs.forEach {
616629
it.cancel()

core/src/main/java/org/kiwix/kiwixmobile/core/main/reader/CoreReaderFragment.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
5656
import androidx.compose.material3.SnackbarDuration
5757
import androidx.compose.material3.SnackbarHostState
5858
import androidx.compose.material3.SnackbarResult
59+
import androidx.compose.runtime.DisposableEffect
5960
import androidx.compose.runtime.LaunchedEffect
6061
import androidx.compose.runtime.getValue
6162
import androidx.compose.runtime.mutableStateListOf
@@ -480,6 +481,14 @@ abstract class CoreReaderFragment :
480481
navHostController = (requireActivity() as CoreMainActivity).navController
481482
)
482483
DialogHost(alertDialogShower as AlertDialogShower)
484+
DisposableEffect(Unit) {
485+
onDispose {
486+
// Dispose UI resources when this Compose view is removed. Compose disposes
487+
// its content before Fragment.onDestroyView(), so callback and listener cleanup
488+
// should happen here.
489+
destroyViews()
490+
}
491+
}
483492
}
484493
}
485494
addAlertDialogToDialogHost()
@@ -1053,8 +1062,27 @@ abstract class CoreReaderFragment :
10531062
}
10541063
}
10551064

1065+
private fun stopReadAloudSafely() {
1066+
runCatching {
1067+
tts?.apply {
1068+
setActionAndStartTTSService(ACTION_STOP_TTS)
1069+
shutdown()
1070+
tts = null
1071+
}
1072+
}.onFailure {
1073+
Log.e(
1074+
TAG_KIWIX,
1075+
"Could not stop read aloud service. Original exception = $it"
1076+
)
1077+
}
1078+
}
1079+
10561080
override fun onDestroyView() {
10571081
super.onDestroyView()
1082+
destroyViews()
1083+
}
1084+
1085+
private fun destroyViews() {
10581086
libkiwixBook = null
10591087
findInPageTitle = null
10601088
searchItemToOpen = null
@@ -1074,11 +1102,7 @@ abstract class CoreReaderFragment :
10741102
tempWebViewListForUndo.clear()
10751103
// create a base Activity class that class this.
10761104
activity?.let(::deleteCachedFiles)
1077-
tts?.apply {
1078-
setActionAndStartTTSService(ACTION_STOP_TTS)
1079-
shutdown()
1080-
tts = null
1081-
}
1105+
stopReadAloudSafely()
10821106
tempWebViewForUndo = null
10831107
// to fix IntroFragmentTest see https://github.com/kiwix/kiwix-android/pull/3217
10841108
try {
@@ -1095,7 +1119,6 @@ abstract class CoreReaderFragment :
10951119
composeView = null
10961120
}
10971121

1098-
@SuppressLint("ClickableViewAccessibility")
10991122
private fun unBindViewsAndBinding() {
11001123
compatCallback?.finish()
11011124
compatCallback = null

core/src/main/java/org/kiwix/kiwixmobile/core/search/SearchFragment.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ class SearchFragment : BaseFragment() {
137137
isDataLoading.value
138138
)
139139
DialogHost(dialogShower as AlertDialogShower)
140+
DisposableEffect(Unit) {
141+
onDispose {
142+
// Dispose UI resources when this Compose view is removed. Compose disposes
143+
// its content before Fragment.onDestroyView(), so callback and listener cleanup
144+
// should happen here.
145+
destroyViews()
146+
}
147+
}
140148
}
141149
}
142150
searchViewModel.setAlertDialogShower(dialogShower as AlertDialogShower)
@@ -217,6 +225,10 @@ class SearchFragment : BaseFragment() {
217225

218226
override fun onDestroyView() {
219227
super.onDestroyView()
228+
destroyViews()
229+
}
230+
231+
private fun destroyViews() {
220232
renderingJob?.cancel()
221233
renderingJob = null
222234
activity?.intent?.action = null

0 commit comments

Comments
 (0)