20 Câu Hỏi Phỏng Vấn Jetpack Compose Hàng Đầu Năm 2026

20 câu hỏi phỏng vấn Jetpack Compose thường gặp nhất: recomposition, quản lý state, navigation, hiệu năng và các pattern kiến trúc.

Câu hỏi phỏng vấn Jetpack Compose dành cho lập trình viên Android

Jetpack Compose đã trở thành bộ công cụ UI tiêu chuẩn cho phát triển Android. Các cuộc phỏng vấn kỹ thuật hiện nay thường xuyên kiểm tra khả năng sử dụng Compose, từ cơ chế recomposition đến quản lý state và tối ưu hiệu năng. Dưới đây là 20 câu hỏi thường gặp nhất, với câu trả lời chi tiết và ví dụ code.

Cách sử dụng hướng dẫn này

Mỗi câu hỏi bao gồm câu trả lời có cấu trúc và ví dụ code. Các câu hỏi được sắp xếp theo độ khó tăng dần: cơ bản, trung cấp, sau đó nâng cao.

Kiến Thức Cơ Bản Jetpack Compose

1. Sự khác biệt giữa Compose và hệ thống view XML là gì?

Compose sử dụng paradigm khai báo: UI được mô tả như một hàm của state, và framework xử lý cập nhật tự động. Hệ thống XML truyền thống là mệnh lệnh, yêu cầu thao tác view thủ công qua findViewById hoặc View Binding.

DeclarativeExample.ktkotlin
// Compose: UI updates automatically when count changes
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }  // Reactive state
    Button(onClick = { count++ }) {               // UI declaration
        Text("Clicks: $count")                    // Recomposed automatically
    }
}

Với Compose, không cần tìm tham chiếu TextView và cập nhật thủ công. Recomposition xử lý mọi thứ.

2. Recomposition là gì?

Recomposition là quá trình Compose gọi lại các hàm @Composable khi state của chúng thay đổi. Chỉ các hàm có tham số đã thay đổi mới được thực thi lại, giúp tối ưu hiệu năng.

RecompositionExample.ktkotlin
@Composable
fun UserCard(name: String, age: Int) {
    Column {
        Text("Name: $name")   // Recomposed only if name changes
        Text("Age: $age")     // Recomposed only if age changes
        StaticBadge()          // Not recomposed if its inputs remain the same
    }
}

@Composable
fun StaticBadge() {
    Text("Static badge")  // Compose knows this function is stable
}

Điểm quan trọng cho phỏng vấn: recomposition mang tính lạc quan (Compose giả định có thể hủy bỏ) và không theo thứ tự (thứ tự thực thi các composable không được đảm bảo).

3. remember làm gì?

remember lưu giữ giá trị qua các lần recomposition. Nếu không có remember, mỗi lần recomposition sẽ đặt lại biến về giá trị ban đầu.

RememberExample.ktkotlin
@Composable
fun InputField() {
    // ✅ Value survives recompositions
    var text by remember { mutableStateOf("") }

    // ❌ Without remember, text resets to "" on every recomposition
    // var text by mutableStateOf("")

    TextField(
        value = text,
        onValueChange = { text = it },  // Triggers recomposition
        label = { Text("Enter text") }
    )
}

4. Sự khác biệt giữa rememberrememberSaveable là gì?

remember lưu giữ giá trị qua các lần recomposition nhưng mất khi configuration change (xoay màn hình). rememberSaveable lưu giữ giá trị qua configuration change sử dụng cơ chế SavedInstanceState.

RememberSaveableExample.ktkotlin
@Composable
fun SearchBar() {
    // Lost after screen rotation
    var query by remember { mutableStateOf("") }

    // Preserved after screen rotation
    var savedQuery by rememberSaveable { mutableStateOf("") }

    TextField(
        value = savedQuery,
        onValueChange = { savedQuery = it },
        placeholder = { Text("Search...") }
    )
}

Quản Lý State trong Compose

5. State hoisting là gì?

State hoisting có nghĩa là di chuyển state từ composable lên composable cha. Composable con trở thành stateless: nhận state như tham số và thông báo thay đổi qua callback. Pattern này thiết yếu để xây dựng các component có thể tái sử dụng và kiểm thử.

