Skip to content

Commit

Permalink
Fix crash when restoring the fragment hierarchy after process death (r…
Browse files Browse the repository at this point in the history
  • Loading branch information
mickael-menu committed Nov 7, 2023
1 parent f87dd95 commit 8bf59ed
Show file tree
Hide file tree
Showing 16 changed files with 1,184 additions and 37 deletions.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ All notable changes to this project will be documented in this file. Take a look
* 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)).
1. Override loading non-linear resources with `VisualNavigator.Listener.shouldJumpToLink()`.
2. Present a new `EpubNavigatorFragment` by providing a custom `readingOrder` with only this resource to the constructor.
* Added dummy navigator fragment factories to prevent crashes caused by Android restoring the fragments after a process death.
* 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.
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
val publication = model.publication ?: run {
childFragmentManager.fragmentFactory = EpubNavigatorFragment.createDummyFactory()
super.onCreate(savedInstanceState)

requireActivity().finish()
// or
navController?.popBackStack()

return
}

// Create the real navigator factory as usual...
}
```

#### Streamer

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

#### Streamer

* Fix issue with the TTS starting from the beginning of the chapter instead of the current position.
* Fixed issue with the TTS starting from the beginning of the chapter instead of the current position.

## [2.3.0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import org.readium.r2.navigator.preferences.ReadingProgression
import org.readium.r2.shared.ExperimentalReadiumApi
import org.readium.r2.shared.fetcher.Resource
import org.readium.r2.shared.publication.Link
import org.readium.r2.shared.publication.LocalizedString
import org.readium.r2.shared.publication.Manifest
import org.readium.r2.shared.publication.Metadata
import org.readium.r2.shared.publication.Publication
import timber.log.Timber

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

// Dummy constructor to address https://github.com/readium/kotlin-toolkit/issues/395
constructor() : this(
publication = Publication(
manifest = Manifest(
metadata = Metadata(
identifier = "readium:dummy",
localizedTitle = LocalizedString("")
)
)
),
link = Link(href = "publication.pdf", type = "application/pdf"),
initialPageIndex = 0,
settings = PdfiumSettings(
fit = Fit.WIDTH,
pageSpacing = 0.0,
readingProgression = ReadingProgression.LTR,
scrollAxis = Axis.VERTICAL
),
appListener = null,
navigatorListener = null
)

interface Listener {
/** Called when configuring [PDFView]. */
fun onConfigurePdfView(configurator: PDFView.Configurator) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.readium.r2.navigator

import org.readium.r2.shared.publication.LocalizedString
import org.readium.r2.shared.publication.Manifest
import org.readium.r2.shared.publication.Metadata
import org.readium.r2.shared.publication.Publication

object RestorationNotSupportedException : Exception(
"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`."
)

