20 najczesciej zadawanych pytan rekrutacyjnych z Jetpack Compose w 2026

20 najczesciej zadawanych pytan na rozmowie kwalifikacyjnej z Jetpack Compose: rekompozycja, zarzadzanie stanem, nawigacja, wydajnosc i wzorce architektoniczne.

Pytania rekrutacyjne z Jetpack Compose dla programistow Android

Jetpack Compose stal sie standardowym zestawem narzedzi UI w rozwoju aplikacji Android. Rozmowy kwalifikacyjne na stanowiska Android regularnie weryfikuja znajomosc Compose — od mechanizmow rekompozycji po zarzadzanie stanem i optymalizacje wydajnosci. Ponizej znajduje sie 20 najczesciej zadawanych pytan wraz ze szczegolowymi odpowiedziami i przykladami kodu.

Jak korzystac z tego przewodnika

Kazde pytanie zawiera ustrukturyzowana odpowiedz i przyklad kodu. Pytania sa uporzadkowane wedlug rosnacego poziomu trudnosci: podstawy, poziom sredniozaawansowany, a nastepnie zaawansowany.

Podstawy Jetpack Compose

1. Jaka jest roznica miedzy Compose a systemem widokow XML?

Compose wykorzystuje paradygmat deklaratywny: interfejs uzytkownika jest opisywany jako funkcja stanu, a framework automatycznie zajmuje sie aktualizacjami. Tradycyjny system XML jest imperatywny — widokami trzeba recznie manipulowac za pomoca findViewById lub 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
    }
}

W Compose nie trzeba wyszukiwac referencji do TextView i recznie jej aktualizowac — rekompozycja zajmuje sie wszystkim.

2. Czym jest rekompozycja?

Rekompozycja to proces, w ktorym Compose ponownie wywoluje funkcje @Composable, gdy zmienia sie ich stan. Tylko funkcje, ktorych parametry ulegly zmianie, sa ponownie wykonywane, co optymalizuje wydajnosc.

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
}

Kluczowa informacja na rozmowe: rekompozycja jest optymistyczna (Compose zaklada, ze moze zostac anulowana) i nieuporzadkowana (kolejnosc wykonywania composables nie jest gwarantowana).

3. Co robi remember?

remember zachowuje wartosc pomiedzy rekompozycjami. Bez remember kazda rekompozycja resetowalaby zmienna do jej poczatkowej wartosci.

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. Jaka jest roznica miedzy remember a rememberSaveable?

remember zachowuje wartosci pomiedzy rekompozycjami, ale traci je przy zmianach konfiguracji (obrot ekranu). rememberSaveable utrwala wartosci przez zmiany konfiguracji, wykorzystujac mechanizm 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...") }
    )
}

Zarzadzanie stanem w Compose

5. Czym jest state hoisting?

State hoisting oznacza przenoszenie stanu z composable do jego rodzica. Komponent potomny staje sie bezstanowy: otrzymuje stan jako parametry i powiadamia o zmianach za pomoca callbackow.

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

Ten wzorzec jest fundamentalny w Compose i czesto pojawia sie na rozmowach kwalifikacyjnych.

6. Jak dziala derivedStateOf?

derivedStateOf tworzy stan pochodny, ktory wyzwala rekompozycje tylko wtedy, gdy wynik obliczen sie zmieni, a nie przy kazdej modyfikacji zrodla.

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) }
        }
    }
}
Kiedy uzywac derivedStateOf

Ten mechanizm jest przydatny, gdy stan zmienia sie czesto, ale wynik pochodny zmienia sie rzadko (np. filtrowana lista, przycisk wlaczony/wylaczony na podstawie walidacji formularza).

7. Jaka jest roznica miedzy StateFlow a State<T> w Compose?

StateFlow (Kotlin coroutines) to reaktywny strumien z ViewModelu. State<T> to natywny mechanizm Compose do wyzwalania rekompozycji. W praktyce StateFlow jest zbierany w composable za pomoca 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}")
}

Zalecane jest uzywanie collectAsStateWithLifecycle() (zamiast collectAsState()), poniewaz respektuje cykl zycia i zatrzymuje zbieranie, gdy ekran nie jest juz widoczny.

Efekty uboczne i cykl zycia

8. Jakie sa glowne efekty uboczne w Compose?

Efekty uboczne pozwalaja na uruchamianie kodu nie-composable (wywolania sieciowe, logowanie, nawigacja) w kontrolowany sposob. Trzy glowne to:

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. Kiedy uzywac LaunchedEffect vs rememberCoroutineScope?

LaunchedEffect jest powiazany z kompozycja: korutyna zostaje anulowana, gdy composable opuszcza kompozycje lub gdy zmieni sie klucz. rememberCoroutineScope dostarcza zakres kontrolowany przez uzytkownika, przydatny do akcji wyzwalanych przez uzytkownika (klikniecia przyciskow).

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

