20 найпоширеніших питань на співбесіді з Jetpack Compose у 2026 році

20 найчастіших питань на співбесіді з Jetpack Compose: рекомпозиція, управління станом, навігація, продуктивність та архітектурні патерни.

Питання на співбесіді з Jetpack Compose для Android-розробників

Jetpack Compose став стандартним UI-інструментарієм для розробки під Android. Технічні співбесіди тепер регулярно перевіряють знання Compose — від механізмів рекомпозиції до управління станом та оптимізації продуктивності. Нижче наведено 20 найчастіших питань з детальними відповідями та прикладами коду.

Як користуватися цим посібником

Кожне питання містить структуровану відповідь і приклад коду. Питання впорядковані за зростанням складності: основи, середній рівень, потім просунутий.

Основи Jetpack Compose

1. Яка різниця між Compose та XML-системою відображень?

Compose використовує декларативну парадигму: UI описується як функція стану, а фреймворк автоматично обробляє оновлення. Традиційна XML-система є імперативною — відображеннями потрібно керувати вручну через findViewById або 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
    }
}

У Compose немає потреби шукати посилання на TextView і вручну його оновлювати — рекомпозиція бере все на себе.

2. Що таке рекомпозиція?

Рекомпозиція — це процес, під час якого Compose повторно викликає функції @Composable при зміні їхнього стану. Повторно виконуються лише ті функції, параметри яких змінилися, що оптимізує продуктивність.

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
}

Ключова інформація для співбесіди: рекомпозиція є оптимістичною (Compose припускає, що її можна скасувати) та невпорядкованою (порядок виконання composable-функцій не гарантується).

3. Що робить remember?

remember зберігає значення між рекомпозиціями. Без remember кожна рекомпозиція скидала б змінну до початкового значення.

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. Яка різниця між remember та rememberSaveable?

remember зберігає значення між рекомпозиціями, але втрачає їх при змінах конфігурації (обертання екрану). rememberSaveable зберігає значення через зміни конфігурації за допомогою механізму 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...") }
    )
}

Управління станом у Compose

5. Що таке state hoisting?

State hoisting означає переміщення стану з composable до його батьківського компонента. Дочірній компонент стає безстанним: отримує стан як параметри та повідомляє про зміни через callback-функції.

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

Цей патерн є фундаментальним у Compose і часто зустрічається на співбесідах.

6. Як працює derivedStateOf?

derivedStateOf створює похідний стан, який ініціює рекомпозицію лише тоді, коли результат обчислення змінюється, а не при кожній зміні джерела.

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) }
        }
    }
}
Коли використовувати derivedStateOf

Цей механізм корисний, коли стан змінюється часто, але похідний результат змінюється рідко (наприклад, відфільтрований список, кнопка увімкнена/вимкнена на основі валідації форми).

7. Яка різниця між StateFlow та Compose State<T>?

StateFlow (Kotlin coroutines) — це реактивний потік з ViewModel. State<T> — це нативний механізм Compose для ініціювання рекомпозиції. На практиці StateFlow збирається у composable за допомогою collectAsStateWithLifecycle().

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

Рекомендується використовувати collectAsStateWithLifecycle() (замість collectAsState()), оскільки це враховує життєвий цикл і припиняє збирання, коли екран більше не видимий.

Побічні ефекти та життєвий цикл

8. Які основні побічні ефекти в Compose?

Побічні ефекти дозволяють виконувати не-composable код (мережеві виклики, логування, навігація) контрольовано. Три основні:

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

9. Коли використовувати LaunchedEffect та rememberCoroutineScope?

LaunchedEffect привʼязаний до композиції: корутина скасовується, коли composable залишає композицію або коли змінюється ключ. rememberCoroutineScope надає область видимості, керовану користувачем, що корисно для дій, ініційованих користувачем (натискання кнопок).

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

Готовий до співбесід з Android?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Макети та просунуті компоненти

10. Як працює LazyColumn і чим відрізняється від RecyclerView?

LazyColumn — це еквівалент RecyclerView у Compose. Він компонує лише видимі на екрані елементи та переробляє composable-функції, що виходять за межі видимої області.

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

