# 20 найпоширеніших питань на співбесіді з Jetpack Compose у 2026 році > 20 найчастіших питань на співбесіді з Jetpack Compose: рекомпозиція, управління станом, навігація, продуктивність та архітектурні патерни. - Published: 2026-01-27 - Updated: 2026-04-12 - Author: SharpSkill - Tags: jetpack compose, android, interview, kotlin, ui - Reading time: 14 min --- Jetpack Compose став стандартним UI-інструментарієм для розробки під Android. Технічні співбесіди тепер регулярно перевіряють знання Compose — від механізмів рекомпозиції до управління станом та оптимізації продуктивності. Нижче наведено 20 найчастіших питань з детальними відповідями та прикладами коду. > **Як користуватися цим посібником** > > Кожне питання містить структуровану відповідь і приклад коду. Питання впорядковані за зростанням складності: основи, середній рівень, потім просунутий. ## Основи Jetpack Compose ### 1. Яка різниця між Compose та XML-системою відображень? Compose використовує **декларативну** парадигму: UI описується як функція стану, а фреймворк автоматично обробляє оновлення. Традиційна XML-система є **імперативною** — відображеннями потрібно керувати вручну через `findViewById` або View Binding. ```kotlin // DeclarativeExample.kt // 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 } } ``` У Compose немає потреби шукати посилання на `TextView` і вручну його оновлювати — рекомпозиція бере все на себе. ### 2. Що таке рекомпозиція? Рекомпозиція — це процес, під час якого Compose повторно викликає функції `@Composable` при зміні їхнього стану. Повторно виконуються лише ті функції, параметри яких змінилися, що оптимізує продуктивність. ```kotlin // RecompositionExample.kt @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 } ``` Ключова інформація для співбесіди: рекомпозиція є **оптимістичною** (Compose припускає, що її можна скасувати) та **невпорядкованою** (порядок виконання composable-функцій не гарантується). ### 3. Що робить `remember`? `remember` зберігає значення між рекомпозиціями. Без `remember` кожна рекомпозиція скидала б змінну до початкового значення. ```kotlin // RememberExample.kt @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. Яка різниця між `remember` та `rememberSaveable`? `remember` зберігає значення між рекомпозиціями, але втрачає їх при змінах конфігурації (обертання екрану). `rememberSaveable` зберігає значення через зміни конфігурації за допомогою механізму `SavedInstanceState`. ```kotlin // RememberSaveableExample.kt @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...") } ) } ``` ## Управління станом у Compose ### 5. Що таке state hoisting? State hoisting означає переміщення стану з composable до його батьківського компонента. Дочірній компонент стає **безстанним**: отримує стан як параметри та повідомляє про зміни через callback-функції. ```kotlin // StateHoistingExample.kt // ✅ 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 ) } ``` Цей патерн є фундаментальним у Compose і часто зустрічається на співбесідах. ### 6. Як працює `derivedStateOf`? `derivedStateOf` створює похідний стан, який ініціює рекомпозицію лише тоді, коли результат обчислення змінюється, а не при кожній зміні джерела. ```kotlin // DerivedStateExample.kt @Composable fun FilteredList(items: List) { 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) } } } } ``` > **Коли використовувати derivedStateOf** > > Цей механізм корисний, коли стан змінюється часто, але похідний результат змінюється рідко (наприклад, відфільтрований список, кнопка увімкнена/вимкнена на основі валідації форми). ### 7. Яка різниця між `StateFlow` та Compose `State`? `StateFlow` (Kotlin coroutines) — це реактивний потік з ViewModel. `State` — це нативний механізм Compose для ініціювання рекомпозиції. На практиці `StateFlow` збирається у composable за допомогою `collectAsStateWithLifecycle()`. ```kotlin // StateFlowExample.kt class UserViewModel : ViewModel() { private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow = _uiState.asStateFlow() } @Composable fun UserScreen(viewModel: UserViewModel = viewModel()) { // Converts StateFlow to State for Compose val uiState by viewModel.uiState.collectAsStateWithLifecycle() Text("Hello, ${uiState.userName}") } ``` Рекомендується використовувати `collectAsStateWithLifecycle()` (замість `collectAsState()`), оскільки це враховує життєвий цикл і припиняє збирання, коли екран більше не видимий. ## Побічні ефекти та життєвий цикл ### 8. Які основні побічні ефекти в Compose? Побічні ефекти дозволяють виконувати не-composable код (мережеві виклики, логування, навігація) контрольовано. Три основні: ```kotlin // SideEffectsExample.kt @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 } } ``` ### 9. Коли використовувати `LaunchedEffect` та `rememberCoroutineScope`? `LaunchedEffect` привʼязаний до композиції: корутина скасовується, коли composable залишає композицію або коли змінюється ключ. `rememberCoroutineScope` надає область видимості, керовану користувачем, що корисно для дій, ініційованих користувачем (натискання кнопок). ```kotlin // CoroutineScopeExample.kt @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") } } ``` ## Макети та просунуті компоненти ### 10. Як працює `LazyColumn` і чим відрізняється від `RecyclerView`? `LazyColumn` — це еквівалент `RecyclerView` у Compose. Він компонує лише видимі на екрані елементи та переробляє composable-функції, що виходять за межі видимої області. ```kotlin // LazyColumnExample.kt @Composable fun UserList(users: List) { 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) } } } ``` Важливий момент: завжди потрібно надавати стабільний параметр `key`, щоб уникати зайвих рекомпозицій при сортуванні або видаленні елементів. ### 11. Як створити власний макет? Compose дозволяє створювати власні макети за допомогою функції `Layout`. Це замінює реалізації власних `ViewGroup` із системи відображень. ```kotlin // CustomLayoutExample.kt @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. Як реалізувати власну тему за допомогою `MaterialTheme`? Темізація в Compose базується на `CompositionLocal`. `MaterialTheme` надає значення кольорів, типографіки та форм, доступні по всьому дереву composable-функцій. ```kotlin // CustomThemeExample.kt // 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 ) } } ``` ## Навігація в Compose ### 13. Як працює навігація в Compose? Compose Navigation використовує `NavHost` з маршрутами, оголошеними як рядки (або серіалізовані типи починаючи з 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 with argument }) } composable( route = "detail/{userId}", arguments = listOf(navArgument("userId") { type = NavType.StringType }) ) { backStackEntry -> val userId = backStackEntry.arguments?.getString("userId") ?: "" DetailScreen(userId = userId) } } } ``` ### 14. Як передавати дані між екранами? Прості аргументи (String, Int) передаються безпосередньо через маршрут. Для складних обʼєктів поточна рекомендація — використовувати спільний `ViewModel` або передавати лише ідентифікатор і завантажувати дані на екрані призначення. ```kotlin // NavigationArgsExample.kt // Type-safe navigation with Kotlin Serialization (Navigation 2.8+) @Serializable data class ProfileRoute(val userId: String, val tab: String = "info") // Declaration composable { backStackEntry -> val route = backStackEntry.toRoute() ProfileScreen(userId = route.userId, tab = route.tab) } // Navigation navController.navigate(ProfileRoute(userId = "123", tab = "stats")) ``` > **Антипатерн, якого слід уникати** > > Ніколи не передавайте складні серіалізовані обʼєкти через маршрут. Передайте ID і дозвольте екрану призначення завантажити дані через ViewModel. ## Продуктивність та оптимізація ### 15. Як запобігти зайвим рекомпозиціям? Три основні стратегії мінімізації непотрібних рекомпозицій: ```kotlin // PerformanceExample.kt // 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) { Column { users.forEach { user -> key(user.id) { // Stable identity UserRow(user) } } } } ``` ### 16. Як профілювати продуктивність Compose-застосунку? Layout Inspector в Android Studio відображає лічильники рекомпозицій для кожного composable. Прапорець `debugInspectorInfo` та `CompositionTracer` допомагають у діагностиці. ```kotlin // ProfilingExample.kt // 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) } ``` Додатково **Compose Compiler Metrics** генерує детальний звіт про функції, які можна пропустити та перезапустити, а також стабільні та нестабільні класи. ### 17. Що таке `Modifier` і чому він важливий? `Modifier` — це впорядкований ланцюжок інструкцій, що модифікують зовнішній вигляд і поведінку composable. Порядок модифікаторів безпосередньо впливає на рендеринг. ```kotlin // ModifierOrderExample.kt @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) ) } ``` Найкраща практика: завжди приймайте параметр `modifier: Modifier = Modifier` у багаторазових composable-функціях, щоб дозволити батьківському компоненту налаштовувати їх. ## Архітектура та просунуті патерни ### 18. Як структурувати екран Compose з ViewModel? Рекомендований патерн розділяє UI-стан у `data class`, події у `sealed interface`, а ViewModel керує бізнес-логікою. ```kotlin // ScreenArchitectureExample.kt // 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. Як тестувати composable-функції? Compose надає бібліотеку тестування з `ComposeTestRule` для UI-тестів та семантичних перевірок. ```kotlin // ComposableTestExample.kt @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() // Verify new state composeTestRule.onNodeWithText("Clicks: 1").assertIsDisplayed() } ``` Для тестування безстанних composable-функцій окреме тестування ViewModel стандартними тестами JUnit/Turbine часто є ефективнішим. ### 20. Як інтегрувати Compose в існуючий XML-застосунок? Сумісність є двонаправленою: `ComposeView` вбудовує Compose в XML, а `AndroidView` використовує класичні відображення всередині Compose. ```kotlin // InteropExample.kt // 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 */ } } ) } ``` > **Рекомендована стратегія міграції** > > Міграцію слід проводити екран за екраном, починаючи з найпростіших. Кожен новий екран має бути повністю на Compose, тоді як існуючі екрани мігрують поступово. ## Висновок Ці 20 питань охоплюють основи, які кожен Android-розробник повинен опанувати перед співбесідою з Jetpack Compose. Контрольний список: - ✅ Розуміння рекомпозиції та її оптимістичної поведінки - ✅ Опанування `remember`, `rememberSaveable` та `derivedStateOf` - ✅ Систематичне застосування state hoisting - ✅ Знання побічних ефектів (`LaunchedEffect`, `DisposableEffect`, `SideEffect`) - ✅ Оптимізація продуктивності (стабільні класи, ключі, лямбди) - ✅ Структурування екранів з ViewModel + UiState + Events - ✅ Тестування composable-функцій за допомогою `ComposeTestRule` - ✅ Обробка сумісності Compose/Views --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/android/jetpack-compose-interview-questions