Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/src/main/assets/js/turbo.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@
}
}

restoreCurrentVisit() {
// A synthetic "restore" visit to the currently rendered location can occur when
// visiting a web -> native -> back to web screen. In this situation, the connect()
// callback (from Stimulus) in bridge component controllers will not be called,
// since they are already connected. We need to notify the web bridge library
// that the webview has been reattached to manually trigger connect() and notify
// the native app so the native bridge component view state can be restored.
document.dispatchEvent(new Event("native:restore"))
}

cacheSnapshot() {
if (window.Turbo) {
Turbo.session.view.cacheSnapshot()
}
}

// Current visit

issueRequestForVisitWithIdentifier(identifier) {
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/kotlin/dev/hotwire/core/turbo/session/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,27 @@ class Session(
visitRendered(visit.identifier)
visitCompleted(visit.identifier, restorationIdentifier)

webView.restoreCurrentVisit()

return true
}

/**
* Cache a snapshot of the current visit.
*/
fun cacheSnapshot() {
if (!isReady) return

currentVisit?.let {
logEvent("cacheSnapshot",
"location" to it.location,
"visitIdentifier" to it.identifier
)

webView.cacheSnapshot()
}
}

fun removeCallback(callback: SessionCallback) {
currentVisit?.let { visit ->
if (visit.callback == callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ open class HotwireWebView @JvmOverloads constructor(
runJavascript("turboNative.visitRenderedForColdBoot('$coldBootVisitIdentifier')")
}

internal fun cacheSnapshot() {
runJavascript("turboNative.cacheSnapshot()")
}

internal fun restoreCurrentVisit() {
runJavascript("turboNative.restoreCurrentVisit()")
}

internal fun installBridge(onBridgeInstalled: () -> Unit) {
val script = "window.turboNative == null"
val bridge = context.contentFromAsset("js/turbo.js")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ class SessionTest : BaseRepositoryTest() {

assertThat(session.restoreCurrentVisit(callback)).isTrue()
verify(callback, times(2)).visitCompleted(false)
verify(webView, times(1)).restoreCurrentVisit()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ open class HotwireWebBottomSheetFragment : HotwireBottomSheetFragment(), Hotwire

override fun onDestroyView() {
super.onDestroyView()
webDelegate.onDestroyView()
viewLifecycleOwner.lifecycle.removeObserver(bridgeDelegate)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ open class HotwireWebFragment : HotwireFragment(), HotwireWebFragmentCallback {

override fun onDestroyView() {
super.onDestroyView()
webDelegate.onDestroyView()
viewLifecycleOwner.lifecycle.removeObserver(bridgeDelegate)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ internal class HotwireWebFragmentDelegate(
}
}

/**
* Should be called by the implementing Fragment during
* [androidx.fragment.app.Fragment.onDestroyView].
*/
fun onDestroyView() {
// Manually cache a snapshot of the WebView when navigating from a
// web screen to a native screen. This allows a "restore" visit when
// revisiting this location again.
if (navigator.session.currentVisit?.location != navigator.location) {
navigator.session.cacheSnapshot()
}
}

/**
* Should be called by the implementing Fragment during [HotwireDestination.refresh].
*/
Expand Down