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.

Pertanyaan wawancara Jetpack Compose untuk developer Android

Jetpack Compose telah menjadi toolkit UI standar untuk pengembangan Android. Wawancara teknis kini secara rutin menguji kemahiran Compose, dari mekanisme recomposition hingga state management dan optimasi performa. Berikut adalah 20 pertanyaan yang paling sering diajukan, dengan jawaban terperinci dan contoh kode.

Cara Menggunakan Panduan Ini

Setiap pertanyaan mencakup jawaban terstruktur dan contoh kode. Pertanyaan disusun berdasarkan tingkat kesulitan: dasar, menengah, kemudian lanjutan.

Dasar-Dasar Jetpack Compose

1. Apa perbedaan antara Compose dan sistem view XML?

Compose menggunakan paradigma deklaratif: UI dideskripsikan sebagai fungsi dari state, dan framework menangani pembaruan secara otomatis. Sistem XML tradisional bersifat imperatif, memerlukan manipulasi view secara manual melalui findViewById atau 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
    }
}

Dengan Compose, tidak perlu mencari referensi TextView dan memperbaruinya secara manual. Recomposition menangani semuanya.

2. Apa itu recomposition?

Recomposition adalah proses di mana Compose memanggil ulang fungsi @Composable ketika state-nya berubah. Hanya fungsi yang parameternya telah berubah yang dieksekusi ulang, yang mengoptimalkan performa.

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
}

Poin penting untuk wawancara: recomposition bersifat optimistis (Compose mengasumsikan dapat dibatalkan) dan tidak berurutan (urutan eksekusi composable tidak dijamin).

3. Apa fungsi remember?

remember mempertahankan nilai selama recomposition. Tanpa remember, setiap recomposition akan mengatur ulang variabel ke nilai awalnya.

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. Apa perbedaan antara remember dan rememberSaveable?

remember mempertahankan nilai selama recomposition tetapi kehilangannya saat configuration change (rotasi layar). rememberSaveable mempertahankan nilai melalui configuration change menggunakan mekanisme 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...") }
    )
}

State Management di Compose

5. Apa itu state hoisting?

State hoisting berarti memindahkan state dari composable ke induknya. Composable anak menjadi stateless: menerima state sebagai parameter dan memberitahukan perubahan melalui callback. Pola ini penting untuk membangun komponen yang dapat digunakan kembali dan dapat diuji.

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
    )
}

Pola ini fundamental dalam Compose dan sering muncul dalam wawancara.

6. Bagaimana cara kerja derivedStateOf?

derivedStateOf membuat state turunan yang hanya memicu recomposition ketika hasil perhitungan berubah, bukan pada setiap modifikasi sumber.

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) }
        }
    }
}
Kapan menggunakan derivedStateOf

Mekanisme ini berguna ketika state sering berubah tetapi hasil turunannya jarang berubah (misalnya, daftar yang difilter, tombol enabled/disabled berdasarkan validitas form).

7. Apa perbedaan antara StateFlow dan Compose State<T>?

StateFlow (Kotlin coroutines) adalah reactive stream dari ViewModel. State<T> adalah mekanisme native Compose untuk memicu recomposition. Dalam praktiknya, StateFlow dikumpulkan dalam composable melalui collectAsStateWithLifecycle(). Untuk pemahaman lebih dalam tentang coroutine flow, lihat panduan lengkap 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}")
}

Rekomendasi adalah menggunakan collectAsStateWithLifecycle() (bukan collectAsState()) karena menghormati lifecycle dan menghentikan collection ketika layar tidak lagi terlihat.

Side Effect dan Lifecycle

8. Apa saja side effect utama di Compose?

Side effect memungkinkan menjalankan kode non-composable (panggilan jaringan, logging, navigasi) secara terkontrol. Side effect utama meliputi LaunchedEffect, DisposableEffect, SideEffect, dan Keyed SideEffect baru yang diperkenalkan di 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
    }
}

Pembaruan Compose 1.12: Keyed SideEffect menyediakan overload baru yang mendukung argumen key untuk side effect sekali jalan setiap kali key tertentu berubah. Menurut benchmark resmi, ini bisa hingga 90% lebih cepat dari LaunchedEffect untuk operasi non-suspended.

9. Kapan menggunakan LaunchedEffect vs rememberCoroutineScope?

LaunchedEffect terikat pada composition: coroutine dibatalkan ketika composable meninggalkan composition atau ketika key berubah. rememberCoroutineScope menyediakan scope yang dikontrol pengguna, berguna untuk aksi yang dipicu pengguna (klik tombol).

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")
    }
}

Siap menguasai wawancara Android Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Layout dan Komponen Lanjutan

10. Bagaimana cara kerja LazyColumn dan bagaimana perbedaannya dengan RecyclerView?

LazyColumn adalah ekuivalen Compose dari RecyclerView. Ini hanya meng-compose elemen yang terlihat di layar dan mendaur ulang composable yang keluar dari jendela yang terlihat.

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)
        }
    }
}

