# Top 20 questions d'entretien Jetpack Compose en 2026 > Les 20 questions les plus posées en entretien sur Jetpack Compose : recomposition, state, navigation, performance et architecture. - Published: 2026-01-27 - Updated: 2026-03-28 - Author: SharpSkill - Tags: jetpack compose, android, interview, kotlin, ui - Reading time: 14 min --- Jetpack Compose s'est imposé comme le toolkit UI standard pour Android. En entretien technique, les recruteurs testent désormais systématiquement la maîtrise de Compose, de la recomposition au state management en passant par la performance. Voici les 20 questions les plus fréquentes, avec des réponses détaillées et des exemples de code. > **Comment utiliser ce guide** > > Chaque question est accompagnée d'une réponse structurée et d'un exemple de code. Les questions sont classées par difficulté croissante : fondamentaux, intermédiaire, puis avancé. ## Fondamentaux de Jetpack Compose ### 1. Quelle est la différence entre Compose et le système de vues XML ? Compose adopte un paradigme **déclaratif** : l'UI est décrite en fonction de l'état, et le framework se charge de la mettre à jour automatiquement. Le système XML traditionnel est **impératif** — il faut manipuler les vues manuellement via `findViewById` ou View Binding. ```kotlin // DeclarativeExample.kt // Compose : l'UI se met à jour automatiquement quand count change @Composable fun Counter() { var count by remember { mutableStateOf(0) } // État réactif Button(onClick = { count++ }) { // Déclaration de l'UI Text("Clics : $count") // Recomposé automatiquement } } ``` En Compose, pas besoin de chercher une référence à un `TextView` pour le mettre à jour : la recomposition s'en charge. ### 2. Qu'est-ce que la recomposition ? La recomposition est le processus par lequel Compose rappelle les fonctions `@Composable` lorsque leur état change. Seules les fonctions dont les paramètres ont changé sont réexécutées, ce qui optimise les performances. ```kotlin // RecompositionExample.kt @Composable fun UserCard(name: String, age: Int) { Column { Text("Nom : $name") // Recomposé uniquement si name change Text("Âge : $age") // Recomposé uniquement si age change StaticBadge() // Non recomposé si ses inputs ne changent pas } } @Composable fun StaticBadge() { Text("Badge statique") // Compose sait que cette fonction est stable } ``` Point clé à mentionner en entretien : la recomposition est **optimiste** (Compose suppose qu'elle peut être annulée) et **non ordonnée** (l'ordre d'exécution des composables n'est pas garanti). ### 3. À quoi sert `remember` ? `remember` permet de conserver une valeur entre les recompositions. Sans `remember`, chaque recomposition réinitialiserait la variable. ```kotlin // RememberExample.kt @Composable fun InputField() { // ✅ La valeur survit aux recompositions var text by remember { mutableStateOf("") } // ❌ Sans remember, text serait réinitialisé à "" à chaque recomposition // var text by mutableStateOf("") TextField( value = text, onValueChange = { text = it }, // Déclenche une recomposition label = { Text("Saisir du texte") } ) } ``` ### 4. Quelle différence entre `remember` et `rememberSaveable` ? `remember` conserve la valeur lors des recompositions, mais la perd lors des changements de configuration (rotation d'écran). `rememberSaveable` persiste la valeur à travers les changements de configuration en utilisant le mécanisme `SavedInstanceState`. ```kotlin // RememberSaveableExample.kt @Composable fun SearchBar() { // Perdu après rotation d'écran var query by remember { mutableStateOf("") } // Conservé après rotation d'écran var savedQuery by rememberSaveable { mutableStateOf("") } TextField( value = savedQuery, onValueChange = { savedQuery = it }, placeholder = { Text("Rechercher...") } ) } ``` ## State Management en Compose ### 5. Qu'est-ce que le state hoisting ? Le state hoisting consiste à remonter l'état d'un composable vers son parent. Le composable enfant devient **stateless** : il reçoit l'état en paramètre et notifie les changements via des callbacks. ```kotlin // StateHoistingExample.kt // ✅ Composable stateless — facile à tester et réutiliser @Composable fun EmailInput( email: String, // État fourni par le parent onEmailChange: (String) -> Unit, // Callback vers le parent modifier: Modifier = Modifier ) { TextField( value = email, onValueChange = onEmailChange, label = { Text("Email") }, modifier = modifier ) } // Parent qui gère l'état @Composable fun LoginForm() { var email by remember { mutableStateOf("") } EmailInput( email = email, onEmailChange = { email = it } // Le parent contrôle l'état ) } ``` Ce pattern est fondamental en Compose et revient très souvent en entretien. ### 6. Comment fonctionne `derivedStateOf` ? `derivedStateOf` crée un état dérivé qui ne déclenche une recomposition que lorsque le résultat du calcul change, pas à chaque modification de la source. ```kotlin // DerivedStateExample.kt @Composable fun FilteredList(items: List) { var searchQuery by remember { mutableStateOf("") } // Recalculé uniquement quand le résultat filtré change réellement 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) } } } } ``` > **Quand utiliser derivedStateOf** > > Ce mécanisme est utile quand un état change fréquemment mais que le résultat dérivé change rarement (ex : une liste filtrée, un bouton activé/désactivé selon un formulaire). ### 7. Quelle est la différence entre `StateFlow` et `State` de Compose ? `StateFlow` (Kotlin coroutines) est un flux réactif du ViewModel. `State` est le mécanisme natif de Compose pour déclencher la recomposition. En pratique, `StateFlow` est collecté dans un composable via `collectAsStateWithLifecycle()`. ```kotlin // StateFlowExample.kt class UserViewModel : ViewModel() { private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow = _uiState.asStateFlow() } @Composable fun UserScreen(viewModel: UserViewModel = viewModel()) { // Convertit StateFlow en State pour Compose val uiState by viewModel.uiState.collectAsStateWithLifecycle() Text("Bonjour, ${uiState.userName}") } ``` La recommandation est d'utiliser `collectAsStateWithLifecycle()` (plutôt que `collectAsState()`) car cette fonction respecte le cycle de vie et arrête la collecte quand l'écran n'est plus visible. ## Side Effects et cycle de vie ### 8. Quels sont les principaux side effects en Compose ? Les side effects permettent d'exécuter du code non-composable (appels réseau, logs, navigation) de manière contrôlée. Les trois principaux : ```kotlin // SideEffectsExample.kt @Composable fun AnalyticsScreen(screenName: String) { // LaunchedEffect : exécuté une fois quand screenName change LaunchedEffect(screenName) { analyticsTracker.logScreenView(screenName) // Appel suspendu } // DisposableEffect : avec nettoyage (comme useEffect avec cleanup) DisposableEffect(Unit) { val listener = onScrollListener() scrollView.addListener(listener) onDispose { scrollView.removeListener(listener) // Nettoyage garanti } } // SideEffect : exécuté après chaque recomposition réussie SideEffect { logger.log("Screen recomposée") // Code non-suspendu } } ``` ### 9. Quand utiliser `LaunchedEffect` vs `rememberCoroutineScope` ? `LaunchedEffect` est lié à la composition : la coroutine est annulée quand le composable quitte la composition ou quand la clé change. `rememberCoroutineScope` fournit un scope contrôlé par l'utilisateur, utile pour des actions déclenchées par l'utilisateur (clic de bouton). ```kotlin // CoroutineScopeExample.kt @Composable fun DataScreen(userId: String) { // ✅ LaunchedEffect : chargement automatique lié au cycle de vie LaunchedEffect(userId) { loadUserData(userId) // Relancé si userId change } // ✅ rememberCoroutineScope : action utilisateur ponctuelle val scope = rememberCoroutineScope() Button(onClick = { scope.launch { refreshData() } // Déclenché manuellement }) { Text("Rafraîchir") } } ``` ## Layouts et composants avancés ### 10. Comment fonctionne `LazyColumn` et en quoi diffère-t-elle de `RecyclerView` ? `LazyColumn` est l'équivalent Compose de `RecyclerView`. Elle ne compose que les éléments visibles à l'écran et recycle les composables sortis de la fenêtre visible. ```kotlin // LazyColumnExample.kt @Composable fun UserList(users: List) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) // Espacement entre items ) { items( items = users, key = { it.id } // Clé stable pour optimiser les recompositions ) { user -> UserCard(user) } } } ``` Point important : toujours fournir un paramètre `key` stable pour éviter des recompositions inutiles lors du tri ou de la suppression d'éléments. ### 11. Comment créer un layout personnalisé ? Compose permet de créer des layouts sur mesure via la fonction `Layout`. Cela remplace les `ViewGroup` custom du système de vues. ```kotlin // CustomLayoutExample.kt @Composable fun OverlappingRow( overlapOffset: Dp = (-16).dp, // Décalage négatif pour le chevauchement 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. Comment implémenter un thème personnalisé avec `MaterialTheme` ? Le theming en Compose repose sur `CompositionLocal`. `MaterialTheme` fournit des valeurs de couleur, typographie et forme accessibles dans tout l'arbre de composables. ```kotlin // CustomThemeExample.kt // Définition de couleurs personnalisées private val DarkColorScheme = darkColorScheme( primary = Color(0xFF6200EE), secondary = Color(0xFF03DAC6), background = Color(0xFF121212) ) @Composable fun AppTheme(content: @Composable () -> Unit) { MaterialTheme( colorScheme = DarkColorScheme, typography = AppTypography, // Typographie personnalisée content = content ) } // Utilisation dans un composable @Composable fun ThemedCard() { Card(colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface // Accès au thème )) { Text( text = "Contenu", style = MaterialTheme.typography.bodyLarge // Typographie du thème ) } } ``` ## Navigation en Compose ### 13. Comment fonctionne la navigation avec Compose Navigation ? Compose Navigation utilise un `NavHost` avec des routes déclarées comme des chaînes de caractères (ou des types sérialisables depuis Navigation 2.8+). ```kotlin // NavigationExample.kt @Composable fun AppNavigation() { val navController = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen(onNavigateToDetail = { id -> navController.navigate("detail/$id") // Navigation avec argument }) } composable( route = "detail/{userId}", arguments = listOf(navArgument("userId") { type = NavType.StringType }) ) { backStackEntry -> val userId = backStackEntry.arguments?.getString("userId") ?: "" DetailScreen(userId = userId) } } } ``` ### 14. Comment passer des données entre écrans ? Les arguments simples (String, Int) passent directement via la route. Pour des objets complexes, les recommandations actuelles sont d'utiliser un `ViewModel` partagé ou de passer uniquement un identifiant et de charger les données dans l'écran de destination. ```kotlin // NavigationArgsExample.kt // Type-safe navigation avec Kotlin Serialization (Navigation 2.8+) @Serializable data class ProfileRoute(val userId: String, val tab: String = "info") // Déclaration composable { backStackEntry -> val route = backStackEntry.toRoute() ProfileScreen(userId = route.userId, tab = route.tab) } // Navigation navController.navigate(ProfileRoute(userId = "123", tab = "stats")) ``` > **Anti-pattern à éviter** > > Ne jamais passer des objets complexes sérialisés dans la route. Passer un ID et laisser l'écran de destination charger les données via le ViewModel. ## Performance et optimisation ### 15. Comment éviter les recompositions inutiles ? Trois stratégies principales pour limiter les recompositions inutiles : ```kotlin // PerformanceExample.kt // 1. Utiliser des classes stables (data class avec propriétés immuables) @Stable // Indique à Compose que cette classe est stable data class UserState( val name: String, val avatar: String ) // 2. Extraire les lambdas avec remember @Composable fun OptimizedList(onItemClick: (String) -> Unit) { val stableCallback = remember(onItemClick) { onItemClick } LazyColumn { items(100) { index -> ItemRow(onClick = { stableCallback("item_$index") }) } } } // 3. Utiliser key() pour aider Compose à identifier les éléments @Composable fun UserTabs(users: List) { Column { users.forEach { user -> key(user.id) { // Identité stable UserRow(user) } } } } ``` ### 16. Comment profiler les performances d'une app Compose ? Le Layout Inspector d'Android Studio affiche le nombre de recompositions par composable. Le flag `debugInspectorInfo` et les `CompositionTracer` aident au diagnostic. ```kotlin // ProfilingExample.kt // Activer les compteurs de recomposition en debug @Composable fun DebugRecomposition(tag: String, content: @Composable () -> Unit) { val recompositionCount = remember { mutableIntStateOf(0) } SideEffect { recompositionCount.intValue++ // Incrémenté à chaque recomposition Log.d("Recomposition", "$tag : ${recompositionCount.intValue} fois") } content() } // Utilisation DebugRecomposition("UserCard") { UserCard(user) } ``` En complément, le **Compose Compiler Metrics** génère un rapport détaillé des fonctions skippable, restartable et des classes stables/instables. ### 17. Qu'est-ce que le modificateur `Modifier` et pourquoi est-il important ? `Modifier` est une chaîne d'instructions ordonnée qui modifie l'apparence et le comportement d'un composable. L'ordre des modificateurs impacte directement le rendu. ```kotlin // ModifierOrderExample.kt @Composable fun ModifierOrderDemo() { // ❌ Padding PUIS background = padding non coloré Text( text = "Hello", modifier = Modifier .padding(16.dp) .background(Color.Red) ) // ✅ Background PUIS padding = padding coloré Text( text = "Hello", modifier = Modifier .background(Color.Red) .padding(16.dp) ) } ``` Bonne pratique : toujours accepter un paramètre `modifier: Modifier = Modifier` dans les composables réutilisables pour permettre la personnalisation par le parent. ## Architecture et patterns avancés ### 18. Comment structurer un écran Compose avec un ViewModel ? Le pattern recommandé sépare l'état UI dans un `data class`, les événements dans une `sealed interface`, et le ViewModel gère la logique métier. ```kotlin // ScreenArchitectureExample.kt // État UI data class ProfileUiState( val user: User? = null, val isLoading: Boolean = false, val error: String? = null ) // Événements utilisateur 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) } } } // Écran Compose @Composable fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() ProfileContent( uiState = uiState, onEvent = viewModel::onEvent // Délégation des événements ) } ``` ### 19. Comment tester des composables ? Compose fournit une bibliothèque de test avec `ComposeTestRule` pour les tests d'UI et les assertions sémantiques. ```kotlin // ComposableTestExample.kt @get:Rule val composeTestRule = createComposeRule() @Test fun counter_incrementsOnClick() { composeTestRule.setContent { Counter() // Le composable à tester } // Vérifier l'état initial composeTestRule.onNodeWithText("Clics : 0").assertIsDisplayed() // Simuler un clic composeTestRule.onNodeWithText("Clics : 0").performClick() // Vérifier le nouvel état composeTestRule.onNodeWithText("Clics : 1").assertIsDisplayed() } ``` Pour les tests unitaires des composables stateless, tester le ViewModel séparément avec des tests classiques JUnit/Turbine est souvent plus efficace. ### 20. Comment intégrer Compose dans une application existante avec des vues XML ? L'interopérabilité est bidirectionnelle : `ComposeView` permet d'intégrer du Compose dans du XML, et `AndroidView` permet d'utiliser des vues classiques dans Compose. ```kotlin // InteropExample.kt // Compose dans XML (dans un Fragment ou Activity) class ProfileFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) setContent { AppTheme { ProfileScreen() } } } } } // Vue XML dans Compose @Composable fun LegacyMapView() { AndroidView( factory = { context -> MapView(context).apply { onCreate(null) } }, update = { mapView -> mapView.getMapAsync { /* config */ } } ) } ``` > **Stratégie de migration recommandée** > > Migrer écran par écran, en commençant par les écrans les plus simples. Chaque nouvel écran devrait être entièrement en Compose, tandis que les écrans existants migrent progressivement. ## Conclusion Ces 20 questions couvrent les fondamentaux que tout développeur Android doit maîtriser pour réussir un entretien sur Jetpack Compose. Voici une checklist récapitulative : - ✅ Comprendre la recomposition et son fonctionnement optimiste - ✅ Maîtriser `remember`, `rememberSaveable` et `derivedStateOf` - ✅ Appliquer le state hoisting systématiquement - ✅ Connaître les side effects (`LaunchedEffect`, `DisposableEffect`, `SideEffect`) - ✅ Savoir optimiser les performances (classes stables, clés, lambdas) - ✅ Structurer un écran avec ViewModel + UiState + Events - ✅ Tester les composables avec `ComposeTestRule` - ✅ Gérer l'interopérabilité Compose/Views --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/android/questions-entretien-jetpack-compose