Gotowy na rozmowy o Android?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Layouty i zaawansowane komponenty

10. Jak dziala LazyColumn i czym rozni sie od RecyclerView?

LazyColumn to odpowiednik RecyclerView w Compose. Komponuje tylko elementy widoczne na ekranie i recykluje composables, ktore wyjda poza widoczny obszar.

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

Wazna uwaga: zawsze nalezy podawac stabilny parametr key, aby unikac niepotrzebnych rekompozycji przy sortowaniu lub usuwaniu elementow.

11. Jak stworzyc wlasny layout?

Compose pozwala na tworzenie wlasnych layoutow za pomoca funkcji Layout. Zastepuje to implementacje niestandardowych ViewGroup z systemu widokow.

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. Jak zaimplementowac wlasny motyw za pomoca MaterialTheme?

Tematyka w Compose opiera sie na CompositionLocal. MaterialTheme dostarcza wartosci kolorow, typografii i ksztaltow dostepne w calym drzewie composables.

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

Nawigacja w Compose

13. Jak dziala nawigacja w Compose?

Compose Navigation korzysta z NavHost z trasami deklarowanymi jako stringi (lub typy serializowalne od 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. Jak przekazywac dane miedzy ekranami?

Proste argumenty (String, Int) przekazuje sie bezposrednio przez trase. W przypadku zlozonych obiektow aktualne zalecenie to uzycie wspoldzielonego ViewModel lub przekazanie jedynie identyfikatora i zaladowanie danych na ekranie docelowym.

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"))
Antywzorzec do unikania

Nigdy nie nalezy przekazywac zlozonych zserializowanych obiektow w trasie. Nalezy przekazac ID i pozwolic ekranowi docelowemu zaladowac dane przez ViewModel.

Wydajnosc i optymalizacja

15. Jak zapobiegac niepotrzebnym rekompozycjom?

Trzy glowne strategie minimalizowania niepotrzebnych rekompozycji:

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. Jak profilowac wydajnosc aplikacji Compose?

Layout Inspector w Android Studio wyswietla liczniki rekompozycji dla kazdego composable. Flaga debugInspectorInfo i CompositionTracer pomagaja w diagnostyce.

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

Dodatkowo Compose Compiler Metrics generuje szczegolowy raport funkcji skippable, restartable oraz klas stabilnych i niestabilnych.

17. Czym jest Modifier i dlaczego jest wazny?

Modifier to uporzadkowany lancuch instrukcji modyfikujacych wyglad i zachowanie composable. Kolejnosc modyfikatorow bezposrednio wplywa na renderowanie.

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

Dobra praktyka: zawsze nalezy akceptowac parametr modifier: Modifier = Modifier w composables wielokrotnego uzytku, aby umozliwic personalizacje przez rodzica.

Architektura i zaawansowane wzorce

18. Jak ustrukturyzowac ekran Compose z ViewModelem?

Zalecany wzorzec rozdziela stan UI w data class, zdarzenia w sealed interface, a ViewModel obsluguje logike biznesowa.

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. Jak testowac composables?

Compose udostepnia biblioteke testowa z ComposeTestRule do testow UI i asercji semantycznych.

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

W przypadku testowania bezstanowych composables, osobne testowanie ViewModelu za pomoca standardowych testow JUnit/Turbine jest czesto bardziej efektywne.

20. Jak zintegrowac Compose z istniejaca aplikacja oparta na XML?

Interoperacyjnosc jest dwukierunkowa: ComposeView osadza Compose w XML, a AndroidView uzywa klasycznych widokow wewnatrz 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 */ } }
    )
}
Zalecana strategia migracji

Migracje nalezy przeprowadzac ekran po ekranie, zaczynajac od najprostszych. Kazdy nowy ekran powinien byc w calosci w Compose, podczas gdy istniejace ekrany migruja stopniowo.

Gotowy na rozmowy o Android?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Podsumowanie

Te 20 pytan obejmuje podstawy, ktore kazdy programista Android musi opanowac przed rozmowa kwalifikacyjna z Jetpack Compose. Oto lista kontrolna:

  • ✅ Zrozumienie rekompozycji i jej optymistycznego zachowania
  • ✅ Opanowanie remember, rememberSaveable i derivedStateOf
  • ✅ Systematyczne stosowanie state hoistingu
  • ✅ Znajomosc efektow ubocznych (LaunchedEffect, DisposableEffect, SideEffect)
  • ✅ Optymalizacja wydajnosci (stabilne klasy, klucze, lambdy)
  • ✅ Strukturyzowanie ekranow z ViewModel + UiState + Events
  • ✅ Testowanie composables za pomoca ComposeTestRule
  • ✅ Obsluga interoperacyjnosci Compose/Views

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#jetpack compose
#android
#interview
#kotlin
#ui

Udostępnij

Powiązane artykuły