Skip to content

Commit 605ebd6

Browse files
authored
Merge pull request #351 from hotwired/fix-same-page-restore
Backport the ability to fix same page restores from `hotwire-native-android`
2 parents 7981e7c + f68c6d0 commit 605ebd6

File tree

7 files changed

+89
-6
lines changed

7 files changed

+89
-6
lines changed

turbo/src/main/assets/js/turbo_bridge.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@
5757
}
5858
}
5959

60+
restoreCurrentVisit() {
61+
// A synthetic "restore" visit to the currently rendered location can occur when
62+
// visiting a web -> native -> back to web screen. In this situation, the connect()
63+
// callback (from Stimulus) in bridge component controllers will not be called,
64+
// since they are already connected. We need to notify the web bridge library
65+
// that the webview has been reattached to manually trigger connect() and notify
66+
// the native app so the native bridge component view state can be restored.
67+
document.dispatchEvent(new Event("native:restore"))
68+
}
69+
70+
cacheSnapshot() {
71+
if (window.Turbo) {
72+
Turbo.session.view.cacheSnapshot()
73+
}
74+
}
75+
6076
// Current visit
6177

6278
issueRequestForVisitWithIdentifier(identifier) {

turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboWebFragmentDelegate.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import dev.hotwire.turbo.session.TurboSession
1818
import dev.hotwire.turbo.session.TurboSessionCallback
1919
import dev.hotwire.turbo.session.TurboSessionModalResult
2020
import dev.hotwire.turbo.util.dispatcherProvider
21+
import dev.hotwire.turbo.util.location
2122
import dev.hotwire.turbo.views.TurboView
2223
import dev.hotwire.turbo.views.TurboWebView
2324
import dev.hotwire.turbo.visit.TurboVisit
@@ -132,6 +133,24 @@ internal class TurboWebFragmentDelegate(
132133
}
133134
}
134135

