WebView'ı Oluşturma'da sarmalama

Jetpack Compose'da WebView kullanmak için bunu AndroidView ile sarmalamanız gerekir. Bu kılavuzda, yaygın kullanım alanları ve bunları Compose'da nasıl destekleyeceğiniz açıklanmaktadır.

WebView'ı AndroidView ile sarmalama

Yazma işleminde WebView kullanmak için AndroidView ile sarmalayın:

@Composable
fun SimpleWebView(
    initialUrl: String,
    modifier: Modifier = Modifier
) {
    AndroidView(
        modifier = modifier.fillMaxSize(),
        factory = { context ->
            WebView(context).apply {
                webViewClient = WebViewClient()
                settings.javaScriptEnabled = true
                loadUrl(initialUrl)			
            }
        }
    )
}

Bu yöntem, uygulamanızda basit bir URL göstermek için uygundur. Ancak WebView, Android View yaşam döngüsünden ve Compose yaşam döngüsünden ayrı olan karmaşık durum yaşam döngüleriyle ilgilenir. Oluştur'u entegre etmek, zor hatalara yol açan karmaşık WebView senaryolara neden olabilir. Aşağıdaki bölümlerde, bu özellikleri desteklemek için özel işlem gerektirebilecek kullanım alanları açıklanmaktadır.

WebView durumunu kalıcı hale getirme

Compose'da yapılandırma değişikliklerini ve gezinmeyi yönetmek zordur. Bunun nedeni, WebView'nın ana makinesine Activity bağlı eski bir View olması ve örneğinin Activity yaşam döngüsünden daha uzun süre yaşaması önerilmemesidir.

Bu nedenle, WebView durumunu kalıcı hale getirmenin standart yolu, WebView örneklerinin Activity ile birlikte yok edilmesine ve yeniden oluşturulmasına izin vermektir. Bundle kullanarak dahili gezinme geçmişini ve kaydırma durumunu manuel olarak kalıcı hale getirebilirsiniz.

@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()
    )
}

Geri gezinmeyi yönetme

WebView'nın gezinme geçmişi olduğunda sistemin geri gitme hareketi, ekrandan çıkmak yerine WebView içinde geriye doğru gezinmelidir.

Sistemin geri gitme etkinliğini yakalamak ve WebView goBack() işlevini çağırmak için Compose BackHandler API'sini kullanın:

// ...
@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
                }
            )
        }
    }
}

Bu uygulama, tarayıcı tarzı gezinme davranışı sağlar.

İç içe kaydırma

Compose'da WebView kullanılırken iç içe kaydırma kolayca desteklenmez. WebView, LazyColumn gibi kaydırılabilir bir Compose kapsayıcısının içine yerleştirildiğinde WebView tüm kaydırma hareketlerini kullanabilir. WebView kendi dahili oluşturma motorunu kullandığından, LazyColumn ile iç içe yerleştirme şu anda düzgün çalışmamaktadır.

WebView için resmi iç içe kaydırma desteğinin ilerleme durumunu izlemek üzere bu soruna göz atın.

Uçtan uca düzenler ve pencere yerleştirmeleri

Uçtan uca düzenler kullanılırken WebView içerik, durum çubuğu gibi sistem çubuklarının altında görünebilir. windowInsetsPadding değiştiricisini kullanarak WebView öğesinin tamamını güvenli alana taşıyabilirsiniz:

@Composable
fun EdgeToEdgeDemo(url: String) {
    AndroidView(
        modifier = Modifier
            .fillMaxSize()
            .windowInsetsPadding(WindowInsets.systemBars),
        factory = { context ->
            WebView(context).apply {
                loadUrl(url)
            }
        }
    )
}

İç boşluklar hakkında daha fazla bilgi için WebView'da pencere iç boşluklarını anlama başlıklı makaleyi inceleyin.

Uygulama temasını WebView içeriğiyle senkronize etme

Uygulama açık ve koyu tema arasında geçiş yaptığında, WebView içerik doğru şekilde işlenirse sayfa yeniden yüklenmeden otomatik olarak güncellenebilir.

Web sayfası içeriğinin sahibiyseniz renkleri uygulamanın temasıyla senkronize etmek için prefers-color-scheme medya sorgusunu işleyerek web sayfanızın seçilen temaya uyum sağladığından emin olun.

Açılır listeler ve pop-up'lar gibi yerel öğelerin uygulama temanızı algılayıp eşleştirmesini sağlamak için DayNight stil teması uygulayın.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)
            }
        }
    )
} 

Web sayfasında koyu tema yoksa veya web içeriğinin sahibi siz değilseniz algoritmik karartma, koyu temayı zorunlu kılmaya yardımcı olabilir. Koyu modu olan modern web siteleri bu algoritmayı yoksayar ve bunun yerine kendi yerleşik stillerini kullanır.

Oluşturma'da web izinlerini yönetme

Bir web sayfası donanım veya veri erişimi (ör. kamera, mikrofon ya da konum) istediğinde WebView içinde belirli geri çağırma işlemleri tetiklenir WebChromeClient. Bu geri çağırmaları işlemeniz ve ilgili Android çalışma zamanı izinlerinin verildiğinden emin olmanız gerekir.

Kamera ve mikrofon izinlerini yönetme

Bir web sayfası kamera veya mikrofon erişimi istediğinde (örneğin, WebRTC veya video kaydı için) WebView çağrısı yapılır WebChromeClient.onPermissionRequest.

Ancak grant() işlevini çağırmadan önce aşağıdaki Android çalışma zamanı izinlerini istemeniz gerekir:

  • Manifest.permission.CAMERA
  • Manifest.permission.RECORD_AUDIO

İlk olarak, WebView için WebView tarafından istenen PermissionRequest izinlerini takip eden bir izin işleyici tanımlayın:

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
    }
}

Ardından, WebViewPermissionHandler değerini hatırlayan bir composable oluşturun. İzin istemek için rememberLauncherForActivityResult kullanın:

@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 }
    }
}

onPermissionRequest geri çağırma işlevinden gelen izni işleyin. Bu işlem, izin başlatıcıyı açar:

@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()
    )
}

Yerleştirilmiş WebView'a alternatif

WebView yerleştirmekten kaçınmayı tercih ederseniz Android, web içeriğini görüntülemek için Chrome Özel Sekmeleri gibi başka seçenekler sunar. Kullanım alanlarınız (ör. göz atma veya kimlik doğrulama) için doğru yaklaşımı nasıl seçeceğinizi anlamak üzere Android uygulamanızda web içeriğini kullanma başlıklı makaleyi inceleyin.