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 với ví dụ code chi tiết.

Phỏng vấn Android hiện đại đòi hỏi sự thành thạo Jetpack Compose. Toolkit khai báo này đã trở thành tiêu chuẩn ngành, thay thế cách tiếp cận XML truyền thống. Hướng dẫn này bao gồm 20 câu hỏi thường gặp nhất, từ kiến thức cơ bản đến các pattern kiến trúc nâng cao.
Người phỏng vấn không chỉ kiểm tra kiến thức cú pháp. Khả năng giải thích các cơ chế nội bộ như recomposition và smart recomposition là yếu tố phân biệt giữa ứng viên học thuộc và người thực sự hiểu framework.
Kiến Thức Cơ Bản Jetpack Compose
1. Sự khác biệt giữa Jetpack Compose và XML truyền thống là gì?
Jetpack Compose sử dụng cách tiếp cận khai báo: UI được mô tả như một hàm của state. Khi state thay đổi, framework tự động tính toán lại phần UI bị ảnh hưởng. XML sử dụng cách tiếp cận mệnh lệnh: lập trình viên phải thủ công thao tác view thông qua findViewById và setter.
// Compose: declarative - UI is a function of state
@Composable
fun Greeting(name: String) {
// UI automatically updates when name changes
Text(text = "Hello, $name!")
}
// XML equivalent requires:
// 1. Layout XML file
// 2. findViewById in Activity
// 3. Manual setText call
// textView.text = "Hello, $name!"Ưu điểm của Compose bao gồm: loại bỏ boilerplate, xem trước trực tiếp trong IDE, type safety hoàn toàn với Kotlin và khả năng composability tự nhiên hơn so với kế thừa trong View.
2. Recomposition là gì và hoạt động như thế nào?
Recomposition là quá trình Compose gọi lại hàm composable khi state mà nó đọc thay đổi. Framework thực hiện smart recomposition: chỉ những composable đọc state thay đổi mới được gọi lại, không phải toàn bộ tree.
@Composable
fun Counter() {
// When count changes, only this composable recomposes
var count by remember { mutableStateOf(0) }
Column {
// This Text recomposes when count changes
Text(text = "Count: $count")
// This Button also recomposes (same scope)
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
// This composable does NOT recompose when count changes
@Composable
fun StaticHeader() {
Text(text = "This never recomposes due to Counter")
}Recomposition mang tính lạc quan và có thể bị hủy bỏ. Compose có thể bắt đầu recomposition, hủy bỏ nếu state thay đổi lần nữa, và bắt đầu lại với state mới nhất.
3. Phân biệt remember và rememberSaveable?
remember lưu trữ giá trị trong khi composable còn nằm trong composition. Giá trị mất khi configuration change (xoay màn hình). rememberSaveable lưu trữ giá trị qua configuration change sử dụng cơ chế Bundle của Android.
@Composable
fun SearchBar() {
// Lost on configuration change (rotation)
var query by remember { mutableStateOf("") }
// Survives configuration change
var savedQuery by rememberSaveable { mutableStateOf("") }
TextField(
value = savedQuery,
onValueChange = { savedQuery = it },
placeholder = { Text("Search...") }
)
}Sử dụng remember cho state tạm thời không cần lưu giữ (animation, state hover). Sử dụng rememberSaveable cho input người dùng và state điều hướng cần tồn tại qua việc xoay màn hình.
4. Compose quản lý vòng đời composable như thế nào?
Mỗi composable có ba giai đoạn: vào composition (lần đầu được gọi), recomposition (được gọi lại khi state thay đổi) và rời composition (bị xóa khỏi tree). Lifecycle này khác với Activity/Fragment lifecycle.
@Composable
fun UserProfile(userId: String) {
// Enters composition: effect starts
// Recomposition: effect restarts if userId changes
// Leaves composition: effect is cancelled
LaunchedEffect(userId) {
// Coroutine tied to this composable's lifecycle
val user = repository.getUser(userId)
}
// DisposableEffect for cleanup
DisposableEffect(Unit) {
val listener = database.addListener { /* ... */ }
onDispose {
// Called when leaving composition
listener.remove()
}
}
}Hiểu vòng đời này rất quan trọng để quản lý side effect và tránh memory leak.
Quản Lý State
5. State hoisting là gì và tại sao quan trọng?
State hoisting là pattern di chuyển state lên composable cha. Composable nhận state trở thành stateless và dễ kiểm thử cũng như tái sử dụng hơn. State được nâng lên cấp thấp nhất bao gồm tất cả consumer.
// Stateless: receives state, emits events
@Composable
fun EmailInput(
email: String,
onEmailChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
value = email,
onValueChange = onEmailChange,
label = { Text("Email") },
modifier = modifier
)
}
// Stateful: owns and manages state
@Composable
fun LoginScreen() {
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
Column {
EmailInput(
email = email,
onEmailChange = { email = it }
)
PasswordInput(
password = password,
onPasswordChange = { password = it }
)
}
}Pattern này tuân theo nguyên tắc unidirectional data flow: state chảy xuống dưới, event chảy lên trên.
6. Khi nào sử dụng derivedStateOf?
derivedStateOf tạo state được tính toán từ state khác và chỉ kích hoạt recomposition khi kết quả tính toán thay đổi. Hữu ích để tránh recomposition không cần thiết.
@Composable
fun ShoppingCart(items: List<CartItem>) {
// Without derivedStateOf: recomposes on EVERY list change
// val total = items.sumOf { it.price }
// With derivedStateOf: recomposes only when total changes
val total by remember(items) {
derivedStateOf { items.sumOf { it.price } }
}
// Only recomposes when hasDiscount value changes
val hasDiscount by remember(total) {
derivedStateOf { total > 100.0 }
}
Text("Total: $$total")
if (hasDiscount) {
Text("Discount applied!")
}
}Sử dụng derivedStateOf khi state nguồn thay đổi thường xuyên nhưng kết quả đạo hàm ít thay đổi. Không sử dụng cho phép biến đổi đơn giản luôn tạo ra giá trị khác.
7. StateFlow khác gì với Compose State?
StateFlow từ Kotlin coroutines và không phụ thuộc platform. MutableState từ Compose tích hợp trực tiếp với hệ thống recomposition. Cả hai bổ sung cho nhau trong kiến trúc hiện đại.
// ViewModel with StateFlow
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UserUiState())
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun updateName(name: String) {
_uiState.update { it.copy(name = name) }
}
}
data class UserUiState(
val name: String = "",
val isLoading: Boolean = false
)
// Composable collecting StateFlow
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
// collectAsState bridges StateFlow to Compose State
val uiState by viewModel.uiState.collectAsState()
if (uiState.isLoading) {
CircularProgressIndicator()
} else {
Text(uiState.name)
}
}Sử dụng StateFlow trong ViewModel cho logic nghiệp vụ. Sử dụng MutableState cho state UI cục bộ trong composable.
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.
Side Effect
8. Các Side Effect API chính trong Compose là gì?
Compose cung cấp nhiều API để thực thi các thao tác có hiệu ứng phụ ngoài scope composable: LaunchedEffect, DisposableEffect, SideEffect và rememberCoroutineScope.
@Composable
fun AnalyticsScreen(screenName: String) {
// LaunchedEffect: runs suspend function, restarts on key change
LaunchedEffect(screenName) {
analytics.logScreenView(screenName)
}
// DisposableEffect: setup + cleanup
DisposableEffect(Unit) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
analytics.logResume()
}
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
// SideEffect: runs on every successful recomposition
SideEffect {
// Update non-Compose state
analyticsHelper.setCurrentScreen(screenName)
}
}
@Composable
fun ActionButton() {
// rememberCoroutineScope: for event-driven coroutines
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
// Cannot use LaunchedEffect here (not composable context)
performAction()
}
}) {
Text("Execute")
}
}Quy tắc: LaunchedEffect cho các thao tác gắn với lifecycle composable, rememberCoroutineScope cho các thao tác được kích hoạt bởi sự kiện người dùng.
9. Quản lý coroutine bên trong Compose như thế nào?
Coroutine trong Compose được quản lý thông qua scope gắn với lifecycle composable. LaunchedEffect hủy coroutine khi composable rời khỏi composition hoặc key thay đổi.
@Composable
fun SearchScreen(query: String) {
var results by remember { mutableStateOf<List<Result>>(emptyList()) }
// Debounced search: cancels previous on new query
LaunchedEffect(query) {
delay(300) // debounce
results = searchRepository.search(query)
}
}
@Composable
fun InfiniteAnimation() {
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
val alpha by infiniteTransition.animateFloat(
initialValue = 0.3f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
),
label = "alpha"
)
Box(
modifier = Modifier
.size(100.dp)
.alpha(alpha)
.background(Color.Blue)
)
}Không bao giờ khởi chạy coroutine trong body composable mà không có side effect API. Điều này gây ra việc khởi chạy coroutine mới mỗi lần recomposition.
Layout
10. LazyColumn hoạt động như thế nào và tối ưu ra sao?
LazyColumn chỉ compose và layout các item hiển thị trên màn hình, tương tự RecyclerView. Tham số key rất quan trọng cho hiệu năng vì giúp Compose nhận diện từng item một cách duy nhất.
@Composable
fun UserList(users: List<User>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = users,
// Key helps Compose track items across changes
key = { user -> user.id }
) { user ->
UserCard(user = user)
}
}
}
// Optimized with remember and stable types
@Composable
fun UserCard(user: User) {
// Skipped during recomposition if user hasn't changed
Card {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = user.name, style = MaterialTheme.typography.titleMedium)
Text(text = user.email, style = MaterialTheme.typography.bodyMedium)
}
}
}Tránh các thao tác nặng trong lambda items. Sử dụng remember cho các phép tính tốn kém và đảm bảo sử dụng data class để Compose có thể thực hiện equality check chính xác.
11. Tạo custom Layout trong Compose như thế nào?
Custom Layout cho phép kiểm soát hoàn toàn việc đo lường và đặt vị trí child. Quá trình này gồm hai giai đoạn: measure (đo từng child) và place (đặt child vào vị trí chính xác).
@Composable
fun StaggeredGrid(
modifier: Modifier = Modifier,
columns: Int = 2,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
val columnWidth = constraints.maxWidth / columns
val itemConstraints = constraints.copy(
minWidth = columnWidth,
maxWidth = columnWidth
)
// Measure phase
val placeables = measurables.map { it.measure(itemConstraints) }
// Track height per column
val columnHeights = IntArray(columns)
val positions = placeables.map { placeable ->
val col = columnHeights.indexOfMin()
val position = IntOffset(col * columnWidth, columnHeights[col])
columnHeights[col] += placeable.height
position
}
val height = columnHeights.max()
// Place phase
layout(constraints.maxWidth, height) {
placeables.forEachIndexed { index, placeable ->
placeable.placeRelative(positions[index])
}
}
}
}Custom Layout nên được sử dụng khi Row, Column và Box không đủ đáp ứng nhu cầu. Cho các trường hợp đơn giản hơn, Modifier.layout là đủ.
12. Sử dụng MaterialTheme trong Compose như thế nào?
MaterialTheme cung cấp hệ thống thiết kế nhất quán thông qua ba trụ cột: color scheme, typography và shapes. Material 3 (Material You) hỗ trợ dynamic color dựa trên hình nền người dùng.
// Custom theme definition
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) {
darkColorScheme(
primary = Color(0xFFBB86FC),
secondary = Color(0xFF03DAC6),
background = Color(0xFF121212)
)
} else {
lightColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
background = Color(0xFFFFFFFF)
)
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
shapes = AppShapes,
content = content
)
}
// Usage in composables
@Composable
fun ThemedButton() {
Button(
onClick = { },
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
) {
Text(
text = "Themed",
style = MaterialTheme.typography.labelLarge
)
}
}Luôn sử dụng token từ MaterialTheme thay vì hardcode màu sắc hoặc kích thước font. Điều này đảm bảo tính nhất quán và dễ dàng triển khai dark mode.
Điều Hướng
13. Triển khai điều hướng với NavHost như thế nào?
NavHost từ thư viện Navigation Compose quản lý back stack và chuyển đổi giữa các màn hình. Mỗi điểm đến được định nghĩa là composable với route duy nhất.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(
navArgument("itemId") { type = NavType.StringType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
composable("settings") {
SettingsScreen(
onBack = { navController.popBackStack() }
)
}
}
}Không bao giờ truyền navController trực tiếp cho composable con. Sử dụng callback lambda để composable có thể kiểm thử được và không phụ thuộc vào điều hướng.
14. Gửi dữ liệu giữa các màn hình trong Compose như thế nào?
Dữ liệu đơn giản được gửi qua tham số route. Cho đối tượng phức tạp, sử dụng savedStateHandle từ ViewModel hoặc shared ViewModel với scope navigation graph.
// Simple arguments via route
navController.navigate("detail/${item.id}")
// Complex data via savedStateHandle
class DetailViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
val itemId: String = checkNotNull(savedStateHandle["itemId"])
// Or pass result back to previous screen
fun setResult(result: String) {
savedStateHandle["result"] = result
}
}
// Reading result in previous screen
@Composable
fun ListScreen(navController: NavController) {
val result = navController.currentBackStackEntry
?.savedStateHandle
?.getStateFlow<String>("result", "")
?.collectAsState()
// Use result
}
// Type-safe navigation (recommended)
@Serializable
data class DetailRoute(val itemId: String)
// In NavHost
composable<DetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<DetailRoute>()
DetailScreen(itemId = route.itemId)
}Tránh gửi đối tượng lớn qua tham số điều hướng. Chỉ gửi identifier, sau đó tải dữ liệu đầy đủ trong ViewModel màn hình đích.
Hiệu Năng
15. Ngăn chặn recomposition không cần thiết như thế nào?
Recomposition quá mức làm giảm hiệu năng. Chiến lược chính: sử dụng stable types, lambda stabilization và Compose compiler reports để xác định vấn đề.
// Stable data class - Compose can skip recomposition
@Immutable
data class UserData(
val id: String,
val name: String,
val avatarUrl: String
)
// Lambda stabilization with remember
@Composable
fun ParentScreen(viewModel: MyViewModel = viewModel()) {
// Without remember: new lambda instance on every recomposition
// UserList(onItemClick = { id -> viewModel.selectItem(id) })
// With remember: stable lambda reference
val onItemClick = remember<(String) -> Unit> {
{ id -> viewModel.selectItem(id) }
}
UserList(onItemClick = onItemClick)
}
// Using key to control recomposition scope
@Composable
fun ItemList(items: List<Item>) {
LazyColumn {
items(items, key = { it.id }) { item ->
// Each item recomposes independently
key(item.id) {
ItemRow(item = item)
}
}
}
}Kích hoạt Compose compiler metrics (-P plugin:...metricsDestination) để xác định các composable không ổn định.
16. Profiling ứng dụng Compose như thế nào?
Android Studio cung cấp Layout Inspector hiển thị số lần recomposition theo thời gian thực. Compose cũng cung cấp modifier đặc biệt cho việc debug.
// Recomposition counter (debug only)
@Composable
fun DebugRecomposition(tag: String) {
if (BuildConfig.DEBUG) {
val recompositionCount = remember { mutableIntStateOf(0) }
SideEffect {
recompositionCount.intValue++
}
Log.d("Recomposition", "$tag: ${recompositionCount.intValue}")
}
}
// Performance tracing
@Composable
fun TracedScreen() {
trace("TracedScreen") {
// Content here is measured in system trace
HeavyContent()
}
}
// Baseline profiles for startup optimization
@ExperimentalBaselineProfilesApi
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generateProfile() {
rule.collect("com.example.app") {
startActivityAndWait()
// Navigate through critical user flows
}
}
}Sử dụng Layout Inspector trong Android Studio để theo dõi số lần recomposition. Composable có số lần cao mà không có thay đổi trực quan cho thấy vấn đề hiệu năng.
17. Tại sao thứ tự Modifier quan trọng?
Modifier trong Compose được áp dụng theo thứ tự từ ngoài vào trong. Thứ tự khác nhau tạo ra kết quả trực quan khác nhau. Lỗi thứ tự Modifier là nguồn bug UI phổ biến.
@Composable
fun ModifierOrderDemo() {
// Order 1: padding THEN background
// Background covers padded area
Box(
modifier = Modifier
.padding(16.dp)
.background(Color.Red)
.size(100.dp)
)
// Order 2: background THEN padding
// Background extends to full size, padding is inside
Box(
modifier = Modifier
.background(Color.Blue)
.padding(16.dp)
.size(100.dp)
)
// Clickable area depends on order
Box(
modifier = Modifier
.padding(16.dp) // Padding outside clickable
.clickable { } // Click area
.padding(8.dp) // Padding inside clickable
.background(Color.Green)
)
}Quy tắc thực tế: đọc chuỗi Modifier từ trên xuống dưới như các lớp từ ngoài vào trong. padding trước background thêm khoảng cách ngoài background, sau đó thêm khoảng cách bên trong.
Đặt clickable sau padding làm cho vùng padding không thể nhấn được. Để có vùng chạm lớn hơn, đặt clickable trước padding.
Kiến Trúc
18. Pattern ViewModel + UiState hoạt động như thế nào với Compose?
Pattern này tách biệt logic nghiệp vụ (ViewModel) khỏi phần trình bày (Composable). ViewModel cung cấp một StateFlow<UiState> duy nhất đại diện cho toàn bộ state màn hình.
// Sealed interface for complete state representation
sealed interface HomeUiState {
data object Loading : HomeUiState
data class Success(
val articles: List<Article>,
val isRefreshing: Boolean = false
) : HomeUiState
data class Error(val message: String) : HomeUiState
}
class HomeViewModel(
private val repository: ArticleRepository
) : ViewModel() {
val uiState: StateFlow<HomeUiState> = repository
.getArticles()
.map<List<Article>, HomeUiState> { HomeUiState.Success(it) }
.catch { emit(HomeUiState.Error(it.message ?: "Unknown error")) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = HomeUiState.Loading
)
fun refresh() {
viewModelScope.launch {
// Update state to show refreshing
repository.refresh()
}
}
}
// Composable consumes UiState
@Composable
fun HomeScreen(viewModel: HomeViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
when (val state = uiState) {
is HomeUiState.Loading -> LoadingIndicator()
is HomeUiState.Success -> ArticleList(
articles = state.articles,
onRefresh = viewModel::refresh
)
is HomeUiState.Error -> ErrorMessage(state.message)
}
}Sử dụng sealed interface cho UiState để Compose có thể thực hiện exhaustive when check. WhileSubscribed(5000) ngừng collection khi không có subscriber, tiết kiệm tài nguyên.
19. Kiểm thử composable như thế nào?
Compose cung cấp ComposeTestRule cho việc kiểm thử UI. Test được viết sử dụng semantic tree, không phải triển khai trực quan.
class LoginScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun loginButton_disabledWhenFieldsEmpty() {
composeTestRule.setContent {
LoginScreen()
}
// Find by test tag
composeTestRule
.onNodeWithTag("loginButton")
.assertIsNotEnabled()
}
@Test
fun loginButton_enabledWhenFieldsFilled() {
composeTestRule.setContent {
LoginScreen()
}
// Type in fields
composeTestRule
.onNodeWithTag("emailField")
.performTextInput("test@example.com")
composeTestRule
.onNodeWithTag("passwordField")
.performTextInput("password123")
// Verify button is enabled
composeTestRule
.onNodeWithTag("loginButton")
.assertIsEnabled()
}
@Test
fun errorMessage_displayedOnFailure() {
composeTestRule.setContent {
LoginScreen()
}
// Verify error message appears
composeTestRule
.onNodeWithText("Invalid credentials")
.assertExists()
}
}Sử dụng testTag trong Modifier để dễ dàng tìm node. Tránh kiểm thử dựa trên vị trí trực quan vì dễ bị hỏng khi thay đổi layout.
20. Tích hợp Compose với View XML hiện có như thế nào?
Di chuyển dần từ XML sang Compose được thực hiện thông qua ComposeView (Compose bên trong XML) và AndroidView (XML bên trong Compose). Chiến lược này cho phép áp dụng Compose mà không cần viết lại toàn bộ ứng dụng.
// Compose inside XML layout
class ExistingFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
AppTheme {
NewComposeFeature()
}
}
}
}
}
// XML View inside Compose
@Composable
fun LegacyMapView() {
AndroidView(
factory = { context ->
// Create traditional Android View
MapView(context).apply {
// Configure the view
onCreate(Bundle())
}
},
update = { mapView ->
// Update view when state changes
mapView.getMapAsync { map ->
map.moveCamera(CameraUpdateFactory.newLatLng(location))
}
},
onRelease = { mapView ->
// Cleanup when leaving composition
mapView.onDestroy()
}
)
}Chiến lược di chuyển được khuyến nghị: bắt đầu với màn hình mới sử dụng Compose, sau đó di chuyển dần các màn hình hiện có từ leaf composable lên trên.
Kết Luận
20 câu hỏi này bao gồm các khía cạnh then chốt của phỏng vấn Jetpack Compose: từ cơ bản khai báo đến các pattern kiến trúc sẵn sàng cho production. Chìa khóa thành công nằm ở sự hiểu biết sâu sắc về cơ chế recomposition, quản lý state đúng cách và kinh nghiệm thực tế xây dựng ứng dụng.
Checklist Chuẩn Bị
- Nắm vững sự khác biệt giữa Compose khai báo và XML mệnh lệnh
- Hiểu cơ chế recomposition và smart recomposition
- Luyện tập state hoisting và unidirectional data flow
- Triển khai side effect đúng cách (LaunchedEffect, DisposableEffect)
- Tối ưu LazyColumn với key và stable types
- Nắm vững pattern ViewModel + UiState với sealed interface
- Viết compose test sử dụng semantic tree
- Hiểu interop Compose-XML cho việc di chuyển dần
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ực hành trực tiếp với dự án thực tế vẫn là phương pháp hiệu quả nhất để làm chủ các khái niệm này. Mỗi câu hỏi được thảo luận trong hướng dẫn này đều đáng được khám phá sâu hơn với việc triển khai code thực tế.
Thẻ
Chia sẻ
Bài viết liên quan

MVVM vs MVI: Kiến Trúc Nào Phù Hợp Cho Android Năm 2026?
So sánh chuyên sâu MVVM và MVI trên Android: ưu điểm, nhược điểm, trường hợp sử dụng và hướng dẫn thực tiễn để lựa chọn kiến trúc phù hợp trong năm 2026.

Kotlin Coroutines cho Android: Huong dan day du 2026
Huong dan toan dien ve Kotlin coroutines trong phat trien Android: suspend functions, scopes, dispatchers va cac pattern nang cao.

Top 25 Câu Hỏi Phỏng Vấn Data Analytics Năm 2026
Hướng dẫn chi tiết 25 câu hỏi phỏng vấn Data Analyst thường gặp năm 2026, bao gồm SQL, Python, thống kê, visualization và behavioral questions kèm code examples.