Per utilizzare un WebView in Jetpack Compose, devi inserirlo in un AndroidView.
Questa guida spiega i casi d'uso comuni e come supportarli in Compose.
Aggrega una WebView con AndroidView
Per utilizzare un WebView in Compose, inseriscilo 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 funziona per mostrare un semplice URL all'interno della tua app. Tuttavia, WebView gestisce cicli di vita di stato complessi che sono separati dal ciclo di vita della visualizzazione Android e dal ciclo di vita di Compose. L'integrazione di Compose può introdurre scenari WebView complessi che generano 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 host Activity e non è consigliabile che la sua istanza sopravviva al ciclo di vita di Activity.
Pertanto, il modo standard per rendere persistente lo stato di un WebView è consentire l'eliminazione e la ricreazione delle istanze WebView insieme a Activity. Puoi rendere persistente 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() ) }
Gestisci la navigazione a ritroso
Quando un WebView ha una cronologia di navigazione, il gesto Indietro di sistema deve navigare
all'indietro all'interno del WebView anziché uscire dalla schermata.
Utilizza l'API Compose BackHandler per intercettare l'evento Indietro di 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 simile a quello del browser.
Scorrimento nidificato
Lo scorrimento nidificato non è facilmente supportato quando si utilizza WebView in Compose. Quando
posizioni 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'annidamento con LazyColumn al momento non funziona correttamente.
Per monitorare l'avanzamento del supporto ufficiale dello scorrimento nidificato per WebView, consulta questo problema.
Layout edge-to-edge e inset delle finestre
Quando utilizzi layout edge-to-edge, i contenuti di WebView potrebbero essere visualizzati sotto le barre di sistema, come la barra di stato. Puoi utilizzare il modificatore windowInsetsPadding per
inserire 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, consulta Informazioni sugli inset delle finestre in WebView.
Sincronizza il tema dell'app con i contenuti WebView
Quando l'applicazione passa dalla modalità Luce alla modalità Buio e viceversa, i contenuti di WebView possono aggiornarsi automaticamente senza ricaricare la pagina, se gestiti correttamente.
Se sei il proprietario dei contenuti della pagina web, per sincronizzare i colori con il tema dell'app,
gestisci la query supporti prefers-color-scheme per assicurarti che la pagina web si adatti
al tema selezionato.
Per consentire a elementi nativi come menu a discesa e popup di rilevare e abbinare il tema della tua 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 sei il proprietario dei contenuti web, l'oscuramento algoritmico può aiutare a forzare un tema scuro. I siti web moderni che hanno già la modalità Buio ignorano questo algoritmo e utilizzano i propri stili integrati.
Gestisci 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.
Gestisci le autorizzazioni di accesso alla fotocamera e al 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 Android:
Manifest.permission.CAMERAManifest.permission.RECORD_AUDIO
Innanzitutto, definisci un gestore delle autorizzazioni per WebView che tenga traccia delle 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 } }
Poi, crea un componibile 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. In questo modo viene avviato il programma di avvio 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 di incorporare WebView, Android offre altre opzioni per visualizzare i contenuti web, come le schede personalizzate di Chrome. Consulta Utilizza i contenuti web
all'interno della tua app per Android per capire come scegliere l'approccio corretto
per i tuoi casi d'uso (ad esempio navigazione o autenticazione).
Risorse aggiuntive
- Gestisci e diagnostica la memoria di WebView
- Gestisci lo stato di WebView in modo efficiente
- Esegui il debug delle app web