136+
/**
137+
* Should be called by the implementing Fragment during
138+
* [androidx.fragment.app.Fragment.onDestroyView].
139+
*/
140+
fun onDestroyView() {
141+
// Manually cache a snapshot of the WebView when navigating from a
142+
// web screen to a native screen. This allows a "restore" visit when
143+
// revisiting this location again.
144+
145+
val navHost = navDestination.sessionNavHostFragment
146+
val currentBackStackEntry = navHost.navController.currentBackStackEntry
147+
val currentLocation = currentBackStackEntry?.location
148+
149+
if (session().currentVisit?.location != currentLocation) {
150+
session().cacheSnapshot()
151+
}
152+
}
153+
135154
/**
136155
* Should be called by the implementing Fragment during
137156
* [dev.hotwire.turbo.nav.TurboNavDestination.refresh]
@@ -323,8 +342,8 @@ internal class TurboWebFragmentDelegate(
323342
// Visit every time the WebView is reattached to the current Fragment.
324343
if (isWebViewAttachedToNewDestination) {
325344
val currentSessionVisitRestored = !isInitialVisit &&
326-
session().currentVisit?.destinationIdentifier == identifier &&
327-
session().restoreCurrentVisit(this)
345+
session().currentVisit?.destinationIdentifier == identifier &&
346+
session().restoreCurrentVisit(this)
328347

329348
if (!currentSessionVisitRestored) {
330349
showProgressView(location)

turbo/src/main/kotlin/dev/hotwire/turbo/fragments/TurboWebBottomSheetDialogFragment.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ abstract class TurboWebBottomSheetDialogFragment : TurboBottomSheetDialogFragmen
4040
webDelegate.onViewCreated()
4141
}
4242

43+
override fun onDestroyView() {
44+
super.onDestroyView()
45+
webDelegate.onDestroyView()
46+
}
47+
4348
override fun activityResultLauncher(requestCode: Int): ActivityResultLauncher<Intent>? {
4449
return when (requestCode) {
4550
TURBO_REQUEST_CODE_FILES -> webDelegate.fileChooserResultLauncher

turbo/src/main/kotlin/dev/hotwire/turbo/fragments/TurboWebFragment.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import android.view.ViewGroup
99
import androidx.activity.result.ActivityResultLauncher
1010
import dev.hotwire.turbo.R
1111
import dev.hotwire.turbo.delegates.TurboWebFragmentDelegate
12+
import dev.hotwire.turbo.errors.TurboVisitError
1213
import dev.hotwire.turbo.session.TurboSessionModalResult
1314
import dev.hotwire.turbo.util.TURBO_REQUEST_CODE_FILES
1415
import dev.hotwire.turbo.views.TurboView
1516
import dev.hotwire.turbo.views.TurboWebChromeClient
16-
import dev.hotwire.turbo.errors.TurboVisitError
1717

1818
/**
1919
* The base class from which all web "standard" fragments (non-dialogs) in a
@@ -38,6 +38,11 @@ abstract class TurboWebFragment : TurboFragment(), TurboWebFragmentCallback {
3838
webDelegate.onViewCreated()
3939
}
4040

41+
override fun onDestroyView() {
42+
super.onDestroyView()
43+
webDelegate.onDestroyView()
44+
}
45+
4146
override fun onStart() {
4247
super.onStart()
4348

turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import android.content.Context
55
import android.graphics.Bitmap
66
import android.net.http.SslError
77
import android.util.SparseArray
8-
import android.webkit.*
8+
import android.webkit.HttpAuthHandler
9+
import android.webkit.JavascriptInterface
10+
import android.webkit.RenderProcessGoneDetail
11+
import android.webkit.SslErrorHandler
12+
import android.webkit.WebChromeClient
13+
import android.webkit.WebResourceRequest
14+
import android.webkit.WebResourceResponse
15+
import android.webkit.WebView
916
import androidx.appcompat.app.AppCompatActivity
1017
import androidx.lifecycle.lifecycleScope
1118
import androidx.webkit.WebResourceErrorCompat
@@ -20,7 +27,11 @@ import dev.hotwire.turbo.errors.HttpError
2027
import dev.hotwire.turbo.errors.LoadError
2128
import dev.hotwire.turbo.errors.WebError
2229
import dev.hotwire.turbo.errors.WebSslError
23-
import dev.hotwire.turbo.http.*
30+
import dev.hotwire.turbo.http.TurboHttpClient
31+
import dev.hotwire.turbo.http.TurboHttpRepository
32+
import dev.hotwire.turbo.http.TurboOfflineRequestHandler
33+
import dev.hotwire.turbo.http.TurboPreCacheRequest
34+
import dev.hotwire.turbo.http.TurboWebViewRequestInterceptor
2435
import dev.hotwire.turbo.nav.TurboNavDestination
2536
import dev.hotwire.turbo.util.isHttpGetRequest
2637
import dev.hotwire.turbo.util.logEvent
@@ -30,7 +41,7 @@ import dev.hotwire.turbo.views.TurboWebView
3041
import dev.hotwire.turbo.visit.TurboVisit
3142
import dev.hotwire.turbo.visit.TurboVisitAction
3243
import dev.hotwire.turbo.visit.TurboVisitOptions
33-
import java.util.*
44+
import java.util.Date
3445

3546
/**
3647
* This class is primarily responsible for managing an instance of an Android WebView that will
@@ -170,9 +181,27 @@ class TurboSession internal constructor(
170181
visitRendered(visit.identifier)
171182
visitCompleted(visit.identifier, restorationIdentifier)
172183

184+
webView.restoreCurrentVisit()
185+
173186
return true
174187
}
175188

189+
/**
190+
* Cache a snapshot of the current visit.
191+
*/
192+
fun cacheSnapshot() {
193+
if (!isReady) return
194+
195+
currentVisit?.let {
196+
logEvent("cacheSnapshot",
197+
"location" to it.location,
198+
"visitIdentifier" to it.identifier
199+
)
200+
201+
webView.cacheSnapshot()
202+
}
203+
}
204+
176205
internal fun removeCallback(callback: TurboSessionCallback) {
177206
currentVisit?.let { visit ->
178207
if (visit.callback == callback) {

turbo/src/main/kotlin/dev/hotwire/turbo/views/TurboWebView.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ open class TurboWebView @JvmOverloads constructor(context: Context, attrs: Attri
6060
runJavascript("turboNative.visitRenderedForColdBoot('$coldBootVisitIdentifier')")
6161
}
6262

63+
internal fun cacheSnapshot() {
64+
runJavascript("turboNative.cacheSnapshot()")
65+
}
66+
67+
internal fun restoreCurrentVisit() {
68+
runJavascript("turboNative.restoreCurrentVisit()")
69+
}
70+
6371
internal fun installBridge(onBridgeInstalled: () -> Unit) {
6472
val script = "window.turboNative == null"
6573
val bridge = context.contentFromAsset("js/turbo_bridge.js")

turbo/src/test/kotlin/dev/hotwire/turbo/session/TurboSessionTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ class TurboSessionTest {
216216

217217
assertThat(session.restoreCurrentVisit(callback)).isTrue()
218218
verify(callback, times(2)).visitCompleted(false)
219+
verify(webView, times(1)).restoreCurrentVisit()
219220
}
220221

221222
@Test

0 commit comments

Comments
 (0)