Per utilizzare un WebView in Jetpack Compose, devi racchiuderlo in un AndroidView.
Questa guida spiega i casi d'uso comuni e come supportarli in Compose.
Eseguire il wrapping di una WebView con AndroidView
Per utilizzare un WebView in Compose, racchiudilo in un AndroidView:
@Composable fun SimpleWebView( initialUrl: String, modifier: Modifier = Modifier ) { AndroidView( modifier = modifier.fillMaxSize(), factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = true loadUrl(initialUrl) } } ) }
Questo metodo funziona per mostrare un semplice URL all'interno dell'app. Tuttavia, WebView gestisce
cicli di vita complessi degli stati separati dal ciclo di vita di Android View
e dal ciclo di vita di Compose. L'integrazione di Compose può introdurre scenari complessi
WebView che comportano bug difficili. Le sezioni seguenti
descrivono i casi d'uso che potrebbero richiedere una gestione specifica per supportare queste funzionalità.
Mantieni lo stato di WebView
La gestione delle modifiche alla configurazione e della navigazione in Compose è difficile perché
WebView è un View legacy associato al relativo Activity host ed è
sconsigliato che la sua istanza sopravviva al ciclo di vita di Activity.
Pertanto, il modo standard per rendere persistente lo stato di un WebView consiste nel consentire
la distruzione e la ricreazione delle istanze WebView insieme a Activity. Puoi
conservare manualmente la cronologia di navigazione interna e lo stato di scorrimento utilizzando un
Bundle.
@Composable fun PersistentWebView(url: String) { val webViewStateBundle = rememberSaveable { Bundle() } AndroidView( factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = true // Restore the state and history if (webViewStateBundle.containsKey("WEBVIEW_STATE")) { restoreState(webViewStateBundle.getBundle("WEBVIEW_STATE")!!) } else { loadUrl(url) } } }, onRelease = { releasedWebView -> // Save navigation history before the instance is destroyed val bundle = Bundle() releasedWebView.saveState(bundle) webViewStateBundle.putBundle("WEBVIEW_STATE", bundle) }, modifier = Modifier.fillMaxSize() ) }
Gestire la navigazione a ritroso
Quando un WebView ha una cronologia di navigazione, il gesto Indietro del sistema deve spostarsi
all'interno del WebView anziché uscire dalla schermata.
Utilizza l'API Compose BackHandler per intercettare l'evento di ritorno del sistema e chiama la funzione WebView goBack():
// ... @Composable fun BackNavigationDemoScreen(onBack: () -> Unit) { // Hold a reference to the WebView to check its history state var webViewReference by remember { mutableStateOf<WebView?>(null) } // Intercept the system back press if the WebView has history BackHandler(enabled = true) { val webView = webViewReference if (webView != null && webView.canGoBack()) { webView.goBack() // Go back in history } else { onBack() // Exit screen } } Scaffold( topBar = { TopAppBar( title = { Text("Back Navigation Demo") }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } } ) } ) { padding -> Column(modifier = Modifier.fillMaxSize().padding(padding)) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true // Keeps link navigations internal to the WebView instead of opening Chrome webViewClient = WebViewClient() loadUrl("https://developer.android.com") webViewReference = this } }, onRelease = { webViewReference = null } ) } } }
Questa implementazione fornisce un comportamento di navigazione in stile browser.
Scorrimento nidificato
Lo scorrimento nidificato non è supportato facilmente quando utilizzi WebView in Scrittura. Quando
inserisci un WebView all'interno di un contenitore Compose scorrevole, ad esempio un
LazyColumn, il WebView potrebbe consumare tutti i gesti di scorrimento.
Poiché WebView si basa sul proprio motore di rendering interno, l'incorporamento con
LazyColumn non funziona correttamente al momento.
Per monitorare l'avanzamento del supporto ufficiale dello scorrimento nidificato per WebView, consulta
questo problema.
Layout edge-to-edge e rientri delle finestre
Quando utilizzi layout edge-to-edge, i contenuti WebView potrebbero essere visualizzati sotto le barre di sistema, ad esempio la barra di stato. Puoi utilizzare il modificatore windowInsetsPadding per
spostare l'intero WebView nell'area sicura:
@Composable fun EdgeToEdgeDemo(url: String) { AndroidView( modifier = Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), factory = { context -> WebView(context).apply { loadUrl(url) } } ) }
Per saperne di più sugli inset, vedi Informazioni sugli inset delle finestre in WebView.
Sincronizzare il tema dell'app con i contenuti di WebView
Quando l'applicazione passa dalla modalità Chiaro alla modalità Buio, i contenuti WebView possono
aggiornarsi automaticamente senza ricaricare la pagina se gestiti correttamente.
Se possiedi i contenuti della pagina web, per sincronizzare i colori con il tema dell'app,
gestisci la media query prefers-color-scheme per assicurarti che la pagina web si adatti
al tema selezionato.
Per consentire agli elementi nativi come i menu a discesa e i popup di rilevare e abbinare il tema dell'app, applica un tema di stile DayNight al tuo Activity.
<resources> <!-- ... <!-- Use a DayNight theme in your manifest to handle both modes automatically --> <style name="Theme.Webviewdemo.DayNight" parent="Theme.AppCompat.DayNight.NoActionBar" /> </resources>
@Composable fun ThemeSyncDemo(onBack: () -> Unit) { val context = LocalContext.current AndroidView( modifier = Modifier.fillMaxSize(), factory = { _ -> WebView(context).apply { settings.javaScriptEnabled = true webViewClient = WebViewClient() val html = """ <html> <head> // ... @media (prefers-color-scheme: dark) { body { background-color: #212121; color: #ffffff; } select { border-color: #BB86FC; background: #212121; color: #ffffff; } } </style> </head> // ... </html> """.trimIndent() loadDataWithBaseURL(null, html, "text/html", "UTF-8", null) } } ) }
Se la pagina web non ha un tema scuro o se non possiedi i contenuti web, l'oscuramento algoritmico� può aiutarti a forzare un tema scuro. I siti web moderni che hanno già la modalità Buio ignorano questo algoritmo e utilizzano i propri stili integrati.
Gestire le autorizzazioni web in Compose
Quando una pagina web richiede l'accesso all'hardware o ai dati (ad esempio fotocamera,
microfono o posizione), WebView attiva callback specifici nel suo
WebChromeClient. Devi gestire questi callback e assicurarti che vengano concesse le autorizzazioni di runtime Android corrispondenti.
Gestire le autorizzazioni per fotocamera e microfono
Quando una pagina web richiede l'accesso alla fotocamera o al microfono (ad esempio WebRTC o
registrazione video), WebView chiama WebChromeClient.onPermissionRequest.
Tuttavia, prima di chiamare grant(), devi richiedere le seguenti autorizzazioni di runtime di Android:
Manifest.permission.CAMERAManifest.permission.RECORD_AUDIO
Innanzitutto, definisci un gestore delle autorizzazioni per WebView che tiene traccia di
PermissionRequest richieste da WebView:
class WebViewPermissionHandler( private val launcher: ManagedActivityResultLauncher<Array<String>, Map<String, Boolean>> ) { var pendingRequest by mutableStateOf<PermissionRequest?>(null) private set fun handleRequest(request: PermissionRequest) { val isTrustedOrigin = request.origin.host == "www.trusted-domain.com" || request.origin.host == "app.local" // Always verify the origin before granting request if (!isTrustedOrigin) { Log.w("WebViewPermission", "Blocked and denied permission request from untrusted origin: ${request.origin.host}") request.deny() return } val androidPermissions = mutableListOf<String>() request.resources.forEach { resource -> when (resource) { PermissionRequest.RESOURCE_VIDEO_CAPTURE -> androidPermissions.add(Manifest.permission.CAMERA) PermissionRequest.RESOURCE_AUDIO_CAPTURE -> androidPermissions.add(Manifest.permission.RECORD_AUDIO) } } // Save the request and launch the Android system dialog pendingRequest = request launcher.launch(androidPermissions.toTypedArray()) } fun onResult(results: Map<String, Boolean>) { val allGranted = results.values.all { it } Log.d("WebViewPermission", "Kotlin: All permissions granted? $allGranted") if (allGranted) { pendingRequest?.grant(arrayOf("/* list of permissions */")) } else { pendingRequest?.deny() } pendingRequest = null } }
Successivamente, crea un composable che memorizzi WebViewPermissionHandler. Utilizza
rememberLauncherForActivityResult per richiedere le autorizzazioni:
@Composable fun rememberWebViewPermissionHandler(): WebViewPermissionHandler { val handlerState = remember { mutableStateOf<WebViewPermissionHandler?>(null) } val launcher = rememberLauncherForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> handlerState.value?.onResult(results) } return remember { WebViewPermissionHandler(launcher).also { handlerState.value = it } } }
Gestisci l'autorizzazione dal callback onPermissionRequest. Viene avviato il
launcher delle autorizzazioni:
@Composable fun WebViewPermissionScreen() { val permissionHandler = rememberWebViewPermissionHandler() AndroidView( factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { // Simply delegate to the handler permissionHandler.handleRequest(request) } } // load a web page that needs permissions } }, modifier = Modifier.fillMaxSize() ) }
Alternativa a una WebView incorporata
Se preferisci evitare l'incorporamento di WebView, Android offre altre opzioni per
visualizzare contenuti web, come le schede personalizzate di Chrome. Consulta Utilizzare i contenuti web
all'interno dell'app per Android per capire come scegliere l'approccio corretto
per i tuoi casi d'uso (come la navigazione o l'autenticazione).