StateHoistingExample.ktkotlin
// ✅ Stateless composable, easy to test and reuse
@Composable
fun EmailInput(
    email: String,                    // State provided by parent
    onEmailChange: (String) -> Unit,  // Callback to parent
    modifier: Modifier = Modifier
) {
    TextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        modifier = modifier
    )
}

// Parent manages the state
@Composable
fun LoginForm() {
    var email by remember { mutableStateOf("") }
    EmailInput(
        email = email,
        onEmailChange = { email = it }  // Parent controls state
    )
}

Pattern này là nền tảng trong Compose và thường xuất hiện trong phỏng vấn.

6. derivedStateOf hoạt động như thế nào?

derivedStateOf tạo state phái sinh chỉ kích hoạt recomposition khi kết quả tính toán thay đổi, không phải trên mỗi lần sửa đổi nguồn.

DerivedStateExample.ktkotlin
@Composable
fun FilteredList(items: List<String>) {
    var searchQuery by remember { mutableStateOf("") }

    // Recalculated only when the filtered result actually changes
    val filteredItems by remember(items) {
        derivedStateOf {
            items.filter { it.contains(searchQuery, ignoreCase = true) }
        }
    }

    Column {
        TextField(value = searchQuery, onValueChange = { searchQuery = it })
        LazyColumn {
            items(filteredItems) { item -> Text(item) }
        }
    }
}
Khi nào sử dụng derivedStateOf

Cơ chế này hữu ích khi state thay đổi thường xuyên nhưng kết quả phái sinh ít khi thay đổi (ví dụ: danh sách đã lọc, nút enabled/disabled dựa trên tính hợp lệ của form).

7. Sự khác biệt giữa StateFlow và Compose State<T> là gì?

StateFlow (Kotlin coroutines) là reactive stream từ ViewModel. State<T> là cơ chế native của Compose để kích hoạt recomposition. Trong thực tế, StateFlow được thu thập trong composable qua collectAsStateWithLifecycle(). Để hiểu sâu hơn về coroutine flow, xem hướng dẫn đầy đủ về Kotlin coroutines.

StateFlowExample.ktkotlin
class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
}

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    // Converts StateFlow to State<T> for Compose
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    Text("Hello, ${uiState.userName}")
}

Khuyến nghị là sử dụng collectAsStateWithLifecycle() (thay vì collectAsState()) vì nó tôn trọng lifecycle và dừng thu thập khi màn hình không còn hiển thị.

Side Effect và Lifecycle

8. Các side effect chính trong Compose là gì?

Side effect cho phép chạy code không phải composable (gọi mạng, logging, điều hướng) một cách có kiểm soát. Các side effect chính bao gồm LaunchedEffect, DisposableEffect, SideEffect, và Keyed SideEffect mới được giới thiệu trong Compose 1.12.

SideEffectsExample.ktkotlin
@Composable
fun AnalyticsScreen(screenName: String) {
    // LaunchedEffect: runs once when screenName changes
    LaunchedEffect(screenName) {
        analyticsTracker.logScreenView(screenName)  // Suspended call
    }

    // DisposableEffect: with cleanup (like useEffect with cleanup)
    DisposableEffect(Unit) {
        val listener = onScrollListener()
        scrollView.addListener(listener)
        onDispose {
            scrollView.removeListener(listener)  // Cleanup guaranteed
        }
    }

    // SideEffect: runs after every successful recomposition
    SideEffect {
        logger.log("Screen recomposed")  // Non-suspended code
    }
}

Cập nhật Compose 1.12: Keyed SideEffect cung cấp overload mới hỗ trợ đối số key cho side effect một lần mỗi khi các key cụ thể thay đổi. Theo benchmark chính thức, nó có thể nhanh hơn đến 90% so với LaunchedEffect cho các thao tác non-suspended.

9. Khi nào sử dụng LaunchedEffect vs rememberCoroutineScope?

LaunchedEffect gắn với composition: coroutine bị hủy khi composable rời composition hoặc khi key thay đổi. rememberCoroutineScope cung cấp scope do người dùng kiểm soát, hữu ích cho các hành động do người dùng kích hoạt (nhấn nút).

