I have a webview with one URL, whenever it redirects within the webview, I load the same composable again with a new URL with a new title, as the redirection happens so fast it is happening before the actual composable composes entirely, so it crashes saying java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. I am currently using ram costa's compose destination library
navigator.value?.navigate(direction = MenuViewDestination,onlyIfResumed = true)
, I have used compose provided navigation as well before, I was facing the same problem in both cases, if I navigate with only resumed true then the page is not navigated itself for some reason, and I can't handle the exception as it is happening internally. Here i have attached the composable used in menu composable to load URL,
/* Adding custom accompanist WebView*/
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun LoadMenuWebView(mainViewModel: MainViewModel, webViewModel: MenuWebViewModel, url: String?) {
Timber.i("LoadWebView from menu $url")
val context = LocalContext.current
var extraHeaders: Map<String, String?>?
webViewModel.menuWebViewState.value = url?.let {
rememberWebViewState(
it
)
}
mainViewModel.currentWebViewClient.value = remember {
getWebViewClient(
context,
mainViewModel.backEnabled,
mainViewModel.progressVisible,
mainViewModel.cookieManager,
mainViewModel
)
}
val state by remember { webViewModel.menuWebViewState }
val navigator = rememberWebViewNavigator()
// A custom WebChromeClient can be provided via subclassing
if (state != null) {
ObMenuWebView(
state = state!!,
captureBackPresses = false,
onCreated = { webview ->
webview.settings.apply {
javaScriptEnabled = true
builtInZoomControls = false
displayZoomControls = false
loadWithOverviewMode = true
cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
javaScriptCanOpenWindowsAutomatically = true
mediaPlaybackRequiresUserGesture = false
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
useWideViewPort = true
domStorageEnabled = true
// Allow open _blank pages in browser
setSupportMultipleWindows(true)
}
webview.addJavascriptClient(mainViewModel, context)
},
navigator = navigator,
client = remember {
mainViewModel.currentWebViewClient.value
},
chromeClient = remember {
ExternalPagesClient(context, mainViewModel._showExternalLinkDialog)
},
webViewModel = webViewModel
)
}
}
important thing to note here is i have modified the accompanist webview a bit and using ViewModel to store the instance of the existing webview because accompanist webview recomposes everytime i navigate between composable within app which leads to reloads of webview, so did that workaround for now, i know storing view instances in ViewModel might cause memory leaks but i had no other way,
/**
* A wrapper around the Android View WebView to provide a basic WebView composable.
*
* If you require more customisation you are most likely better rolling your own and using this
* wrapper as an example.
*
* @param state The webview state holder where the Uri to load is defined.
* @param captureBackPresses Set to true to have this Composable capture back presses and navigate
* the WebView back.
* @param navigator An optional navigator object that can be used to control the WebView's
* navigation from outside the composable.
* @param onCreated Called when the WebView is first created, this can be used to set additional
* settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be
* subsequently overwritten after this lambda is called.
* @param client Provides access to WebViewClient via subclassing
* @param chromeClient Provides access to WebChromeClient via subclassing
* @sample com.google.accompanist.sample.webview.BasicWebViewSample
*/
@Composable
fun ObMenuWebView(
state: com.ob_core_framework.base.WebViewState,
modifier: Modifier = Modifier,
captureBackPresses: Boolean = true,
navigator: WebViewNavigator = rememberWebViewNavigator(),
onCreated: (WebView) -> Unit = {},
client: com.ob_core_framework.base.AccompanistWebViewClient = remember { com.ob_core_framework.base.AccompanistWebViewClient() },
chromeClient: com.ob_core_framework.base.AccompanistWebChromeClient = remember { com.ob_core_framework.base.AccompanistWebChromeClient() },
webViewModel: MenuWebViewModel
) {
var existingWebView by remember { webViewModel.existingWebView }
BackHandler(captureBackPresses && navigator.canGoBack) {
existingWebView?.goBack()
}
LaunchedEffect(existingWebView, navigator) {
with(navigator) { existingWebView?.handleNavigationEvents() }
}
// Set the state of the client and chrome client
// This is done internally to ensure they always are the same instance as the
// parent Web composable
client.stateLocal = state
client.navigatorLocal = navigator
chromeClient.stateLocal = state
AndroidView(
factory = { context ->
existingWebView ?: WebView(context).apply {
onCreated(this)
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webChromeClient = chromeClient
webViewClient = client
}.also {
existingWebView = it
}
},
modifier = modifier
) { view ->
when (val content = state.content) {
is WebContent.Url -> {
val url = content.url
if (url.isNotEmpty() && url != view.url) {
view.loadUrl(url, content.additionalHttpHeaders.toMutableMap())
}
}
is WebContent.Data -> {
view.loadDataWithBaseURL(content.baseUrl, content.data, null, "utf-8", null)
}
}
navigator.canGoBack = view.canGoBack()
navigator.canGoForward = view.canGoForward()
}
}
An Android View cannot have more than one parents.
In your AndroidView lambda, you can check if the view has already been given a parent, which will be true during recompositions because you are remembering your View. If it has a parent, you should remove the view from the parent's view hierarchy.
You can modify the factory in ObMenuWebView and add the following
factory = { context ->
existingWebView ?: WebView(context).apply {
onCreated(this)
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webChromeClient = chromeClient
webViewClient = client
}.also {
if(it.parent != null) (it.parent as ViewGroup).removeView(it) // this line is very crucial
existingWebView = it
}
},
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With