Closed nickfaces closed 7 months ago
@nickfaces Thank you for your feedback. I understand the issue you encountered and I want to support this feature in the library design. However, I am concerned that we may not be able to support it in a multiplatform situation. This is because the WebView in accompanist relies on Android-specific methods like saveState
and restoreState
to implement this feature. Unfortunately, these methods cannot be used on other platforms. As a result, the library is currently unable to save the state. I will explore alternative approaches to support this feature in the future, but it may take some time.
However, for your situation, I think the problem is not in my library. The main issue now is that Voyager destroys the Compose page when switching tabs, which results in a complete page refresh upon re-entry. In my opinion, this is actually unreasonable because tab switching on the home page is very frequent, and if every switch causes the destruction and reconstruction of the Compose page, it will lead to performance issues. We also encounter similar situations in our Android applications, where the tabs on our home page are generally composed of fragments. To avoid refreshing the view every time we switch, we use fragment.show/hide to toggle tabs, so it doesn't cause page reconstruction. I'm not quite sure how Voyager implements tab switching, but I don't think it should destroy the Compose page, resulting in a page reconstruction upon re-entry. Perhaps you can raise this issue with them and see if they have any suggestions.
Hi @KevinnZou
This problem is there even with AnimatedContent
, my web view state is lost when switching between composables using AnimatedContent
This is my code
@Composable
fun ReaderScreen(
url: String,
component: ReaderComponent
) {
val listState = rememberLazyListState()
val readerMode by component.readerMode.collectAsState()
val state = rememberWebViewState(url)
DisposableEffect(Unit){
state.webSettings.apply {
isJavaScriptEnabled = true
backgroundColor = Color.White
androidWebSettings.apply {
domStorageEnabled = true
safeBrowsingEnabled = true
isAlgorithmicDarkeningAllowed = true
}
}
onDispose { }
}
BaseScreenContent(component = component){
AnimatedContent(
targetState = readerMode,
transitionSpec = { fadeIn() togetherWith fadeOut() }
){
when(it){
ReaderMode.Reader -> InAppReader(Modifier.fillMaxSize(), listState, component)
ReaderMode.Web -> WebReader(state, Modifier.fillMaxSize())
}
}
}
}
@nickfaces Yes, as long as the onDispose
is called, the Webview state is lost. The only solution for this problem is to use rememberSaveableWebViewState
. However, supporting it is challenging because iOS does not have built-in functions like saveState
and restoreState
for WKWebview. Therefore, we might have to seek external assistance to resolve this issue.
There is an interesting thing in WKWebView https://developer.apple.com/documentation/webkit/wkwebview/3752236-interactionstate Maybe it could help, but only for iOS 15+
@nickfaces Thanks for your suggestions! It appears that interactionState is an NSObject
? We need to find a way to serialize and deserialize it like Android.
@KevinnZou If I understand it correctly, then this may help https://developer.apple.com/documentation/foundation/nscoding
@KevinnZou If I understand it correctly, then this may help https://developer.apple.com/documentation/foundation/nscoding
@nickfaces Thanks for your information! Yes, it works for the iOS side. However, we need to find a KMP way to handle the state save and restore which could be challenging.
Hello as I see you still did not solve the problem when navigating to another page and returning to the same page when i have web view still crushing ! Pls brother this is a big problem i think!
Hello Kevinn, thank you so much for the wonderful webView implementation. I encountered the same problem for the state save/restore of the webView. Without this functionality, the webView can only be displayed as static without any animation and navigation, which is a huge limitation. I just wonder if you can elevate this issue and find a temporarily solution? If it is not possible to make it work both for Android and iOS for now, would you please make it work for Android first, then make it work in the iOS in future release? Thanks.
@grandleaf Thank you for your understanding. As stated earlier, the problem is caused by the tab's force recomposition, and can only be temporarily resolved by state save/restore. I've been researching state save/restore support on iOS, but haven't found a way to make it work in a multiplatform environment. I will recheck next week and update you on the progress.
@KevinnZou, is it possible to provide a temporarily support for Android, then support the iOS part in the future? The reason for this is because the recomposition happens when the WebView in a navigation or even animation. Without a state save/restore, the usage of this component is very limited.
@grandleaf Yes, I will start working on supporting at least the Android side next month. I apologize for any inconvenience caused by this issue.
@KevinnZou. Thank you for the help. I think the root cause might be the Single Activity design for the Composable. Any activity will be dispose if it is not in the foreground (unnecessary IMHO).
@grandleaf I have added state save/restore functionality for Android and iOS 15+ in this pull request. It allows the webview to restore its state after recomposition. However, there may still be a flickering effect initially due to the recomposition. To resolve this issue completely, we may require official support to avoid recomposition in such situations.
@grandleaf I have added state save/restore functionality for Android and iOS 15+ in this pull request. It allows the webview to restore its state after recomposition. However, there may still be a flickering effect initially due to the recomposition. To resolve this issue completely, we may require official support to avoid recomposition in such situations.
@KevinnZou. Thank so much for your wonderful work. I do not expect the iOS part is also be accomplished 👍 One thing puzzled me a little bit: for such a common component, why these big companies do not even provide a basic support?
Thank you again!
@grandleaf I have added state save/restore functionality for Android and iOS 15+ in this pull request. It allows the webview to restore its state after recomposition. However, there may still be a flickering effect initially due to the recomposition. To resolve this issue completely, we may require official support to avoid recomposition in such situations.
The flicker issue is hard to solve since the WebView is too expensive to be recomposed even its states can be restored. This might be an issue for the single activity design of the framework. There is one approach that I found might solve this problem: simply hide the WebView. I planned to implement my own navigation instead of using the standard way.
@KevinnZou The rememberSaveable overload is only for URL Can you also add it for HTML data, this one
@Composable
fun rememberWebViewStateWithHTMLData(
data: String,
baseUrl: String? = null,
encoding: String = "utf-8",
mimeType: String? = null,
historyUrl: String? = null,
)
@Vaibhav2002 I don't have to look into it deeply now. But I did a quick test and it seems that Android WebView does not support restore state for HTML data. You are welcomed to submit a PR if you find a way to make it workable. Thanks!
@KevinnZou the fix rememberSaveableWebViewState
still loads the url again when switching composables using a AnimatedContent
This is my code, am i doing anything wrong
@Composable
internal fun WebReader(
url: String,
modifier:Modifier = Modifier
) {
val state = rememberSaveableWebViewState(url)
val navigator = rememberWebViewNavigator()
DisposableEffect(Unit){
state.webSettings.apply {
isJavaScriptEnabled = true
backgroundColor = Color.White
androidWebSettings.apply {
domStorageEnabled = true
safeBrowsingEnabled = true
isAlgorithmicDarkeningAllowed = true
}
}
onDispose { }
}
LaunchedEffect(navigator){
navigator.loadUrl(url)
}
Column(modifier = modifier) {
if (state.loadingState !is LoadingState.Finished) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(3.dp),
trackColor = MaterialTheme.colorScheme.surface,
color = LocalContentColor.current
)
}
WebView(
state = state,
navigator = navigator,
modifier = Modifier.fillMaxSize().navigationBarsPadding()
)
}
}
My animated content code
AnimatedContent(
targetState = readerMode,
transitionSpec = { fadeIn() togetherWith fadeOut() }
){
when(it){
ReaderMode.Reader -> InAppReader(Modifier.fillMaxSize(), listState, component)
ReaderMode.Web -> WebReader(url, Modifier.fillMaxSize())
}
}
@Vaibhav2002 Does AnimatedContent implement SaveableStateHolder? rememberSaveableWebViewState
only works with SaveableStateHolder
.
@Vaibhav2002 Does AnimatedContent implement SaveableStateHolder?
rememberSaveableWebViewState
only works withSaveableStateHolder
.
@KevinnZou I does not work even without Animated Content
This is my code
when (readerMode) {
ReaderMode.Reader -> InAppReader(Modifier.fillMaxSize(), listState, component)
ReaderMode.Web -> WebReader(url, Modifier.fillMaxSize())
}
This is my webview composable
@Composable
internal fun WebReader(
url: String,
modifier:Modifier = Modifier
) {
val state = rememberSaveableWebViewState(url)
val navigator = rememberWebViewNavigator()
LaunchedEffect(url){
navigator.loadUrl(url)
}
DisposableEffect(Unit){
state.webSettings.apply {
isJavaScriptEnabled = true
backgroundColor = Color.White
androidWebSettings.apply {
domStorageEnabled = true
safeBrowsingEnabled = true
isAlgorithmicDarkeningAllowed = true
}
}
onDispose { }
}
Column(modifier = modifier) {
if (state.loadingState !is LoadingState.Finished) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().height(3.dp),
trackColor = MaterialTheme.colorScheme.surface,
color = LocalContentColor.current
)
}
WebView(
state = state,
navigator = navigator,
modifier = Modifier.fillMaxSize().navigationBarsPadding()
)
}
}
@Vaibhav2002 rememberSavable
only works with SaveableStateHolder
, you can check this sample for reference.
@KevinnZou this works, but there is a blink and my web view scroll state is lost and it does not work when i use AnimatedContent to add animation
Also this SaveableStateHolder
approach does not work when WebView is a part of a LazyColumn Item.
There are several screens and tabs in my application. On one of the screens in the tab there is a webview. Every time you switch between screens or tablets, webview refreshes the page. I'm not sure if the problem is in the webview or in voyager navigation, or even in my code. For the accompanist, I found a similar problem where the solution was to use rememberSaveableWebViewState https://github.com/google/accompanist/pull/1557 However, there is no such class in your library.