CoroutineScopeExample.ktkotlin
@Composable
fun DataScreen(userId: String) {
    // ✅ LaunchedEffect: automatic loading tied to lifecycle
    LaunchedEffect(userId) {
        loadUserData(userId)  // Re-launched if userId changes
    }

    // ✅ rememberCoroutineScope: one-off user action
    val scope = rememberCoroutineScope()
    Button(onClick = {
        scope.launch { refreshData() }  // Triggered manually
    }) {
        Text("Refresh")
    }
}

Sẵn sàng chinh phục phỏng vấn Android?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Layout và Component Nâng Cao

10. LazyColumn hoạt động như thế nào và khác với RecyclerView ra sao?

LazyColumn là tương đương Compose của RecyclerView. Nó chỉ compose các phần tử hiển thị trên màn hình và tái chế các composable cuộn ra khỏi cửa sổ hiển thị.

LazyColumnExample.ktkotlin
@Composable
fun UserList(users: List<User>) {
    LazyColumn(
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)  // Spacing between items
    ) {
        items(
            items = users,
            key = { it.id }  // Stable key to optimize recompositions
        ) { user ->
            UserCard(user)
        }
    }
}

Điểm quan trọng: luôn cung cấp tham số key ổn định để tránh recomposition không cần thiết khi sắp xếp hoặc xóa phần tử.

11. Cách tạo custom layout như thế nào?

Compose cho phép tạo custom layout qua hàm Layout. Điều này thay thế các triển khai ViewGroup tùy chỉnh từ hệ thống view.

CustomLayoutExample.ktkotlin
@Composable
fun OverlappingRow(
    overlapOffset: Dp = (-16).dp,  // Negative offset for overlap
    content: @Composable () -> Unit
) {
    Layout(content = content) { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val width = placeables.sumOf { it.width } + (overlapOffset.roundToPx() * (placeables.size - 1))
        val height = placeables.maxOf { it.height }

        layout(width, height) {
            var xOffset = 0
            placeables.forEach { placeable ->
                placeable.placeRelative(xOffset, 0)
                xOffset += placeable.width + overlapOffset.roundToPx()
            }
        }
    }
}

12. Cách triển khai theming tùy chỉnh với MaterialTheme?

Theming trong Compose dựa trên CompositionLocal. MaterialTheme cung cấp giá trị màu sắc, typography và hình dạng có thể truy cập trong toàn bộ composable tree.

CustomThemeExample.ktkotlin
// Custom color definitions
private val DarkColorScheme = darkColorScheme(
    primary = Color(0xFF6200EE),
    secondary = Color(0xFF03DAC6),
    background = Color(0xFF121212)
)

@Composable
fun AppTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = DarkColorScheme,
        typography = AppTypography,    // Custom typography
        content = content
    )
}

// Usage in a composable
@Composable
fun ThemedCard() {
    Card(colors = CardDefaults.cardColors(
        containerColor = MaterialTheme.colorScheme.surface  // Theme access
    )) {
        Text(
            text = "Content",
            style = MaterialTheme.typography.bodyLarge  // Theme typography
        )
    }
}

Điều Hướng trong Compose

13. Compose Navigation hoạt động như thế nào?

Compose Navigation sử dụng NavHost với route được khai báo dưới dạng serializable type (Navigation 2.8+). Kể từ Navigation 2.9, thư viện hỗ trợ value class và List<Enum> như kiểu đối số mà không cần triển khai NavType tùy chỉnh.

NavigationExample.ktkotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen(onNavigateToDetail = { id ->
                navController.navigate("detail/$id")  // Navigation with argument
            })
        }
        composable(
            route = "detail/{userId}",
            arguments = listOf(navArgument("userId") { type = NavType.StringType })
        ) { backStackEntry ->
            val userId = backStackEntry.arguments?.getString("userId") ?: ""
            DetailScreen(userId = userId)
        }
    }
}

14. Cách truyền dữ liệu giữa các màn hình?