Важливий момент: завжди потрібно надавати стабільний параметр key, щоб уникати зайвих рекомпозицій при сортуванні або видаленні елементів.

11. Як створити власний макет?

Compose дозволяє створювати власні макети за допомогою функції Layout. Це замінює реалізації власних ViewGroup із системи відображень.

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. Як реалізувати власну тему за допомогою MaterialTheme?

Темізація в Compose базується на CompositionLocal. MaterialTheme надає значення кольорів, типографіки та форм, доступні по всьому дереву composable-функцій.

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

Навігація в Compose

13. Як працює навігація в Compose?

Compose Navigation використовує NavHost з маршрутами, оголошеними як рядки (або серіалізовані типи починаючи з Navigation 2.8+).

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. Як передавати дані між екранами?

Прості аргументи (String, Int) передаються безпосередньо через маршрут. Для складних обʼєктів поточна рекомендація — використовувати спільний ViewModel або передавати лише ідентифікатор і завантажувати дані на екрані призначення.

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"))
Антипатерн, якого слід уникати

Ніколи не передавайте складні серіалізовані обʼєкти через маршрут. Передайте ID і дозвольте екрану призначення завантажити дані через ViewModel.

Продуктивність та оптимізація

15. Як запобігти зайвим рекомпозиціям?

Три основні стратегії мінімізації непотрібних рекомпозицій:

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. Як профілювати продуктивність Compose-застосунку?

Layout Inspector в Android Studio відображає лічильники рекомпозицій для кожного composable. Прапорець debugInspectorInfo та CompositionTracer допомагають у діагностиці.

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

Додатково Compose Compiler Metrics генерує детальний звіт про функції, які можна пропустити та перезапустити, а також стабільні та нестабільні класи.

17. Що таке Modifier і чому він важливий?

Modifier — це впорядкований ланцюжок інструкцій, що модифікують зовнішній вигляд і поведінку composable. Порядок модифікаторів безпосередньо впливає на рендеринг.

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

Найкраща практика: завжди приймайте параметр modifier: Modifier = Modifier у багаторазових composable-функціях, щоб дозволити батьківському компоненту налаштовувати їх.

Архітектура та просунуті патерни

18. Як структурувати екран Compose з ViewModel?

Рекомендований патерн розділяє UI-стан у data class, події у sealed interface, а ViewModel керує бізнес-логікою.

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. Як тестувати composable-функції?

Compose надає бібліотеку тестування з ComposeTestRule для UI-тестів та семантичних перевірок.

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

    // Verify new state
    composeTestRule.onNodeWithText("Clicks: 1").assertIsDisplayed()
}

Для тестування безстанних composable-функцій окреме тестування ViewModel стандартними тестами JUnit/Turbine часто є ефективнішим.

20. Як інтегрувати Compose в існуючий XML-застосунок?

Сумісність є двонаправленою: ComposeView вбудовує Compose в XML, а AndroidView використовує класичні відображення всередині 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 */ } }
    )
}
Рекомендована стратегія міграції

Міграцію слід проводити екран за екраном, починаючи з найпростіших. Кожен новий екран має бути повністю на Compose, тоді як існуючі екрани мігрують поступово.

Готовий до співбесід з Android?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Висновок

Ці 20 питань охоплюють основи, які кожен Android-розробник повинен опанувати перед співбесідою з Jetpack Compose. Контрольний список:

  • ✅ Розуміння рекомпозиції та її оптимістичної поведінки
  • ✅ Опанування remember, rememberSaveable та derivedStateOf
  • ✅ Систематичне застосування state hoisting
  • ✅ Знання побічних ефектів (LaunchedEffect, DisposableEffect, SideEffect)
  • ✅ Оптимізація продуктивності (стабільні класи, ключі, лямбди)
  • ✅ Структурування екранів з ViewModel + UiState + Events
  • ✅ Тестування composable-функцій за допомогою ComposeTestRule
  • ✅ Обробка сумісності Compose/Views

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#jetpack compose
#android
#interview
#kotlin
#ui

Поділитися

Пов'язані статті