Jetpack Compose-এ WebView ব্যবহার করতে হলে, সেটিকে অবশ্যই একটি AndroidView মধ্যে রাখতে হবে। এই নির্দেশিকায় এর সাধারণ ব্যবহার এবং Compose-এ কীভাবে সেগুলোকে সমর্থন করা যায়, তা ব্যাখ্যা করা হয়েছে।
একটি WebView-কে AndroidView দিয়ে মুড়ে দিন।
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 এমন জটিল স্টেট লাইফসাইকেল পরিচালনা করে যা অ্যান্ড্রয়েড ভিউ লাইফসাইকেল এবং কম্পোজ লাইফসাইকেল থেকে আলাদা। কম্পোজ ইন্টিগ্রেট করলে WebView এমন জটিল পরিস্থিতি তৈরি হতে পারে, যার ফলে কঠিন বাগ দেখা দেয়। নিম্নলিখিত বিভাগগুলিতে এমন ব্যবহারের ক্ষেত্রগুলি বর্ণনা করা হয়েছে, যেগুলিকে সমর্থন করার জন্য বিশেষ ব্যবস্থাপনার প্রয়োজন হতে পারে।
ওয়েবভিউ অবস্থা স্থায়ী করুন
Compose-এ কনফিগারেশন পরিবর্তন এবং নেভিগেশন পরিচালনা করা বেশ কঠিন, কারণ WebView হলো একটি লিগ্যাসি View যা তার হোস্ট Activity সাথে আবদ্ধ থাকে, এবং এর ইনস্ট্যান্সটিকে Activity জীবনচক্রের পরেও সচল রাখা বাঞ্ছনীয় নয় ।
সুতরাং, একটি WebView এর স্টেট সংরক্ষণ করার আদর্শ উপায় হলো Activity এর সাথে WebView ইনস্ট্যান্সগুলোকেও ডেস্ট্রয় এবং রি-ক্রিয়েট করার সুযোগ দেওয়া। আপনি একটি 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 ব্যবহার করার সময় নেস্টেড স্ক্রলিং সহজে সাপোর্ট করে না। যখন একটি WebView LazyColumn এর মতো কোনো স্ক্রলযোগ্য Compose কন্টেইনারের ভেতরে রাখা হয়, তখন WebView সমস্ত স্ক্রল জেসচার ব্যবহার করে ফেলতে পারে। যেহেতু WebView তার নিজস্ব অভ্যন্তরীণ রেন্ডারিং ইঞ্জিনের উপর নির্ভর করে, তাই এটিকে LazyColumn সাথে নেস্ট করা বর্তমানে সঠিকভাবে কাজ করে না।
WebView এর জন্য আনুষ্ঠানিক নেস্টেড স্ক্রলিং সমর্থনের অগ্রগতি জানতে এই ইস্যুটি দেখুন।
প্রান্ত থেকে প্রান্ত পর্যন্ত লেআউট এবং উইন্ডো ইনসেট
এজ-টু-এজ লেআউট ব্যবহার করার সময়, WebView কন্টেন্ট স্ট্যাটাস বারের মতো সিস্টেম বারের নিচে প্রদর্শিত হতে পারে। আপনি সম্পূর্ণ WebView টিকে নিরাপদ এলাকায় ঠেলে দিতে windowInsetsPadding মডিফায়ারটি ব্যবহার করতে পারেন:
@Composable fun EdgeToEdgeDemo(url: String) { AndroidView( modifier = Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), factory = { context -> WebView(context).apply { loadUrl(url) } } ) }
ইনসেট সম্পর্কে আরও তথ্যের জন্য, WebView-তে উইন্ডো ইনসেট বুঝুন দেখুন।
অ্যাপ থিমকে ওয়েবভিউ কন্টেন্টের সাথে সিঙ্ক্রোনাইজ করুন
অ্যাপ্লিকেশনটি লাইট এবং ডার্ক মোডের মধ্যে পরিবর্তন করার সময়, যদি সঠিকভাবে পরিচালনা করা হয়, তাহলে পেজ রিলোড ছাড়াই WebView কন্টেন্ট স্বয়ংক্রিয়ভাবে আপডেট হতে পারে।
আপনি যদি ওয়েব পেজের কন্টেন্টের মালিক হন, তাহলে অ্যাপের থিমের সাথে রং সমন্বয় করতে এবং আপনার ওয়েব পেজটি যেন নির্বাচিত থিমের সাথে খাপ খায় তা নিশ্চিত করতে prefers-color-scheme মিডিয়া কোয়েরিটি ব্যবহার করুন।
ড্রপডাউন এবং পপআপের মতো নেটিভ এলিমেন্টগুলোকে আপনার অ্যাপ থিম শনাক্ত ও তার সাথে মেলানোর জন্য, আপনার অ্যাক্টিভিটিতে একটি DayNight স্টাইলের থিম প্রয়োগ করুন 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) } } ) }
যদি ওয়েব পেজটিতে ডার্ক থিম না থাকে, অথবা আপনি ওয়েব কন্টেন্টটির মালিক না হন, তাহলে অ্যালগরিদমিক ডার্কেনিং একটি ডার্ক থিম প্রয়োগ করতে সাহায্য করতে পারে। যেসব আধুনিক ওয়েবসাইটে আগে থেকেই ডার্ক মোড রয়েছে, তারা এই অ্যালগরিদমকে উপেক্ষা করে এবং এর পরিবর্তে তাদের নিজস্ব বিল্ট-ইন স্টাইল ব্যবহার করে।
Compose-এ ওয়েব অনুমতিগুলি পরিচালনা করুন
যখন কোনো ওয়েব পেজ হার্ডওয়্যার বা ডেটা অ্যাক্সেসের (যেমন, ক্যামেরা, মাইক্রোফোন বা অবস্থান) অনুরোধ করে, তখন WebView তার WebChromeClient এ নির্দিষ্ট কলব্যাক ট্রিগার করে। আপনাকে অবশ্যই এই কলব্যাকগুলি হ্যান্ডেল করতে হবে এবং সংশ্লিষ্ট অ্যান্ড্রয়েড রানটাইম পারমিশনগুলি মঞ্জুর করা নিশ্চিত করতে হবে।
ক্যামেরা এবং মাইক্রোফোনের অনুমতি পরিচালনা করুন
যখন কোনো ওয়েব পেজ ক্যামেরা বা মাইক্রোফোন অ্যাক্সেসের অনুরোধ করে (যেমন, WebRTC বা ভিডিও রেকর্ডিং), তখন WebView WebChromeClient.onPermissionRequest কল করে।
তবে, grant() কল করার আগে, আপনাকে অবশ্যই নিম্নলিখিত অ্যান্ড্রয়েড রানটাইম অনুমতিগুলির জন্য অনুরোধ করতে হবে:
-
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 এমবেড করা এড়াতে চান, তাহলে অ্যান্ড্রয়েড ওয়েব কন্টেন্ট প্রদর্শনের জন্য অন্যান্য বিকল্প প্রদান করে, যেমন ক্রোম কাস্টম ট্যাব । আপনার ব্যবহারের ক্ষেত্রগুলোর (যেমন ব্রাউজিং বা অথেনটিকেশন) জন্য সঠিক পদ্ধতিটি কীভাবে বেছে নেবেন তা বুঝতে, আপনার অ্যান্ড্রয়েড অ্যাপের মধ্যে ওয়েব কন্টেন্টের ব্যবহার দেখুন।