Đối số đơn giản (String, Int) được truyền trực tiếp qua route. Với đối tượng phức tạp, khuyến nghị hiện tại là sử dụng ViewModel chung hoặc chỉ truyền identifier và tải dữ liệu ở màn hình đích. Navigation 2.9 giới thiệu CollectionNavType<T> cho đối số dựa trên collection như list và array.

NavigationArgsExample.ktkotlin
// Type-safe navigation with Kotlin Serialization (Navigation 2.8+)
@Serializable
data class ProfileRoute(val userId: String, val tab: String = "info")

// Declaration
composable<ProfileRoute> { backStackEntry ->
    val route = backStackEntry.toRoute<ProfileRoute>()
    ProfileScreen(userId = route.userId, tab = route.tab)
}

// Navigation
navController.navigate(ProfileRoute(userId = "123", tab = "stats"))
Anti-pattern cần tránh

Không bao giờ truyền đối tượng phức tạp được serialize trong route. Truyền ID và để màn hình đích tải dữ liệu qua ViewModel. Để có hướng dẫn kiến trúc, xem MVVM vs MVI: Chọn Kiến Trúc Nào.

Hiệu Năng và Tối Ưu

15. Cách ngăn chặn recomposition không cần thiết?

Ba chiến lược chính để giảm thiểu recomposition không cần thiết:

PerformanceExample.ktkotlin
// 1. Use stable classes (data class with immutable properties)
@Stable  // Tells Compose this class is stable
data class UserState(
    val name: String,
    val avatar: String
)

// 2. Extract lambdas with remember
@Composable
fun OptimizedList(onItemClick: (String) -> Unit) {
    val stableCallback = remember(onItemClick) { onItemClick }
    LazyColumn {
        items(100) { index ->
            ItemRow(onClick = { stableCallback("item_$index") })
        }
    }
}

// 3. Use key() to help Compose identify elements
@Composable
fun UserTabs(users: List<User>) {
    Column {
        users.forEach { user ->
            key(user.id) {    // Stable identity
                UserRow(user)
            }
        }
    }
}

16. Cách profiling hiệu năng ứng dụng Compose?

Layout Inspector của Android Studio hiển thị số lần recomposition cho mỗi composable. Compose 1.13 alpha giới thiệu RecompositionTracer để ghi lại luồng vô hiệu hóa recomposition. Flag debugInspectorInfo cũng hỗ trợ chẩn đoán.

ProfilingExample.ktkotlin
// Enable recomposition counters in debug
@Composable
fun DebugRecomposition(tag: String, content: @Composable () -> Unit) {
    val recompositionCount = remember { mutableIntStateOf(0) }

    SideEffect {
        recompositionCount.intValue++  // Incremented on every recomposition
        Log.d("Recomposition", "$tag: ${recompositionCount.intValue} times")
    }

    content()
}

// Usage
DebugRecomposition("UserCard") {
    UserCard(user)
}

Ngoài ra, Compose Compiler Metrics tạo báo cáo chi tiết về các hàm skippable, restartable và các class stable/unstable.

17. Modifier là gì và tại sao quan trọng?

Modifier là chuỗi hướng dẫn có thứ tự để sửa đổi giao diện và hành vi của composable. Thứ tự modifier ảnh hưởng trực tiếp đến rendering.

ModifierOrderExample.ktkotlin
@Composable
fun ModifierOrderDemo() {
    // ❌ Padding THEN background = padding not colored
    Text(
        text = "Hello",
        modifier = Modifier
            .padding(16.dp)
            .background(Color.Red)
    )

    // ✅ Background THEN padding = padding is colored
    Text(
        text = "Hello",
        modifier = Modifier
            .background(Color.Red)
            .padding(16.dp)
    )
}

Thực hành tốt: luôn chấp nhận tham số modifier: Modifier = Modifier trong các composable có thể tái sử dụng để cho phép tùy chỉnh từ cha.

Kiến Trúc và Pattern Nâng Cao

18. Cách cấu trúc màn hình Compose với ViewModel?

Pattern được khuyến nghị tách biệt state UI trong data class, event trong sealed interface, và ViewModel xử lý logic nghiệp vụ.

ScreenArchitectureExample.ktkotlin
// UI State
data class ProfileUiState(
    val user: User? = null,
    val isLoading: Boolean = false,
    val error: String? = null
)

