Pour utiliser un WebView dans Jetpack Compose, vous devez l'encapsuler dans un AndroidView.
Ce guide explique les cas d'utilisation courants et comment les prendre en charge dans Compose.
Encapsuler une WebView avec AndroidView
Pour utiliser un WebView dans Compose, encapsulez-le avec 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) } } ) }
Cette méthode fonctionne pour afficher une URL simple dans votre application. Toutefois, WebView gère des cycles de vie d'état complexes qui sont distincts du cycle de vie d'Android View et de Compose. L'intégration de Compose peut introduire des scénarios WebView complexes qui entraînent des bugs difficiles à résoudre. Les sections suivantes décrivent les cas d'utilisation qui peuvent nécessiter un traitement spécifique pour prendre en charge ces fonctionnalités.
Persister l'état WebView
La gestion des changements de configuration et de la navigation dans Compose est difficile, car WebView est un View hérité lié à son Activity hôte. Il est déconseillé que son instance survive au cycle de vie de Activity.
Par conséquent, la méthode standard pour rendre l'état d'un WebView persistant consiste à autoriser la destruction et la recréation des instances WebView avec le Activity. Vous pouvez conserver manuellement son historique de navigation interne et son état de défilement à l'aide d'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() ) }
Gérer la navigation vers l'arrière
Lorsqu'un WebView possède un historique de navigation, le geste de retour système doit permettre de revenir en arrière dans le WebView plutôt que de quitter l'écran.
Utilisez l'API Compose BackHandler pour intercepter l'événement de retour système et appelez la fonction 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 } ) } } }
Cette implémentation fournit un comportement de navigation de type navigateur.
Défilement imbriqué
Le défilement imbriqué n'est pas facilement pris en charge lorsque vous utilisez WebView dans Compose. Lorsque vous placez un WebView dans un conteneur Compose pouvant être défilé, tel qu'un LazyColumn, le WebView peut consommer tous les gestes de défilement.
Étant donné que WebView repose sur son propre moteur de rendu interne, l'imbriquer avec LazyColumn ne fonctionne pas correctement pour le moment.
Pour suivre l'état de la prise en charge officielle du défilement imbriqué pour WebView, consultez ce problème.
Mises en page bord à bord et encarts de fenêtre
Lorsque vous utilisez des mises en page bord à bord, le contenu WebView peut apparaître sous les barres système, comme la barre d'état. Vous pouvez utiliser le modificateur windowInsetsPadding pour insérer l'intégralité de WebView dans la zone de sécurité :
@Composable fun EdgeToEdgeDemo(url: String) { AndroidView( modifier = Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), factory = { context -> WebView(context).apply { loadUrl(url) } } ) }
Pour en savoir plus sur les encarts, consultez Comprendre les encarts de fenêtre dans WebView.
Synchroniser le thème de l'application avec le contenu WebView
Lorsque l'application bascule entre le mode clair et le mode sombre, le contenu WebView peut se mettre à jour automatiquement sans recharger la page s'il est géré correctement.
Si vous êtes propriétaire du contenu de la page Web, gérez la requête média prefers-color-scheme pour vous assurer que votre page Web s'adapte au thème sélectionné et synchroniser les couleurs avec le thème de l'application.
Pour permettre aux éléments natifs tels que les menus déroulants et les pop-ups de détecter et de correspondre au thème de votre application, appliquez un thème de style DayNight à votre 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) } } ) }
Si la page Web n'a pas de thème sombre ou si vous ne possédez pas le contenu Web, l'assombrissement algorithmique peut vous aider à forcer un thème sombre. Les sites Web modernes qui disposent déjà d'un mode sombre ignorent cet algorithme et utilisent leurs propres styles intégrés.
Gérer les autorisations Web dans Compose
Lorsqu'une page Web demande l'accès à du matériel ou à des données (par exemple, l'appareil photo, le micro ou la position), WebView déclenche des rappels spécifiques dans son WebChromeClient. Vous devez gérer ces rappels et vous assurer que les autorisations d'exécution Android correspondantes sont accordées.
Gérer les autorisations d'accès à la caméra et au micro
Lorsqu'une page Web demande l'accès à la caméra ou au micro (par exemple, WebRTC ou l'enregistrement vidéo), WebView appelle WebChromeClient.onPermissionRequest.
Toutefois, avant d'appeler grant(), vous devez demander les autorisations d'exécution Android suivantes :
Manifest.permission.CAMERAManifest.permission.RECORD_AUDIO
Commencez par définir un gestionnaire d'autorisations pour WebView qui suit les PermissionRequest demandés à partir de 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 } }
Ensuite, créez un composable qui mémorise le WebViewPermissionHandler. Utilisez rememberLauncherForActivityResult pour demander des autorisations :
@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 } } }
Gérez l'autorisation à partir du rappel onPermissionRequest. Le lanceur d'autorisation s'ouvre :
@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() ) }
Alternative à une WebView intégrée
Si vous préférez éviter d'intégrer WebView, Android propose d'autres options pour afficher du contenu Web, comme les onglets personnalisés Chrome. Consultez Utiliser du contenu Web dans votre application Android pour savoir comment choisir l'approche adaptée à vos cas d'utilisation (comme la navigation ou l'authentification).