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.

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.
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.
// 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.
@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.
@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 remember và rememberSaveable 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.
@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ử.
// ✅ 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.
@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) }
}
}
}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.
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.
@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).
@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ị.
@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.
@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.
// 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.
@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.
// 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"))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:
// 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.
// 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.
@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ụ.
// 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.
@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.
// 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 */ } }
)
}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
- What's new in the Jetpack Compose August '26 release - Phát hành Compose 1.12 với Keyed SideEffect, BOM 2026.08.00
- What's new in the Jetpack Compose April '26 release - Testing API v2, debugging shared element
- Navigation releases - Navigation 2.9 với hỗ trợ value class và CollectionNavType
- Side effects in Compose - Tài liệu side effect chính thức
Đ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
ComposeTestRulesử 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.
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ử.

Viết bởi
Anthony Fillion-MailletNgườ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ẻ
Chia sẻ
Bài viết liên quan

Jetpack Compose: Hoạt ảnh nâng cao từng bước
Hướng dẫn đầy đủ về hoạt ảnh nâng cao trong Compose: chuyển tiếp, AnimatedVisibility, Animatable, cử chỉ và hiệu năng cho giao diện Android mượt mà.

Kotlin Flow vs StateFlow vs SharedFlow: Câu hỏi phỏng vấn Android năm 2026
Những câu hỏi Kotlin Flow vs StateFlow vs SharedFlow mà nhà tuyển dụng Android hỏi năm 2026, kèm câu trả lời rõ ràng, một bảng so sánh và mã nguồn sẵn sàng cho production.

Kotlin 2.3 cho Android: Name-Based Destructuring, KMP và Câu Hỏi Phỏng Vấn 2026
Câu hỏi phỏng vấn Kotlin 2.3 dành cho lập trình viên Android năm 2026. Name-based destructuring, KMP, context parameters, Flow và coroutines kèm ví dụ code.