internal val dummyPublication = Publication(
Manifest(
metadata = Metadata(
identifier = "readium:dummy",
localizedTitle = LocalizedString("")
)
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,11 @@ class EpubNavigatorFragment internal constructor(

override fun onResume() {
super.onResume()

if (publication == dummyPublication) {
throw RestorationNotSupportedException
}

notifyCurrentLocation()
}

Expand Down Expand Up @@ -1066,6 +1071,27 @@ class EpubNavigatorFragment internal constructor(
)
}

/**
* Creates a factory for a dummy [EpubNavigatorFragment].
*
* Used when Android restore the [EpubNavigatorFragment] after the process was killed. You
* need to make sure the fragment is removed from the screen before [onResume] is called.
*/
fun createDummyFactory(): FragmentFactory = createFragmentFactory {
EpubNavigatorFragment(
publication = dummyPublication,
baseUrl = null,
initialLocator = Locator(href = "#", type = "application/xhtml+xml"),
readingOrder = null,
initialPreferences = EpubPreferences(),
listener = null,
paginationListener = null,
epubLayout = EpubLayout.REFLOWABLE,
defaults = EpubDefaults(),
configuration = Configuration()
)
}

/**
* Returns a URL to the application asset at [path], served in the web views.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.runBlocking
import org.readium.r2.navigator.RestorationNotSupportedException
import org.readium.r2.navigator.SimplePresentation
import org.readium.r2.navigator.VisualNavigator
import org.readium.r2.navigator.databinding.ActivityR2ViewpagerBinding
import org.readium.r2.navigator.dummyPublication
import org.readium.r2.navigator.extensions.layoutDirectionIsRTL
import org.readium.r2.navigator.pager.R2CbzPageFragment
import org.readium.r2.navigator.pager.R2PagerAdapter
Expand Down Expand Up @@ -155,6 +157,14 @@ class ImageNavigatorFragment private constructor(
notifyCurrentLocation()
}

override fun onResume() {
super.onResume()

if (publication == dummyPublication) {
throw RestorationNotSupportedException
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
Expand All @@ -173,10 +183,10 @@ class ImageNavigatorFragment private constructor(
}

private fun notifyCurrentLocation() {
val locator = positions[resourcePager.currentItem]
if (locator == _currentLocator.value) {
return
}
val locator = positions.getOrNull(resourcePager.currentItem)
?.takeUnless { it == _currentLocator.value }
?: return

_currentLocator.value = locator
}

Expand Down Expand Up @@ -240,5 +250,19 @@ class ImageNavigatorFragment private constructor(
listener: Listener? = null
): FragmentFactory =
createFragmentFactory { ImageNavigatorFragment(publication, initialLocator, listener) }

/**
* Creates a factory for a dummy [ImageNavigatorFragment].
*
* Used when Android restore the [ImageNavigatorFragment] after the process was killed. You
* need to make sure the fragment is removed from the screen before `onResume` is called.
*/
fun createDummyFactory(): FragmentFactory = createFragmentFactory {
ImageNavigatorFragment(
publication = dummyPublication,
initialLocator = Locator(href = "#", type = "image/jpg"),
listener = null
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.readium.r2.navigator.R
import org.readium.r2.navigator.RestorationNotSupportedException
import org.readium.r2.navigator.VisualNavigator
import org.readium.r2.navigator.dummyPublication
import org.readium.r2.navigator.extensions.page
import org.readium.r2.navigator.preferences.Configurable
import org.readium.r2.navigator.preferences.PreferencesEditor
Expand Down Expand Up @@ -87,15 +89,35 @@ class PdfNavigatorFragment<S : Configurable.Settings, P : Configurable.Preferenc
listener, pdfEngineProvider
)
}

/**
* Creates a factory for a dummy [PdfNavigatorFragment].
*
* Used when Android restore the [PdfNavigatorFragment] after the process was killed. You need
* to make sure the fragment is removed from the screen before `onResume` is called.
*/
fun <P : Configurable.Preferences<P>> createDummyFactory(
pdfEngineProvider: PdfEngineProvider<*, P, *>
): FragmentFactory = createFragmentFactory {
PdfNavigatorFragment(
publication = dummyPublication,
initialLocator = Locator(href = "#", type = "application/pdf"),
initialPreferences = pdfEngineProvider.createEmptyPreferences(),
listener = null,
pdfEngineProvider = pdfEngineProvider
)
}
}

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

require(
publication.readingOrder.count() == 1 &&
publication.readingOrder.first().mediaType.matches(MediaType.PDF)
) { "[PdfNavigatorFragment] currently supports only publications with a single PDF for reading order" }
if (publication != dummyPublication) {
require(
publication.readingOrder.count() == 1 &&
publication.readingOrder.first().mediaType.matches(MediaType.PDF)
) { "[PdfNavigatorFragment] currently supports only publications with a single PDF for reading order" }
}
}

// Configurable
Expand Down Expand Up @@ -167,6 +189,14 @@ class PdfNavigatorFragment<S : Configurable.Settings, P : Configurable.Preferenc
}
}

override fun onResume() {
super.onResume()

if (publication == dummyPublication) {
throw RestorationNotSupportedException
}
}

private suspend fun createPdfDocumentFragment(locator: Locator, settings: S): PdfDocumentFragment<S>? {
val link = publication.linkWithHref(locator.href) ?: return null

Expand Down
Loading

0 comments on commit 8bf59ed

Please sign in to comment.