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) } } ) }
এটি আপনার অ্যাপের মধ্যে একটি সাধারণ URL দেখানোর জন্য কাজ করে। তবে, WebView জটিল স্টেট লাইফসাইকেল নিয়ে কাজ করে যা Android View লাইফসাইকেল এবং Compose লাইফসাইকেল থেকে আলাদা। Compose ইন্টিগ্রেট করলে জটিল
WebView পরিস্থিতি তৈরি হতে পারে যার ফলে কঠিন বাগ দেখা দিতে পারে। নিম্নলিখিত বিভাগগুলি
ব্যবহারের ক্ষেত্রে বর্ণনা করে যেগুলি সেইসব ফিচার সাপোর্ট করার জন্য নির্দিষ্ট হ্যান্ডলিং প্রয়োজন হতে পারে।
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 কন্টেনারের মধ্যে
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 কন্টেন্টের সাথে অ্যাপ থিম সিঙ্ক করা
অ্যাপ্লিকেশন যখন লাইট ও ডার্ক মোডের মধ্যে সুইচ করে, তখন 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.CAMERAManifest.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-এর বিকল্প
আপনি যদি WebView এম্বেড করা এড়াতে চান, তাহলে Android ওয়েব কন্টেন্ট দেখানোর জন্য অন্যান্য বিকল্প প্রদান করে, যেমন Chrome কাস্টম ট্যাব। আপনার ব্যবহারের ক্ষেত্রে (যেমন ব্রাউজিং বা যাচাইকরণ) সঠিক পদ্ধতি কীভাবে বেছে নেবেন তা বুঝতে,
আপনার Android অ্যাপের মধ্যে
ওয়েব কন্টেন্ট ব্যবহার করুন দেখুন।
অতিরিক্ত রিসোর্স
- WebView মেমরি ম্যানেজ ও ডায়াগনসিস করা
- WebView-এর স্ট্যাটাস দক্ষতার সাথে ম্যানেজ করা
- ওয়েব অ্যাপ ডিবাগ করা