Skip to Content
Android📚 Học lập trình AndroidAndroidView - Interoperability

AndroidView trong Jetpack Compose

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?

Trường hợpVí dụ
View chưa có trong ComposeWebView, MapView, AdView
Thư viện bên thứ baVideo player (ExoPlayer), Chart libraries
Migration dần dầnChuyển đổi từ XML sang Compose từng bước
Custom Views phức tạpCá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

@Composable fun <T : View> AndroidView( factory: (Context) -> T, modifier: Modifier = Modifier, update: (T) -> Unit = { } )

Các tham số:

Tham sốMô tả
factoryLambda được gọi một lần để tạo View. Nhận Context và trả về instance của View
modifierModifier để điều chỉnh kích thước, padding, căn chỉnh…
updateLambda đượ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

@Composable fun 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 @Composable fun MyScreen() { WebViewScreen(url = "https://www.google.com") }

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 khi url thay đổ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ế

4.1 VideoView

@Composable fun 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

@Composable fun 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

@Composable fun 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

5.1 WebView với Back Navigation

@Composable fun 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

@Composable fun 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

Nếu bạn có layout XML đã định nghĩa sẵn, có thể dùng AndroidViewBinding:

@Composable fun 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

7.1 DisposableEffect cho Cleanup

@Composable fun 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

@Composable fun 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

✅ Nên làm

PracticeLý 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 updatePhản ứng với state changes
Dùng remember khi cần giữ referenceTránh tạo lại khi recomposition
Cleanup trong DisposableEffectGiải phóng resources đúng cách
Thêm webViewClientKiểm soát navigation

❌ Không nên

Anti-patternVấn đề
Tạo View ngoài factoryView có thể bị tạo lại nhiều lần
Load URL trong factoryKhông thể cập nhật URL khi state thay đổi
Quên destroy WebViewMemory leak
Không handle back pressUX kém khi dùng WebView

9. So sánh AndroidView vs Compose Native

Tiêu chíAndroidViewCompose Native
PerformanceOverhead nhỏ do bridgeTối ưu nhất
ComplexityĐơn giản hơn nếu đã có ViewCần viết mới
MaintenancePhụ thuộc View systemThuần Compose
Use caseWebView, Maps, 3rd partyMọi thứ khác

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

  • 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ý lifecyclecleanup đú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
Last updated on