MVVM vs MVI: Arsitektur Mana yang Dipilih di 2026?
Perbandingan mendalam antara MVVM dan MVI di Android: kelebihan, keterbatasan, kasus penggunaan, dan panduan praktis memilih arsitektur. Diperbarui dengan explicit backing fields Kotlin 2.4.

Memilih arsitektur yang tepat adalah keputusan krusial yang berdampak pada kemampuan pemeliharaan, pengujian, dan skalabilitas aplikasi Android. Di tahun 2026, dua pola mendominasi ekosistem: MVVM, standar industri, dan MVI, pendekatan reaktif yang menjadi pilihan alami untuk Jetpack Compose.
Pilihan arsitektur yang buruk sangat mahal: utang teknis, bug yang sulit direproduksi, dan refaktorisasi yang menyakitkan. Memahami kekuatan dan kelemahan setiap pendekatan menghemat banyak masalah di kemudian hari.
Memahami MVVM: Standar yang Sudah Mapan
MVVM (Model-View-ViewModel) adalah arsitektur yang direkomendasikan Google sejak diperkenalkannya Jetpack. Arsitektur ini memisahkan tanggung jawab ke dalam tiga lapisan yang jelas, membuat kode lebih terorganisir dan mudah diuji.
Prinsip Inti MVVM
Pola MVVM mengandalkan pemisahan yang jelas: Model mengelola data dan logika bisnis, View menampilkan antarmuka, dan ViewModel menjembatani keduanya dengan menyediakan state yang dapat diamati.
Contoh pertama ini menunjukkan struktur dasar dengan ViewModel yang mengekspos state yang dapat diamati dan metode untuk interaksi pengguna. Perhatikan bagaimana explicit backing fields di Kotlin 2.4 menghilangkan pola boilerplate tradisional _uiState / uiState.
// MVVM ViewModel with Kotlin 2.4 explicit backing fields
// The backing field syntax eliminates the _state / state dance
class UserProfileViewModel(
private val userRepository: UserRepository,
private val analyticsTracker: AnalyticsTracker
) : ViewModel() {
// Kotlin 2.4: explicit backing field - no more _uiState / uiState pair
val uiState: StateFlow<UserProfileState>
field = MutableStateFlow(UserProfileState())
// Separate loading state - MVVM allows multiple flows
val isLoading: StateFlow<Boolean>
field = MutableStateFlow(false)
// One-shot error messages via SharedFlow
private val _errorMessage = MutableSharedFlow<String>()
val errorMessage: SharedFlow<String> = _errorMessage.asSharedFlow()
// Initial profile loading
fun loadProfile(userId: String) {
viewModelScope.launch {
isLoading.value = true
try {
// Repository call to fetch data
val user = userRepository.getUser(userId)
// Update state with new data
uiState.update { currentState ->
currentState.copy(
user = user,
isEditing = false
)
}
// Analytics tracking
analyticsTracker.trackProfileViewed(userId)
} catch (e: Exception) {
// Emit one-shot error message
_errorMessage.emit("Unable to load profile")
} finally {
isLoading.value = false
}
}
}
// Enable edit mode
fun enableEditMode() {
uiState.update { it.copy(isEditing = true) }
}
// Save profile changes
fun saveProfile(name: String, bio: String) {
viewModelScope.launch {
isLoading.value = true
try {
val updatedUser = userRepository.updateUser(
uiState.value.user?.id ?: return@launch,
name = name,
bio = bio
)
uiState.update {
it.copy(user = updatedUser, isEditing = false)
}
} catch (e: Exception) {
_errorMessage.emit("Failed to save profile")
} finally {
isLoading.value = false
}
}
}
}
// Data class representing the screen state
data class UserProfileState(
val user: User? = null,
val isEditing: Boolean = false
)ViewModel ini mengilustrasikan pendekatan MVVM yang khas: beberapa flow yang dapat diamati (state utama, loading, error) dan metode publik untuk setiap aksi pengguna. Sintaks explicit backing fields di Kotlin 2.4 membuat kode lebih bersih dengan mendeklarasikan tipe properti sebagai StateFlow sementara backing field-nya adalah MutableStateFlow.
Keunggulan MVVM
MVVM memiliki beberapa kekuatan yang menjelaskan adopsinya yang masif:
- Keakraban: Sebagian besar pengembang Android mengenal pola ini
- Fleksibilitas: State dapat distrukturkan sesuai keinginan
- Ekosistem: Integrasi sempurna dengan Jetpack (LiveData, StateFlow, Hilt)
- Kesederhanaan: Kurva pembelajaran yang landai untuk pemula
MVVM sangat cocok untuk tim campuran dengan pengembang berbagai tingkat keahlian. Kesederhanaan konseptualnya memudahkan proses onboarding.
Keterbatasan MVVM
Namun MVVM menunjukkan keterbatasannya seiring pertumbuhan aplikasi. Masalah utama adalah pengelolaan state yang terdistribusi. Contoh berikut mengilustrasikan masalah umum state yang terfragmentasi:
// Example MVVM ViewModel with fragmented state
// This pattern becomes problematic as the screen grows in complexity
class CheckoutViewModel : ViewModel() {
// Problem: state scattered across multiple flows
val cart: StateFlow<List<CartItem>>
field = MutableStateFlow(emptyList())
val selectedAddress: StateFlow<Address?>
field = MutableStateFlow(null)
val selectedPayment: StateFlow<PaymentMethod?>
field = MutableStateFlow(null)
val promoCode: StateFlow<String?>
field = MutableStateFlow(null)
val isLoading: StateFlow<Boolean>
field = MutableStateFlow(false)
val error: StateFlow<String?>
field = MutableStateFlow(null)
// Each modification can create temporary inconsistent states
fun applyPromoCode(code: String) {
viewModelScope.launch {
isLoading.value = true
error.value = null
try {
val discount = promoRepository.validate(code)
promoCode.value = code
// Cart state also needs updating...
// but there's a delay between the two updates
recalculateCart()
} catch (e: Exception) {
error.value = e.message
promoCode.value = null
} finally {
isLoading.value = false
}
}
}
// Hard to guarantee consistency across all these states
private fun recalculateCart() {
// Complex logic depending on multiple states...
}
}Contoh ini menunjukkan bagaimana state bisa terfragmentasi di MVVM, mempersulit pelacakan transisi dan reproduksi bug.
Memahami MVI: Pendekatan Satu Arah
MVI (Model-View-Intent) mengadopsi filosofi yang berbeda: aliran data satu arah dan satu state yang tidak dapat diubah. Pendekatan yang terinspirasi dari Redux ini menghilangkan masalah state yang tidak konsisten.
Prinsip Inti MVI
Dalam MVI, segalanya mengikuti siklus yang jelas: pengguna mengirimkan Intent (aksi), Reducer mengubah state saat ini menjadi state baru, dan View menampilkan satu state tersebut. Ini dapat diprediksi, diuji, dan di-debug.
Implementasi layar profil yang sama ini menunjukkan bagaimana state dipusatkan dan aksi diketik secara eksplisit:
// MVI ViewModel for the same user profile screen
// Note the structure: Intent -> Reducer -> Single State
class UserProfileMviViewModel(
private val userRepository: UserRepository,
private val analyticsTracker: AnalyticsTracker
) : ViewModel() {
// Single, immutable state - the absolute source of truth
val state: StateFlow<UserProfileState>
field = MutableStateFlow(UserProfileState())
// Channel for side effects (navigation, snackbar)
private val _sideEffect = Channel<UserProfileSideEffect>()
val sideEffect: Flow<UserProfileSideEffect> = _sideEffect.receiveAsFlow()
// Single entry point for all user actions
fun onIntent(intent: UserProfileIntent) {
when (intent) {
is UserProfileIntent.LoadProfile -> loadProfile(intent.userId)
is UserProfileIntent.EnableEditMode -> enableEditMode()
is UserProfileIntent.SaveProfile -> saveProfile(intent.name, intent.bio)
is UserProfileIntent.CancelEdit -> cancelEdit()
}
}
private fun loadProfile(userId: String) {
viewModelScope.launch {
// Transition to loading state
state.update { it.copy(isLoading = true, error = null) }
try {
val user = userRepository.getUser(userId)
// Single atomic state update
state.update {
it.copy(
user = user,
isLoading = false,
error = null
)
}
analyticsTracker.trackProfileViewed(userId)
} catch (e: Exception) {
// Error state is part of the main state
state.update {
it.copy(
isLoading = false,
error = "Unable to load profile"
)
}
}
}
}
private fun enableEditMode() {
// Simple, predictable update
state.update { it.copy(isEditing = true) }
}
private fun saveProfile(name: String, bio: String) {
viewModelScope.launch {
val currentUser = state.value.user ?: return@launch
state.update { it.copy(isLoading = true) }
try {
val updatedUser = userRepository.updateUser(
currentUser.id,
name = name,
bio = bio
)
state.update {
it.copy(
user = updatedUser,
isEditing = false,
isLoading = false
)
}
// Side effect to notify the user
_sideEffect.send(UserProfileSideEffect.ShowSuccess("Profile updated"))
} catch (e: Exception) {
state.update {
it.copy(isLoading = false, error = "Failed to save profile")
}
}
}
}
private fun cancelEdit() {
state.update { it.copy(isEditing = false) }
}
}
// All possible actions, explicitly typed
sealed class UserProfileIntent {
data class LoadProfile(val userId: String) : UserProfileIntent()
data object EnableEditMode : UserProfileIntent()
data class SaveProfile(val name: String, val bio: String) : UserProfileIntent()
data object CancelEdit : UserProfileIntent()
}
// Single, complete screen state
data class UserProfileState(
val user: User? = null,
val isLoading: Boolean = false,
val isEditing: Boolean = false,
val error: String? = null
)
// One-shot side effects
sealed class UserProfileSideEffect {
data class ShowSuccess(val message: String) : UserProfileSideEffect()
data class NavigateTo(val destination: String) : UserProfileSideEffect()
}Perbedaannya jelas: satu aliran state, aksi yang eksplisit, dan pemisahan bersih antara state persisten dan efek sekali pakai.
Dengan MVI, setiap Intent dan setiap transisi state dapat dicatat. Mereproduksi bug menjadi sepele: cukup putar ulang urutan Intent.
MVI dengan Jetpack Compose
MVI bersinar secara khusus dengan Jetpack Compose, karena keduanya berbagi filosofi yang sama: state yang tidak dapat diubah dan antarmuka deklaratif. Berikut cara menghubungkan ViewModel ke layar Compose:
// Compose screen consuming MVI state
// The connection between ViewModel and UI is elegant and reactive
@Composable
fun UserProfileScreen(
viewModel: UserProfileMviViewModel = hiltViewModel(),
onNavigateBack: () -> Unit
) {
// Collect the single state
val state by viewModel.state.collectAsStateWithLifecycle()
// Handle side effects
LaunchedEffect(Unit) {
viewModel.sideEffect.collect { effect ->
when (effect) {
is UserProfileSideEffect.ShowSuccess -> {
// Show snackbar
}
is UserProfileSideEffect.NavigateTo -> {
// Navigate
}
}
}
}
// Purely declarative UI based on state
UserProfileContent(
state = state,
onIntent = viewModel::onIntent
)
}
@Composable
private fun UserProfileContent(
state: UserProfileState,
onIntent: (UserProfileIntent) -> Unit
) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Conditional rendering based on the single state
when {
state.isLoading -> {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
state.error != null -> {
ErrorMessage(
message = state.error,
onRetry = {
state.user?.id?.let {
onIntent(UserProfileIntent.LoadProfile(it))
}
}
)
}
state.user != null -> {
ProfileCard(
user = state.user,
isEditing = state.isEditing,
onEditClick = { onIntent(UserProfileIntent.EnableEditMode) },
onSaveClick = { name, bio ->
onIntent(UserProfileIntent.SaveProfile(name, bio))
},
onCancelClick = { onIntent(UserProfileIntent.CancelEdit) }
)
}
}
}
}Antarmuka menjadi fungsi murni dari state: dapat diprediksi, diuji, dan tanpa efek samping tersembunyi.
Perbandingan Mendetail
Setelah kedua pola jelas, berikut perbandingan berdasarkan kriteria yang benar-benar penting di lingkungan produksi.
Manajemen State
Perbedaan mendasar terletak pada manajemen state. Perbedaan ini berdampak langsung pada kemampuan pemeliharaan jangka panjang.
// MVVM: potentially fragmented state
class MvvmViewModel : ViewModel() {
// Multiple sources of truth - manual synchronization needed
val users: StateFlow<List<User>>
field = MutableStateFlow(emptyList())
val selectedUser: StateFlow<User?>
field = MutableStateFlow(null)
val isLoading: StateFlow<Boolean>
field = MutableStateFlow(false)
val searchQuery: StateFlow<String>
field = MutableStateFlow("")
// What happens if selectedUser points to a user
// that's no longer in users after a refresh?
// -> Inconsistent state that's hard to detect
}
// MVI: consistent state by construction
class MviViewModel : ViewModel() {
// Single source of truth - inconsistencies are impossible
val state: StateFlow<UsersState>
field = MutableStateFlow(UsersState())
data class UsersState(
val users: List<User> = emptyList(),
val selectedUser: User? = null, // Always consistent with users
val isLoading: Boolean = false,
val searchQuery: String = ""
)
// Each update automatically maintains invariants
private fun selectUser(userId: String) {
state.update { currentState ->
currentState.copy(
selectedUser = currentState.users.find { it.id == userId }
)
}
}
}Dalam MVVM, state yang tidak konsisten sering muncul sebagai bug intermiten yang sulit direproduksi. Dalam MVI, jika state tidak valid, maka tidak valid secara deterministik.
Kemampuan Pengujian Arsitektur
Kedua arsitektur dapat diuji, tetapi MVI menawarkan keunggulan signifikan melalui kemampuan prediksinya.
// MVVM test: requires verifying multiple flows
@Test
fun `loadUsers should update state correctly`() = runTest {
val viewModel = MvvmViewModel(fakeRepository)
// Observe multiple flows simultaneously
val users = mutableListOf<List<User>>()
val loadingStates = mutableListOf<Boolean>()
val job1 = launch { viewModel.users.toList(users) }
val job2 = launch { viewModel.isLoading.toList(loadingStates) }
viewModel.loadUsers()
advanceUntilIdle()
// Assertions on different flows
assertThat(users.last()).isEqualTo(expectedUsers)
assertThat(loadingStates).containsExactly(false, true, false)
job1.cancel()
job2.cancel()
}
// MVI test: single flow to verify, clear state sequence
@Test
fun `LoadUsers intent should produce correct state sequence`() = runTest {
val viewModel = MviViewModel(fakeRepository)
// Collect all states in order
val states = mutableListOf<UsersState>()
val job = launch { viewModel.state.toList(states) }
// Send the intent
viewModel.onIntent(UsersIntent.LoadUsers)
advanceUntilIdle()
// Verify the exact state sequence
assertThat(states).containsExactly(
UsersState(), // Initial
UsersState(isLoading = true), // Loading
UsersState(users = expectedUsers, isLoading = false) // Success
)
job.cancel()
}MVI memungkinkan pengujian urutan transisi state yang tepat, yang sangat berguna untuk layar kompleks dengan banyak interaksi.
Kompleksitas dan Boilerplate
Sejak Kotlin 2.4, perbedaan boilerplate antara MVVM dan MVI telah menyempit secara signifikan. Fitur explicit backing fields menghilangkan pola _state / state yang sebelumnya menambah baris di setiap ViewModel.
// MVVM: quick start, less code
class SimpleViewModel : ViewModel() {
val name: StateFlow<String>
field = MutableStateFlow("")
fun updateName(newName: String) {
name.value = newName
}
}
// Total: ~8 lines
// MVI: more structure, more code
class SimpleMviViewModel : ViewModel() {
val state: StateFlow<SimpleState>
field = MutableStateFlow(SimpleState())
fun onIntent(intent: SimpleIntent) {
when (intent) {
is SimpleIntent.UpdateName -> {
state.update { it.copy(name = intent.name) }
}
}
}
}
data class SimpleState(val name: String = "")
sealed class SimpleIntent {
data class UpdateName(val name: String) : SimpleIntent()
}
// Total: ~18 linesUntuk layar sederhana, MVI mungkin terasa berlebihan. Tetapi struktur ini memberikan manfaat seiring bertambahnya kompleksitas layar.
Siap menguasai wawancara Android Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Kapan Memilih MVVM?
MVVM tetap menjadi pilihan pragmatis dalam beberapa situasi:
Proyek yang Sudah Ada
Jika aplikasi sudah menggunakan MVVM, migrasi ke MVI membutuhkan upaya yang besar. Memperbaiki struktur MVVM yang ada sering kali merupakan keputusan yang lebih bijak.
Tim Junior atau Campuran
MVVM lebih mudah diakses. Tim dengan pengembang pemula akan lebih cepat produktif dengan MVVM daripada MVI.
Layar Sederhana
Untuk layar dengan sedikit state dan interaksi, MVI menambah kompleksitas tanpa manfaat yang proporsional.
// For a simple settings screen, MVVM is plenty
class SettingsViewModel(
private val preferencesRepository: PreferencesRepository
) : ViewModel() {
val darkMode = preferencesRepository.darkModeFlow
.stateIn(viewModelScope, SharingStarted.Lazily, false)
val notificationsEnabled = preferencesRepository.notificationsFlow
.stateIn(viewModelScope, SharingStarted.Lazily, true)
fun toggleDarkMode() {
viewModelScope.launch {
preferencesRepository.setDarkMode(!darkMode.value)
}
}
fun toggleNotifications() {
viewModelScope.launch {
preferencesRepository.setNotifications(!notificationsEnabled.value)
}
}
}Kapan Memilih MVI?
MVI membuktikan nilainya dalam konteks tertentu:
Aplikasi dengan State yang Kompleks
Ketika layar memiliki banyak state yang saling bergantung, MVI menjamin konsistensi.
// Checkout screen with complex state: MVI excels
data class CheckoutState(
val cartItems: List<CartItem> = emptyList(),
val selectedAddress: Address? = null,
val selectedPayment: PaymentMethod? = null,
val promoCode: PromoCode? = null,
val deliveryOptions: List<DeliveryOption> = emptyList(),
val selectedDelivery: DeliveryOption? = null,
val subtotal: Money = Money.ZERO,
val discount: Money = Money.ZERO,
val deliveryFee: Money = Money.ZERO,
val total: Money = Money.ZERO,
val isLoading: Boolean = false,
val error: CheckoutError? = null,
val step: CheckoutStep = CheckoutStep.CART
) {
// Verifiable invariants
init {
require(total == subtotal - discount + deliveryFee) {
"Total inconsistent with components"
}
}
}
sealed class CheckoutIntent {
data class AddItem(val item: CartItem) : CheckoutIntent()
data class RemoveItem(val itemId: String) : CheckoutIntent()
data class SelectAddress(val address: Address) : CheckoutIntent()
data class SelectPayment(val method: PaymentMethod) : CheckoutIntent()
data class ApplyPromo(val code: String) : CheckoutIntent()
data object RemovePromo : CheckoutIntent()
data class SelectDelivery(val option: DeliveryOption) : CheckoutIntent()
data object ProceedToPayment : CheckoutIntent()
data object ConfirmOrder : CheckoutIntent()
}Aplikasi Real-time
Untuk aplikasi dengan WebSocket, notifikasi push, atau sinkronisasi real-time, MVI mengelola beberapa aliran data dengan elegan.
Persyaratan Debugging yang Ketat
Di domain yang diregulasi (fintech, kesehatan), kemampuan untuk mereproduksi urutan kejadian secara tepat sangat berharga.
MVI memudahkan implementasi "time-travel debugging": merekam semua state dan memutar ulang sesi pengguna.
Alternatif yang Muncul: Circuit
Circuit, yang dikembangkan oleh Slack, menawarkan implementasi MVI native Compose yang patut dipertimbangkan untuk proyek baru. Circuit menggabungkan presenter dengan Molecule di balik layar, menghilangkan banyak boilerplate MVI sambil mempertahankan aliran data satu arah.
Circuit sangat menarik untuk proyek Kotlin Multiplatform karena mendukung berbagi logika presentasi lintas platform. Namun, adopsi di luar Slack masih terbatas dibandingkan pendekatan standar Jetpack ViewModel.
Pendekatan Hibrida: Yang Terbaik dari Dua Dunia
Dalam praktiknya, banyak tim mengadopsi pendekatan hibrida: MVI untuk layar yang kompleks, MVVM yang disederhanakan untuk layar yang sederhana. Berikut pola yang direkomendasikan:
// Base ViewModel with lightweight MVI structure
// Reusable for all screens
abstract class MviViewModel<S, I>(initialState: S) : ViewModel() {
val state: StateFlow<S>
field = MutableStateFlow(initialState)
protected val currentState: S get() = state.value
// Single entry point for intents
abstract fun onIntent(intent: I)
// Helper to update state
protected fun updateState(reducer: S.() -> S) {
state.update { it.reducer() }
}
}
// Concrete implementation stays simple
class ProfileViewModel(
private val userRepository: UserRepository
) : MviViewModel<ProfileState, ProfileIntent>(ProfileState()) {
override fun onIntent(intent: ProfileIntent) {
when (intent) {
is ProfileIntent.Load -> load(intent.userId)
is ProfileIntent.Refresh -> refresh()
is ProfileIntent.ToggleFavorite -> toggleFavorite()
}
}
private fun load(userId: String) {
viewModelScope.launch {
updateState { copy(isLoading = true) }
val user = userRepository.getUser(userId)
updateState {
copy(user = user, isLoading = false)
}
}
}
private fun refresh() = load(currentState.user?.id ?: return)
private fun toggleFavorite() {
updateState {
copy(user = user?.copy(isFavorite = !user.isFavorite))
}
}
}Pendekatan ini menawarkan manfaat MVI (state tunggal, intent yang diketik) tanpa boilerplate yang berlebihan.
Rekomendasi
Berikut rekomendasi untuk memilih antara dua arsitektur berdasarkan konteks:
Untuk Proyek Baru dengan Compose
Adopsi MVI dari awal. Compose dan MVI berbagi filosofi yang sama, dan investasi awal terbayar dengan cepat. Dengan explicit backing fields di Kotlin 2.4, perbedaan boilerplate telah menyusut.
Untuk Proyek Berbasis View yang Sudah Ada
Tetap dengan MVVM, tetapi secara bertahap adopsi praktik terbaik MVI: state tunggal di ViewModel, aksi yang diketik dengan sealed class.
Untuk Tim yang Besar
Standardisasi pada satu pendekatan dan dokumentasikan. Konsistensi di seluruh kode lebih penting daripada pilihan pola itu sendiri.
Pola terbaik adalah yang dipahami dan diterapkan dengan benar oleh tim. MVVM yang diimplementasikan dengan baik mengalahkan MVI yang tidak dipahami dengan baik.
Sumber
- Kotlin 2.4.0 What's New - Explicit backing fields kini stabil, menghilangkan boilerplate
_state / state - Jetpack Compose August 2026 Release - Compose 1.12 dengan BOM 2026.08.00
- Circuit by Slack - Arsitektur MVI berbasis Compose dengan integrasi Molecule
- Android Architecture Guide - Rekomendasi arsitektur berlapis resmi dari Google
Kesimpulan
MVVM dan MVI keduanya merupakan pendekatan yang valid untuk mengarsitektur aplikasi Android. MVVM menawarkan kesederhanaan dan keakraban, sementara MVI membawa prediktabilitas dan debugging yang lebih mudah.
Daftar Periksa Keputusan
- Pilih MVVM jika: tim junior, proyek sederhana, migrasi mahal
- Pilih MVI jika: Compose native, state kompleks, debugging kritis
- Pendekatan hibrida direkomendasikan: MVI ringan dengan state tunggal, tanpa over-engineering
- Prioritas tertinggi: konsistensi di seluruh kode
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Apapun pilihannya, kunci utama adalah memahami kekuatan dan kelemahan setiap pendekatan untuk membuat keputusan yang tepat. Kode terbaik adalah kode yang dapat dipelihara oleh tim dengan tenang dalam jangka panjang.
Bisakah kamu menemukan bug di Android?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 19 Agustus 2026
Tag
Bagikan
Artikel terkait

Jetpack Compose: Animasi Lanjutan Langkah demi Langkah
Panduan lengkap animasi lanjutan di Compose: transisi, AnimatedVisibility, Animatable, gestur, dan performa untuk antarmuka Android yang halus.

20 Pertanyaan Wawancara Jetpack Compose Teratas di Tahun 2026
20 pertanyaan wawancara Jetpack Compose yang paling sering ditanyakan: recomposition, state management, navigation, performa, dan pola arsitektur.

Android 16 Tahun 2026: API Terbaru, Desktop Mode, dan Pertanyaan Wawancara Developer
Pembahasan mendalam tentang Android 16 (API 36) mencakup edge-to-edge enforcement, Desktop Mode, ProgressStyle Notifications, Predictive Back, serta pertanyaan wawancara teknis untuk developer Android tahun 2026.