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 एपीआई का इस्तेमाल करें. साथ ही, 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 कंटेनर में WebView को डालते समय, जैसे कि LazyColumn, 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 कॉन्टेंट अपने-आप अपडेट हो सकता है. इसके लिए, पेज को फिर से लोड करने की ज़रूरत नहीं होती. हालांकि, इसके लिए ज़रूरी है कि इसे सही तरीके से हैंडल किया गया हो.

अगर आपके पास वेब पेज के कॉन्टेंट का मालिकाना हक है, तो ऐप्लिकेशन की थीम के साथ रंगों को सिंक करने के लिए, मीडिया क्वेरी 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 ऐप्लिकेशन में वेब कॉन्टेंट का इस्तेमाल करना लेख पढ़ें. इससे आपको यह समझने में मदद मिलेगी कि अपने इस्तेमाल के उदाहरणों (जैसे, ब्राउज़िंग या पुष्टि करना) के लिए सही तरीका कैसे चुना जाए.

अन्य संसाधन