Skip to content

Commit 8bf59ed

Browse files
authored
Fix crash when restoring the fragment hierarchy after process death (#418)
1 parent f87dd95 commit 8bf59ed

File tree

16 files changed

+1184
-37
lines changed

16 files changed

+1184
-37
lines changed

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ All notable changes to this project will be documented in this file. Take a look
1919
* Support for non-linear EPUB resources with an opt-in in reading apps (contributed by @chrfalch in [#375](https://github.com/readium/kotlin-toolkit/pull/375) and [#376](https://github.com/readium/kotlin-toolkit/pull/376)).
2020
1. Override loading non-linear resources with `VisualNavigator.Listener.shouldJumpToLink()`.
2121
2. Present a new `EpubNavigatorFragment` by providing a custom `readingOrder` with only this resource to the constructor.
22+
* Added dummy navigator fragment factories to prevent crashes caused by Android restoring the fragments after a process death.
23+
* To use it, set the dummy fragment factory when you don't have access to the `Publication` instance. Then, either finish the `Activity` or pop the fragment from the UI before it resumes.
24+
```kotlin
25+
override fun onCreate(savedInstanceState: Bundle?) {
26+
val publication = model.publication ?: run {
27+
childFragmentManager.fragmentFactory = EpubNavigatorFragment.createDummyFactory()
28+
super.onCreate(savedInstanceState)
29+
30+
requireActivity().finish()
31+
// or
32+
navController?.popBackStack()
33+
34+
return
35+
}
36+
37+
// Create the real navigator factory as usual...
38+
}
39+
```
2240

2341
#### Streamer
2442

@@ -39,7 +57,7 @@ All notable changes to this project will be documented in this file. Take a look
3957

4058
#### Streamer
4159

42-
* Fix issue with the TTS starting from the beginning of the chapter instead of the current position.
60+
* Fixed issue with the TTS starting from the beginning of the chapter instead of the current position.
4361

4462
## [2.3.0]
4563

readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDocumentFragment.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import org.readium.r2.navigator.preferences.ReadingProgression
2323
import org.readium.r2.shared.ExperimentalReadiumApi
2424
import org.readium.r2.shared.fetcher.Resource
2525
import org.readium.r2.shared.publication.Link
26+
import org.readium.r2.shared.publication.LocalizedString
27+
import org.readium.r2.shared.publication.Manifest
28+
import org.readium.r2.shared.publication.Metadata
2629
import org.readium.r2.shared.publication.Publication
2730
import timber.log.Timber
2831

@@ -36,6 +39,28 @@ class PdfiumDocumentFragment internal constructor(
3639
private val navigatorListener: PdfDocumentFragment.Listener?
3740
) : PdfDocumentFragment<PdfiumSettings>() {
3841

42+
// Dummy constructor to address https://github.com/readium/kotlin-toolkit/issues/395
43+
constructor() : this(
44+
publication = Publication(
45+
manifest = Manifest(
46+
metadata = Metadata(
47+
identifier = "readium:dummy",
48+
localizedTitle = LocalizedString("")
49+
)
50+
)
51+
),
52+
link = Link(href = "publication.pdf", type = "application/pdf"),
53+
initialPageIndex = 0,
54+
settings = PdfiumSettings(
55+
fit = Fit.WIDTH,
56+
pageSpacing = 0.0,
57+
readingProgression = ReadingProgression.LTR,
58+
scrollAxis = Axis.VERTICAL
59+
),
60+
appListener = null,
61+
navigatorListener = null
62+
)
63+
3964
interface Listener {
4065
/** Called when configuring [PDFView]. */
4166
fun onConfigurePdfView(configurator: PDFView.Configurator) {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.readium.r2.navigator
2+
3+
import org.readium.r2.shared.publication.LocalizedString
4+
import org.readium.r2.shared.publication.Manifest
5+
import org.readium.r2.shared.publication.Metadata
6+
import org.readium.r2.shared.publication.Publication
7+
8+
object RestorationNotSupportedException : Exception(
9+
"Restoration of the navigator fragment after process death is not supported. You must pop it from the back stack or finish the host Activity before `onResume`."
10+
)
11+
12+
internal val dummyPublication = Publication(
13+
Manifest(
14+
metadata = Metadata(
15+
identifier = "readium:dummy",
16+
localizedTitle = LocalizedString("")
17+
)
18+
)
19+
)

readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,11 @@ class EpubNavigatorFragment internal constructor(
550550

551551
override fun onResume() {
552552
super.onResume()
553+
554+
if (publication == dummyPublication) {
555+
throw RestorationNotSupportedException
556+
}
557+
553558
notifyCurrentLocation()
554559
}
555560

@@ -1066,6 +1071,27 @@ class EpubNavigatorFragment internal constructor(
10661071
)
10671072
}
10681073

1074+
/**
1075+
* Creates a factory for a dummy [EpubNavigatorFragment].
1076+
*
1077+
* Used when Android restore the [EpubNavigatorFragment] after the process was killed. You
1078+
* need to make sure the fragment is removed from the screen before [onResume] is called.
1079+
*/
1080+
fun createDummyFactory(): FragmentFactory = createFragmentFactory {
1081+
EpubNavigatorFragment(
1082+
publication = dummyPublication,
1083+
baseUrl = null,
1084+
initialLocator = Locator(href = "#", type = "application/xhtml+xml"),
1085+
readingOrder = null,
1086+
initialPreferences = EpubPreferences(),
1087+
listener = null,
1088+
paginationListener = null,
1089+
epubLayout = EpubLayout.REFLOWABLE,
1090+
defaults = EpubDefaults(),
1091+
configuration = Configuration()
1092+
)
1093+
}
1094+
10691095
/**
10701096
* Returns a URL to the application asset at [path], served in the web views.
10711097
*/

readium/navigator/src/main/java/org/readium/r2/navigator/image/ImageNavigatorFragment.kt

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
2323
import kotlinx.coroutines.flow.StateFlow
2424
import kotlinx.coroutines.flow.asStateFlow
2525
import kotlinx.coroutines.runBlocking
26+
import org.readium.r2.navigator.RestorationNotSupportedException
2627
import org.readium.r2.navigator.SimplePresentation
2728
import org.readium.r2.navigator.VisualNavigator
2829
import org.readium.r2.navigator.databinding.ActivityR2ViewpagerBinding
30+
import org.readium.r2.navigator.dummyPublication
2931
import org.readium.r2.navigator.extensions.layoutDirectionIsRTL
3032
import org.readium.r2.navigator.pager.R2CbzPageFragment
3133
import org.readium.r2.navigator.pager.R2PagerAdapter
@@ -155,6 +157,14 @@ class ImageNavigatorFragment private constructor(
155157
notifyCurrentLocation()
156158
}
157159

160+
override fun onResume() {
161+
super.onResume()
162+
163+
if (publication == dummyPublication) {
164+
throw RestorationNotSupportedException
165+
}
166+
}
167+
158168
override fun onDestroyView() {
159169
super.onDestroyView()
160170
_binding = null
@@ -173,10 +183,10 @@ class ImageNavigatorFragment private constructor(
173183
}
174184

175185
private fun notifyCurrentLocation() {
176-
val locator = positions[resourcePager.currentItem]
177-
if (locator == _currentLocator.value) {
178-
return
179-
}
186+
val locator = positions.getOrNull(resourcePager.currentItem)
187+
?.takeUnless { it == _currentLocator.value }
188+
?: return
189+
180190
_currentLocator.value = locator
181191
}
182192

@@ -240,5 +250,19 @@ class ImageNavigatorFragment private constructor(
240250
listener: Listener? = null
241251
): FragmentFactory =
242252
createFragmentFactory { ImageNavigatorFragment(publication, initialLocator, listener) }
253+
254+
/**
255+
* Creates a factory for a dummy [ImageNavigatorFragment].
256+
*
257+
* Used when Android restore the [ImageNavigatorFragment] after the process was killed. You
258+
* need to make sure the fragment is removed from the screen before `onResume` is called.
259+
*/
260+
fun createDummyFactory(): FragmentFactory = createFragmentFactory {
261+
ImageNavigatorFragment(
262+
publication = dummyPublication,
263+
initialLocator = Locator(href = "#", type = "image/jpg"),
264+
listener = null
265+
)
266+
}
243267
}
244268
}

readium/navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import androidx.lifecycle.repeatOnLifecycle
1818
import kotlinx.coroutines.flow.*
1919
import kotlinx.coroutines.launch
2020
import org.readium.r2.navigator.R
21+
import org.readium.r2.navigator.RestorationNotSupportedException
2122
import org.readium.r2.navigator.VisualNavigator
23+
import org.readium.r2.navigator.dummyPublication
2224
import org.readium.r2.navigator.extensions.page
2325
import org.readium.r2.navigator.preferences.Configurable
2426
import org.readium.r2.navigator.preferences.PreferencesEditor
@@ -87,15 +89,35 @@ class PdfNavigatorFragment<S : Configurable.Settings, P : Configurable.Preferenc
8789
listener, pdfEngineProvider
8890
)
8991
}
92+
93+
/**
94+
* Creates a factory for a dummy [PdfNavigatorFragment].
95+
*
96+
* Used when Android restore the [PdfNavigatorFragment] after the process was killed. You need
97+
* to make sure the fragment is removed from the screen before `onResume` is called.
98+
*/
99+
fun <P : Configurable.Preferences<P>> createDummyFactory(
100+
pdfEngineProvider: PdfEngineProvider<*, P, *>
101+
): FragmentFactory = createFragmentFactory {
102+
PdfNavigatorFragment(
103+
publication = dummyPublication,
104+
initialLocator = Locator(href = "#", type = "application/pdf"),
105+
initialPreferences = pdfEngineProvider.createEmptyPreferences(),
106+
listener = null,
107+
pdfEngineProvider = pdfEngineProvider
108+
)
109+
}
90110
}
91111

92112
init {
93113
require(!publication.isRestricted) { "The provided publication is restricted. Check that any DRM was properly unlocked using a Content Protection." }
94114

95-
require(
96-
publication.readingOrder.count() == 1 &&
97-
publication.readingOrder.first().mediaType.matches(MediaType.PDF)
98-
) { "[PdfNavigatorFragment] currently supports only publications with a single PDF for reading order" }
115+
if (publication != dummyPublication) {
116+
require(
117+
publication.readingOrder.count() == 1 &&
118+
publication.readingOrder.first().mediaType.matches(MediaType.PDF)
119+
) { "[PdfNavigatorFragment] currently supports only publications with a single PDF for reading order" }
120+
}
99121
}
100122

101123
// Configurable
@@ -167,6 +189,14 @@ class PdfNavigatorFragment<S : Configurable.Settings, P : Configurable.Preferenc
167189
}
168190
}
169191

192+
override fun onResume() {
193+
super.onResume()
194+
195+
if (publication == dummyPublication) {
196+
throw RestorationNotSupportedException
197+
}
198+
}
199+
170200
private suspend fun createPdfDocumentFragment(locator: Locator, settings: S): PdfDocumentFragment<S>? {
171201
val link = publication.linkWithHref(locator.href) ?: return null
172202

0 commit comments

Comments
 (0)