AndroidView - Interoperability
1. Giới thiệu
Phần tiêu đề “1. Giới thiệu”AndroidView là một Composable đặc biệt trong Jetpack Compose cho phép bạn nhúng các View truyền thống (XML-based Views) vào trong giao diện Compose. Đây là cầu nối quan trọng giữa hệ thống View cũ và Compose mới.
Tại sao cần AndroidView?
Phần tiêu đề “Tại sao cần AndroidView?”| Trường hợp | Ví dụ |
|---|---|
| View chưa có trong Compose | WebView, MapView, AdView |
| Thư viện bên thứ ba | Video player (ExoPlayer), Chart libraries |
| Migration dần dần | Chuyển đổi từ XML sang Compose từng bước |
| Custom Views phức tạp | Các view tự tạo đã có sẵn |
Lưu ý: Compose đang phát triển nhanh và nhiều components mới được thêm vào. Tuy nhiên, AndroidView vẫn cần thiết cho các trường hợp đặc biệt như WebView.
2. Cú pháp cơ bản
Phần tiêu đề “2. Cú pháp cơ bản”@Composablefun <T : View> AndroidView( factory: (Context) -> T, modifier: Modifier = Modifier, update: (T) -> Unit = { })Các tham số:
Phần tiêu đề “Các tham số:”| Tham số | Mô tả |
|---|---|
factory |
Lambda được gọi một lần để tạo View. Nhận Context và trả về instance của View |
modifier |
Modifier để điều chỉnh kích thước, padding, căn chỉnh… |
update |
Lambda được gọi mỗi khi recomposition xảy ra. Dùng để cập nhật View theo state |
3. Ví dụ: WebView trong Compose
Phần tiêu đề “3. Ví dụ: WebView trong Compose”@Composablefun WebViewScreen(url: String) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true webViewClient = WebViewClient() // Giữ navigation trong app } }, update = { webView -> webView.loadUrl(url) } )}
// Sử dụng@Composablefun MyScreen() { WebViewScreen(url = "https://www.google.com")}Giải thích:
Phần tiêu đề “Giải thích:”factory: Chỉ chạy một lần khi Composable được tạo. Khởi tạo và cấu hình WebView tại đây.update: Chạy mỗi khiurlthay đổi, cho phép cập nhật mà không tạo lại WebView.webViewClient: Giữ điều hướng bên trong WebView thay vì mở trình duyệt ngoài.
4. Các ví dụ thực tế
Phần tiêu đề “4. Các ví dụ thực tế”4.1 VideoView
Phần tiêu đề “4.1 VideoView”@Composablefun VideoPlayerView(videoUri: Uri) { AndroidView( modifier = Modifier .fillMaxWidth() .height(200.dp), factory = { context -> VideoView(context).apply { setMediaController(MediaController(context)) } }, update = { videoView -> videoView.setVideoURI(videoUri) videoView.start() } )}4.2 CalendarView
Phần tiêu đề “4.2 CalendarView”@Composablefun CalendarPicker( onDateSelected: (Long) -> Unit) { AndroidView( modifier = Modifier.wrapContentSize(), factory = { context -> CalendarView(context).apply { setOnDateChangeListener { _, year, month, dayOfMonth -> val calendar = Calendar.getInstance() calendar.set(year, month, dayOfMonth) onDateSelected(calendar.timeInMillis) } } } )}4.3 Custom EditText với TextWatcher
Phần tiêu đề “4.3 Custom EditText với TextWatcher”@Composablefun LegacyEditText( text: String, onTextChange: (String) -> Unit) { AndroidView( modifier = Modifier .fillMaxWidth() .padding(16.dp), factory = { context -> EditText(context).apply { hint = "Nhập text..." addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { onTextChange(s?.toString() ?: "") } override fun afterTextChanged(s: Editable?) {} }) } }, update = { editText -> if (editText.text.toString() != text) { editText.setText(text) } } )}5. WebView nâng cao
Phần tiêu đề “5. WebView nâng cao”5.1 WebView với Back Navigation
Phần tiêu đề “5.1 WebView với Back Navigation”@Composablefun AdvancedWebView(url: String) { var webView by remember { mutableStateOf<WebView?>(null) }
BackHandler(enabled = webView?.canGoBack() == true) { webView?.goBack() }
AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> WebView(context).apply { settings.apply { javaScriptEnabled = true domStorageEnabled = true loadWithOverviewMode = true useWideViewPort = true } webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? ): Boolean { return false } } }.also { webView = it } }, update = { view -> view.loadUrl(url) } )}5.2 WebView với Loading State
Phần tiêu đề “5.2 WebView với Loading State”@Composablefun WebViewWithLoading(url: String) { var isLoading by remember { mutableStateOf(true) }
Box(modifier = Modifier.fillMaxSize()) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true webViewClient = object : WebViewClient() { override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { isLoading = true } override fun onPageFinished(view: WebView?, url: String?) { isLoading = false } } loadUrl(url) } } )
if (isLoading) { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center) ) } }}6. AndroidViewBinding - Sử dụng View Binding
Phần tiêu đề “6. AndroidViewBinding - Sử dụng View Binding”Nếu bạn có layout XML đã định nghĩa sẵn, có thể dùng AndroidViewBinding:
@Composablefun LegacyLayoutInCompose() { AndroidViewBinding(MyLegacyLayoutBinding::inflate) { binding -> binding.textView.text = "Hello from Compose!" binding.button.setOnClickListener { // Handle click } }}Cần thêm dependency:
implementation("androidx.compose.ui:ui-viewbinding")7. Lifecycle và Memory Management
Phần tiêu đề “7. Lifecycle và Memory Management”7.1 DisposableEffect cho Cleanup
Phần tiêu đề “7.1 DisposableEffect cho Cleanup”@Composablefun WebViewWithLifecycle(url: String) { val context = LocalContext.current
val webView = remember { WebView(context).apply { settings.javaScriptEnabled = true } }
DisposableEffect(Unit) { onDispose { webView.stopLoading() webView.destroy() } }
AndroidView( modifier = Modifier.fillMaxSize(), factory = { webView }, update = { it.loadUrl(url) } )}7.2 Lifecycle Observer
Phần tiêu đề “7.2 Lifecycle Observer”@Composablefun LifecycleAwareWebView(url: String) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current
val webView = remember { WebView(context) }
DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_RESUME -> webView.onResume() Lifecycle.Event.ON_PAUSE -> webView.onPause() Lifecycle.Event.ON_DESTROY -> webView.destroy() else -> {} } } lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } }
AndroidView( modifier = Modifier.fillMaxSize(), factory = { webView }, update = { it.loadUrl(url) } )}8. Best Practices
Phần tiêu đề “8. Best Practices”✅ Nên làm
Phần tiêu đề “✅ Nên làm”| Practice | Lý do |
|---|---|
Khởi tạo View trong factory |
Đảm bảo View chỉ được tạo một lần |
Cập nhật trong update |
Phản ứng với state changes |
Dùng remember khi cần giữ reference |
Tránh tạo lại khi recomposition |
Cleanup trong DisposableEffect |
Giải phóng resources đúng cách |
Thêm webViewClient |
Kiểm soát navigation |
❌ Không nên
Phần tiêu đề “❌ Không nên”| Anti-pattern | Vấn đề |
|---|---|
Tạo View ngoài factory |
View có thể bị tạo lại nhiều lần |
Load URL trong factory |
Không thể cập nhật URL khi state thay đổi |
| Quên destroy WebView | Memory leak |
| Không handle back press | UX kém khi dùng WebView |
9. So sánh AndroidView vs Compose Native
Phần tiêu đề “9. So sánh AndroidView vs Compose Native”| Tiêu chí | AndroidView | Compose Native |
|---|---|---|
| Performance | Overhead nhỏ do bridge | Tối ưu nhất |
| Complexity | Đơn giản hơn nếu đã có View | Cần viết mới |
| Maintenance | Phụ thuộc View system | Thuần Compose |
| Use case | WebView, Maps, 3rd party | Mọi thứ khác |
10. Permissions cần thiết
Phần tiêu đề “10. Permissions cần thiết”Với WebView, cần thêm permission trong AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />11. Tổng kết
Phần tiêu đề “11. Tổng kết”- AndroidView là cầu nối giữa View System truyền thống và Jetpack Compose
- Sử dụng
factoryđể tạo View một lần - Sử dụng
updateđể cập nhật View khi state thay đổi - Luôn xử lý lifecycle và cleanup đúng cách với
DisposableEffect - Ưu tiên dùng Compose native components khi có thể
- AndroidView hữu ích cho: WebView, MapView, VideoView, và các thư viện bên thứ ba