Le 20 domande più frequenti su Jetpack Compose nei colloqui 2026
Le 20 domande più frequenti su Jetpack Compose nei colloqui tecnici: recomposition, gestione dello stato, navigazione, performance e pattern architetturali.

Jetpack Compose si è affermato come toolkit UI standard per lo sviluppo Android. I colloqui tecnici verificano ormai sistematicamente le competenze su Compose, dai meccanismi di recomposition alla gestione dello stato fino all'ottimizzazione delle performance. Ecco le 20 domande più frequenti con risposte dettagliate ed esempi di codice.
Ogni domanda include una risposta strutturata e un esempio di codice. Le domande sono organizzate per difficoltà crescente: fondamenti, livello intermedio, poi avanzato.
Fondamenti di Jetpack Compose
1. Qual è la differenza tra Compose e il sistema di viste XML?
Compose utilizza un paradigma dichiarativo: l'interfaccia viene descritta come funzione dello stato e il framework gestisce gli aggiornamenti automaticamente. Il sistema XML tradizionale è imperativo e richiede la manipolazione manuale delle viste tramite findViewById o 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
}
}Con Compose non serve cercare un riferimento a TextView e aggiornarlo manualmente. La recomposition gestisce tutto.
2. Che cos'è la recomposition?
La recomposition è il processo attraverso il quale Compose richiama le funzioni @Composable quando il loro stato cambia. Solo le funzioni i cui parametri sono cambiati vengono rieseguite, ottimizzando le performance.
@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
}Punto chiave per i colloqui: la recomposition è ottimistica (Compose presume che possa essere annullata) e non ordinata (l'ordine di esecuzione dei composable non è garantito).
3. Cosa fa remember?
remember preserva un valore attraverso le recomposition. Senza remember, ogni recomposition ripristinerebbe la variabile al suo valore iniziale.
@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. Qual è la differenza tra remember e rememberSaveable?
remember preserva i valori attraverso le recomposition ma li perde durante i cambiamenti di configurazione (rotazione dello schermo). rememberSaveable persiste i valori anche attraverso i cambiamenti di configurazione utilizzando il meccanismo 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...") }
)
}Gestione dello Stato in Compose
5. Che cos'è lo state hoisting?
Lo state hoisting consiste nello spostare lo stato da un composable al suo genitore. Il composable figlio diventa stateless: riceve lo stato come parametri e notifica le modifiche tramite callback. Questo pattern è essenziale per costruire componenti riutilizzabili e testabili.
// ✅ 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
)
}Questo pattern è fondamentale in Compose e viene chiesto regolarmente nei colloqui.
6. Come funziona derivedStateOf?
derivedStateOf crea uno stato derivato che attiva la recomposition solo quando il risultato del calcolo cambia effettivamente, non ad ogni modifica della sorgente.
@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) }
}
}
}Questo meccanismo è utile quando uno stato cambia frequentemente ma il risultato derivato cambia raramente (es. una lista filtrata, un pulsante abilitato/disabilitato in base alla validità del form).
7. Qual è la differenza tra StateFlow e State<T> di Compose?
StateFlow (Kotlin Coroutines) è uno stream reattivo proveniente dal ViewModel. State<T> è il meccanismo nativo di Compose per attivare la recomposition. Nella pratica, StateFlow viene raccolto in un composable tramite collectAsStateWithLifecycle(). Per una comprensione più approfondita dei coroutine flow, consultare la guida completa alle 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}")
}La raccomandazione è utilizzare collectAsStateWithLifecycle() (invece di collectAsState()) perché rispetta il lifecycle e interrompe la raccolta quando lo schermo non è più visibile.
Side Effects e Lifecycle
8. Quali sono i principali side effects in Compose?
I side effects permettono di eseguire codice non-composable (chiamate di rete, logging, navigazione) in modo controllato. I principali includono LaunchedEffect, DisposableEffect, SideEffect e il nuovo Keyed SideEffect introdotto in 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
}
}Aggiornamento Compose 1.12: Keyed SideEffect fornisce un nuovo overload con argomenti key per effetti collaterali una tantum quando specifiche chiavi cambiano. Secondo i benchmark ufficiali, può essere fino al 90% più veloce di LaunchedEffect per operazioni non sospese.
9. Quando usare LaunchedEffect vs rememberCoroutineScope?
LaunchedEffect è legato alla composition: la coroutine viene annullata quando il composable esce dalla composition o quando la chiave cambia. rememberCoroutineScope fornisce uno scope controllato dall'utente, utile per azioni attivate dall'utente (click su pulsanti).
@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")
}
}Pronto a superare i tuoi colloqui su Android?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Layout e componenti avanzati
10. Come funziona LazyColumn e in cosa differisce da RecyclerView?
LazyColumn è l'equivalente Compose di RecyclerView. Compone solo gli elementi visibili sullo schermo e ricicla i composable che escono dalla finestra visibile.
@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)
}
}
}Punto importante: fornire sempre un parametro key stabile per evitare recomposition inutili durante l'ordinamento o la rimozione di elementi.
11. Come creare un layout personalizzato?
Compose consente di creare layout personalizzati tramite la funzione Layout. Questo sostituisce le implementazioni personalizzate di ViewGroup del sistema di viste.
@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. Come implementare un theming personalizzato con MaterialTheme?
Il theming in Compose si basa su CompositionLocal. MaterialTheme fornisce valori di colori, tipografia e forme accessibili in tutto l'albero dei composable.
// 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
)
}
}Navigazione in Compose
13. Come funziona la navigazione in Compose?
Compose Navigation utilizza un NavHost con rotte dichiarate come tipi serializzabili (Navigation 2.8+). A partire da Navigation 2.9, la libreria supporta value class e List<Enum> come tipi di argomento senza richiedere implementazioni NavType personalizzate.
@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. Come passare dati tra schermate?
Gli argomenti semplici (String, Int) vengono passati direttamente tramite la rotta. Per oggetti complessi, la raccomandazione attuale è utilizzare un ViewModel condiviso o passare solo un identificatore e caricare i dati nella schermata di destinazione. Navigation 2.9 ha introdotto CollectionNavType<T> per argomenti basati su collection come liste e 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"))Non passare mai oggetti complessi serializzati nella rotta. Passare un ID e lasciare che la schermata di destinazione carichi i dati tramite il ViewModel. Per indicazioni sull'architettura, consultare MVVM vs MVI: Quale Architettura Scegliere.
Performance e ottimizzazione
15. Come prevenire recomposition inutili?
Tre strategie principali per minimizzare le recomposition non necessarie:
// 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. Come profilare le performance di un'app Compose?
Il Layout Inspector di Android Studio mostra i contatori di recomposition per composable. Compose 1.13 alpha introduce RecompositionTracer per registrare i flussi di invalidazione delle recomposition. Il flag debugInspectorInfo aiuta anche nella diagnostica.
// 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)
}Inoltre, le Compose Compiler Metrics generano un report dettagliato delle funzioni skippable, restartable e delle classi stabili/instabili.
17. Cos'è il Modifier e perché è importante?
Modifier è una catena ordinata di istruzioni che modifica l'aspetto e il comportamento di un composable. L'ordine dei modifier influisce direttamente sul 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)
)
}Best practice: accettare sempre un parametro modifier: Modifier = Modifier nei composable riutilizzabili per consentire la personalizzazione da parte del genitore.
Architettura e pattern avanzati
18. Come strutturare una schermata Compose con un ViewModel?
Il pattern consigliato separa lo stato UI in una data class, gli eventi in un sealed interface, e il ViewModel gestisce la logica di business.
// 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. Come testare i composable?
Compose fornisce una libreria di testing con ComposeTestRule per test UI e asserzioni semantiche. A partire da Compose 1.11, le API di testing v2 sono lo standard, con v1 deprecato. La modifica chiave è il passaggio da UnconfinedTestDispatcher (esecuzione immediata) a StandardTestDispatcher (esecuzione in coda), il che significa che i test rispecchiano meglio il comportamento in produzione.
@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()
}Per il testing unitario di composable stateless, testare il ViewModel separatamente con test JUnit/Turbine standard risulta spesso più efficiente.
20. Come integrare Compose in un'app XML esistente?
L'interoperabilità è bidirezionale: ComposeView incorpora Compose in XML, e AndroidView utilizza viste classiche all'interno di 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 */ } }
)
}Migrare schermata per schermata, partendo dalle più semplici. Ogni nuova schermata dovrebbe essere interamente in Compose, mentre le schermate esistenti vengono migrate progressivamente.
Pronto a superare i tuoi colloqui su Android?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Sources
- What's new in the Jetpack Compose August '26 release - Compose 1.12 release con Keyed SideEffect, BOM 2026.08.00
- What's new in the Jetpack Compose April '26 release - Testing v2 APIs, shared element debugging
- Navigation releases - Navigation 2.9 con value class e supporto CollectionNavType
- Side effects in Compose - Documentazione ufficiale sui side effects
Checklist per i colloqui Jetpack Compose
Queste 20 domande coprono i fondamenti che ogni sviluppatore Android deve padroneggiare per un colloquio su Jetpack Compose. Per esercitarsi, utilizzare il modulo domande Jetpack Compose.
- ✅ Comprendere la recomposition e il suo comportamento ottimistico
- ✅ Padroneggiare
remember,rememberSaveableederivedStateOf - ✅ Applicare sistematicamente lo state hoisting
- ✅ Conoscere i side effects (
LaunchedEffect,DisposableEffect,SideEffecte il nuovo Keyed SideEffect) - ✅ Ottimizzare le performance (classi stabili, key, lambda)
- ✅ Strutturare le schermate con ViewModel + UiState + Events
- ✅ Testare i composable con
ComposeTestRulee le API di testing v2 - ✅ Gestire l'interoperabilità Compose/Views
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Sapresti trovare il bug in Android?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 20 agosto 2026
Tag
Condividi
Articoli correlati

Jetpack Compose: Animazioni Avanzate Passo dopo Passo
Guida completa alle animazioni avanzate in Compose: transizioni, AnimatedVisibility, Animatable, gesture e prestazioni per interfacce Android fluide.

Jetpack Navigation Compose nel 2026: Navigazione Type-Safe e Domande da Colloquio
Una guida completa alla navigazione type-safe con Jetpack Compose. Esempi pratici, best practice e domande frequenti nei colloqui tecnici per sviluppatori Android.

Kotlin Flow vs StateFlow vs SharedFlow: domande da colloquio Android nel 2026
Le domande su Kotlin Flow vs StateFlow vs SharedFlow che gli intervistatori Android pongono nel 2026, con risposte chiare, una tabella comparativa e codice pronto per la produzione.