Poin penting: selalu berikan parameter key yang stabil untuk menghindari recomposition yang tidak perlu saat mengurutkan atau menghapus elemen.

11. Bagaimana cara membuat custom layout?

Compose memungkinkan pembuatan custom layout melalui fungsi Layout. Ini menggantikan implementasi ViewGroup kustom dari sistem 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. Bagaimana cara mengimplementasikan theming kustom dengan MaterialTheme?

Theming di Compose bergantung pada CompositionLocal. MaterialTheme menyediakan nilai warna, tipografi, dan bentuk yang dapat diakses di seluruh 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
        )
    }
}

13. Bagaimana cara kerja Compose Navigation?

Compose Navigation menggunakan NavHost dengan route yang dideklarasikan sebagai tipe serializable (Navigation 2.8+). Sejak Navigation 2.9, library mendukung value class dan List<Enum> sebagai tipe argumen tanpa memerlukan implementasi NavType kustom.

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. Bagaimana cara mengirim data antar layar?

Argumen sederhana (String, Int) diteruskan langsung melalui route. Untuk objek kompleks, rekomendasi saat ini adalah menggunakan ViewModel bersama atau hanya mengirim identifier dan memuat data di layar tujuan. Navigation 2.9 memperkenalkan CollectionNavType<T> untuk argumen berbasis koleksi seperti list dan 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 yang harus dihindari

Jangan pernah mengirim objek kompleks yang di-serialize dalam route. Kirim ID dan biarkan layar tujuan memuat data melalui ViewModel. Untuk panduan arsitektur, lihat MVVM vs MVI: Arsitektur Mana yang Dipilih.

Performa dan Optimasi

15. Bagaimana cara mencegah recomposition yang tidak perlu?

Tiga strategi utama untuk meminimalkan recomposition yang tidak perlu:

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. Bagaimana cara melakukan profiling performa aplikasi Compose?

Layout Inspector Android Studio menampilkan jumlah recomposition per composable. Compose 1.13 alpha memperkenalkan RecompositionTracer untuk merekam alur invalidasi recomposition. Flag debugInspectorInfo juga membantu diagnostik.

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)
}

Selain itu, Compose Compiler Metrics menghasilkan laporan terperinci tentang fungsi skippable, restartable, dan kelas stable/unstable.

17. Apa itu Modifier dan mengapa penting?

Modifier adalah rantai instruksi yang terurut yang memodifikasi tampilan dan perilaku composable. Urutan modifier secara langsung memengaruhi 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)
    )
}

Praktik terbaik: selalu terima parameter modifier: Modifier = Modifier dalam composable yang dapat digunakan kembali untuk memungkinkan kustomisasi oleh induk.

Arsitektur dan Pola Lanjutan

18. Bagaimana cara menyusun layar Compose dengan ViewModel?

Pola yang direkomendasikan memisahkan state UI dalam data class, event dalam sealed interface, dan ViewModel menangani logika bisnis.

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. Bagaimana cara menguji composable?

Compose menyediakan library pengujian dengan ComposeTestRule untuk UI test dan asersi semantik. Sejak Compose 1.11, API testing v2 menjadi default, dengan v1 deprecated. Perubahan utama adalah peralihan dari UnconfinedTestDispatcher (eksekusi langsung) ke StandardTestDispatcher (eksekusi antrian), yang berarti test sekarang lebih mencerminkan perilaku 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()
}

Untuk unit testing composable stateless, menguji ViewModel secara terpisah dengan test JUnit/Turbine standar seringkali lebih efisien.

20. Bagaimana cara mengintegrasikan Compose ke aplikasi berbasis XML yang sudah ada?

Interoperabilitas bersifat dua arah: ComposeView menyematkan Compose dalam XML, dan AndroidView menggunakan view klasik dalam 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 */ } }
    )
}
Strategi migrasi yang direkomendasikan

Migrasikan layar demi layar, dimulai dengan layar yang paling sederhana. Setiap layar baru harus sepenuhnya dalam Compose, sementara layar yang ada bermigrasi secara bertahap.

Siap menguasai wawancara Android Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Sumber

Poin Penting untuk Wawancara Jetpack Compose

20 pertanyaan ini mencakup dasar-dasar yang harus dikuasai setiap developer Android untuk wawancara Jetpack Compose. Untuk latihan praktis, coba modul pertanyaan wawancara Jetpack Compose.

  • ✅ Pahami recomposition dan perilaku optimistisnya
  • ✅ Kuasai remember, rememberSaveable, dan derivedStateOf
  • ✅ Terapkan state hoisting secara sistematis
  • ✅ Ketahui side effect (LaunchedEffect, DisposableEffect, SideEffect, dan Keyed SideEffect baru)
  • ✅ Optimalkan performa (stable class, key, lambda)
  • ✅ Susun layar dengan ViewModel + UiState + Event
  • ✅ Uji composable dengan ComposeTestRule menggunakan API testing v2
  • ✅ Tangani interoperabilitas Compose/View

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di Android?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 20 Agustus 2026

Tag

#jetpack compose
#android
#interview
#kotlin
#ui

Bagikan

Artikel terkait