Compose में WebView को रैप करना

Jetpack Compose में WebView का इस्तेमाल करने के लिए, आपको इसे AndroidView में रैप करना होगा. इस गाइड में, इस्तेमाल के सामान्य उदाहरणों के बारे में बताया गया है. साथ ही, Compose में इनका इस्तेमाल करने का तरीका बताया गया है.

AndroidView की मदद से WebView को रैप करना

Compose में WebView का इस्तेमाल करने के लिए, इसे 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)			
            }
        }
    )
}

यह आपके ऐप्लिकेशन में एक सामान्य यूआरएल दिखाने के लिए काम करता है. हालांकि, WebView जटिल स्टेट लाइफ़साइकल को मैनेज करता है. ये लाइफ़साइकल, Android View लाइफ़साइकल और Compose लाइफ़साइकल से अलग होते हैं. Compose को इंटिग्रेट करने से, WebView से जुड़ी मुश्किल स्थितियां पैदा हो सकती हैं. इससे बग ठीक करने में मुश्किल आ सकती है. यहां दिए गए सेक्शन में, इस्तेमाल के ऐसे उदाहरणों के बारे में बताया गया है जिनमें उन सुविधाओं को सपोर्ट करने के लिए, खास तरीके से काम करना पड़ सकता है.

इस कुकी का इस्तेमाल, वेबव्यू की स्थिति को बनाए रखने के लिए किया जाता है

Compose में कॉन्फ़िगरेशन में हुए बदलावों और नेविगेशन को मैनेज करना मुश्किल है, क्योंकि WebView एक लेगसी View है, जो अपने होस्ट Activity से जुड़ा होता है. साथ ही, यह सुझाव नहीं दिया जाता कि इसका इंस्टेंस, Activity के लाइफ़साइकल से ज़्यादा समय तक चले.

इसलिए, WebView के स्टेटस को बनाए रखने का स्टैंडर्ड तरीका यह है कि WebView इंस्टेंस को Activity के साथ डिस्ट्रॉय और फिर से बनाया जाए. 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()
    )
}

पीछे जाने की प्रोसेस को मैनेज करना

अगर WebView में नेविगेशन इतिहास है, तो सिस्टम के बैक जेस्चर से स्क्रीन से बाहर निकलने के बजाय, WebView में पीछे की ओर नेविगेट करना चाहिए.

सिस्टम के बैक इवेंट को इंटरसेप्ट करने के लिए, Compose BackHandler API का इस्तेमाल करें. साथ ही, 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
                }
            )
        }
    }
}

इस तरीके से, ब्राउज़र की तरह नेविगेट करने की सुविधा मिलती है.

नेस्ट की गई स्क्रोलिंग

Compose में WebView का इस्तेमाल करते समय, नेस्टेड स्क्रोलिंग की सुविधा आसानी से काम नहीं करती. जब स्क्रोल किए जा सकने वाले Compose कंटेनर, जैसे कि LazyColumn में WebView को रखा जाता है, तो WebView स्क्रोल करने के सभी जेस्चर इस्तेमाल कर सकता है. WebView अपने इंटरनल रेंडरिंग इंजन पर निर्भर करता है. इसलिए, फ़िलहाल इसे LazyColumn के साथ नेस्ट करने पर, यह ठीक से काम नहीं करता.

WebView के लिए, नेस्टेड स्क्रोलिंग की आधिकारिक सुविधा की प्रोग्रेस को ट्रैक करने के लिए, यह समस्या देखें.

एज-टू-एज लेआउट और विंडो इनसेट

किनारे से किनारे तक फैले लेआउट का इस्तेमाल करते समय, WebView कॉन्टेंट, स्टेटस बार जैसे सिस्टम बार के नीचे दिख सकता है. windowInsetsPadding मॉडिफ़ायर का इस्तेमाल करके, पूरे WebView को सुरक्षित जगह पर ले जाया जा सकता है:

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

इनसेट के बारे में ज़्यादा जानने के लिए, WebView में विंडो इनसेट को समझना लेख पढ़ें.