// User events
sealed interface ProfileEvent {
    data object Refresh : ProfileEvent
    data class UpdateName(val name: String) : ProfileEvent
}

// ViewModel
class ProfileViewModel(private val repo: UserRepository) : ViewModel() {
    private val _uiState = MutableStateFlow(ProfileUiState(isLoading = true))
    val uiState = _uiState.asStateFlow()

    fun onEvent(event: ProfileEvent) {
        when (event) {
            is ProfileEvent.Refresh -> loadProfile()
            is ProfileEvent.UpdateName -> updateName(event.name)
        }
    }
}

// Compose screen
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    ProfileContent(
        uiState = uiState,
        onEvent = viewModel::onEvent  // Event delegation
    )
}

19. Cách kiểm thử composable?

Compose cung cấp thư viện kiểm thử với ComposeTestRule cho UI test và semantic assertion. Kể từ Compose 1.11, testing API v2 là mặc định, với v1 đã deprecated. Thay đổi chính là chuyển từ UnconfinedTestDispatcher (thực thi ngay lập tức) sang StandardTestDispatcher (thực thi xếp hàng), nghĩa là test bây giờ phản ánh tốt hơn hành vi production.

ComposableTestExample.ktkotlin
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun counter_incrementsOnClick() {
    composeTestRule.setContent {
        Counter()  // The composable under test
    }

    // Verify initial state
    composeTestRule.onNodeWithText("Clicks: 0").assertIsDisplayed()

    // Simulate a click
    composeTestRule.onNodeWithText("Clicks: 0").performClick()

    // With v2 APIs, advance the clock explicitly
    composeTestRule.waitForIdle()

    // Verify new state
    composeTestRule.onNodeWithText("Clicks: 1").assertIsDisplayed()
}

Để unit test composable stateless, kiểm thử ViewModel riêng với test JUnit/Turbine tiêu chuẩn thường hiệu quả hơn.

20. Cách tích hợp Compose vào ứng dụng dựa trên XML hiện có?

Khả năng tương tác hai chiều: ComposeView nhúng Compose trong XML, và AndroidView sử dụng view cổ điển trong Compose.

InteropExample.ktkotlin
// Compose in XML (in a Fragment or Activity)
class ProfileFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(
                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
            )
            setContent {
                AppTheme { ProfileScreen() }
            }
        }
    }
}

// XML View in Compose
@Composable
fun LegacyMapView() {
    AndroidView(
        factory = { context -> MapView(context).apply { onCreate(null) } },
        update = { mapView -> mapView.getMapAsync { /* config */ } }
    )
}
Chiến lược di chuyển được khuyến nghị

Di chuyển từng màn hình, bắt đầu với các màn hình đơn giản nhất. Mọi màn hình mới nên hoàn toàn bằng Compose, trong khi các màn hình hiện có di chuyển dần dần.

Sẵn sàng chinh phục phỏng vấn Android?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Nguồn Tham Khảo

Điểm Quan Trọng cho Phỏng Vấn Jetpack Compose

20 câu hỏi này bao gồm các kiến thức cơ bản mà mọi lập trình viên Android cần nắm vững cho phỏng vấn Jetpack Compose. Để thực hành, thử module câu hỏi phỏng vấn Jetpack Compose.

  • ✅ Hiểu recomposition và hành vi lạc quan của nó
  • ✅ Thành thạo remember, rememberSaveable, và derivedStateOf
  • ✅ Áp dụng state hoisting một cách có hệ thống
  • ✅ Biết các side effect (LaunchedEffect, DisposableEffect, SideEffect, và Keyed SideEffect mới)
  • ✅ Tối ưu hiệu năng (stable class, key, lambda)
  • ✅ Cấu trúc màn hình với ViewModel + UiState + Event
  • ✅ Kiểm thử composable với ComposeTestRule sử dụng testing API v2
  • ✅ Xử lý tương tác Compose/View

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thử thách hôm nay

Bạn có tìm ra lỗi trong Android không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 20 tháng 8, 2026

Thẻ

#jetpack compose
#android
#interview
#kotlin
#ui

Chia sẻ

Bài viết liên quan