ऐप्लिकेशन की थीम को WebView कॉन्टेंट के साथ सिंक करना

जब ऐप्लिकेशन, लाइट और डार्क मोड के बीच स्विच करता है, तो WebView कॉन्टेंट को सही तरीके से मैनेज करने पर, पेज को रीलोड किए बिना अपने-आप अपडेट किया जा सकता है.

अगर आपके पास वेब पेज के कॉन्टेंट का मालिकाना हक है, तो ऐप्लिकेशन की थीम के साथ रंगों को सिंक करने के लिए, मीडिया क्वेरी prefers-color-scheme को मैनेज करें. इससे यह पक्का किया जा सकेगा कि आपका वेब पेज, चुनी गई थीम के हिसाब से दिखे.

ड्रॉपडाउन और पॉप-अप जैसे नेटिव एलिमेंट को, आपके ऐप्लिकेशन की थीम का पता लगाने और उससे मैच करने की अनुमति देने के लिए, अपने Activity. पर DayNight स्टाइल थीम लागू करें

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

अगर वेब पेज पर गहरे रंग वाली थीम नहीं है या आपके पास वेब कॉन्टेंट का मालिकाना हक नहीं है, तो एल्गोरिदम के हिसाब से गहरे रंग की थीम लागू करने की सुविधा का इस्तेमाल करके, गहरे रंग की थीम लागू की जा सकती है. गहरे रंग वाले मोड की सुविधा देने वाली आधुनिक वेबसाइटें, इस एल्गोरिदम को अनदेखा करती हैं. इसके बजाय, वे पहले से मौजूद अपनी स्टाइल का इस्तेमाल करती हैं.

Compose में वेब की अनुमतियां मैनेज करना

जब कोई वेब पेज हार्डवेयर या डेटा ऐक्सेस करने का अनुरोध करता है (जैसे, कैमरा, माइक्रोफ़ोन या जगह की जानकारी), तो WebView, WebChromeClient में कुछ खास कॉलबैक ट्रिगर करता है. आपको इन कॉलबैक को मैनेज करना होगा. साथ ही, यह पक्का करना होगा कि Android रनटाइम की ज़रूरी अनुमतियां दी गई हों.

कैमरा और माइक्रोफ़ोन ऐक्सेस करने की अनुमतियों को मैनेज करना

जब कोई वेब पेज, कैमरा या माइक्रोफ़ोन ऐक्सेस करने का अनुरोध करता है (उदाहरण के लिए, WebRTC या वीडियो रिकॉर्डिंग), तब WebView कॉल करता है WebChromeClient.onPermissionRequest.

हालांकि, grant() को कॉल करने से पहले, आपको Android की ये रनटाइम अनुमतियाँ माँगनी होंगी:

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

सबसे पहले, WebView के लिए अनुमति हैंडलर तय करें. यह WebView से PermissionRequest के लिए किए गए अनुरोध को ट्रैक करता है:

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

इसके बाद, एक ऐसा कंपोज़ेबल बनाएं जो WebViewPermissionHandler को याद रखता हो. अनुमतियों का अनुरोध करने के लिए, rememberLauncherForActivityResult का इस्तेमाल करें:

@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 कॉलबैक फ़ंक्शन से अनुमति को मैनेज करें. इससे अनुमति लॉन्चर लॉन्च होता है:

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

एम्बेड किए गए वेबव्यू का विकल्प

अगर आपको WebView को एम्बेड नहीं करना है, तो Android, वेब कॉन्टेंट दिखाने के लिए अन्य विकल्प उपलब्ध कराता है. जैसे, Chrome के कस्टम टैब. अपने इस्तेमाल के उदाहरणों (जैसे, ब्राउज़ करना या पुष्टि करना) के लिए सही तरीका चुनने का तरीका जानने के लिए, अपने Android ऐप्लिकेशन में वेब कॉन्टेंट का इस्तेमाल करना लेख पढ़ें.