2026年Jetpack Compose面接質問トップ20

Jetpack Compose面接で最も出題される20の質問:リコンポジション、状態管理、ナビゲーション、パフォーマンス、アーキテクチャパターン。

Android開発者向けJetpack Compose面接質問ガイド

Jetpack ComposeはAndroid UI開発の標準ツールキットとなっています。技術面接ではリコンポジションの仕組みから状態管理、パフォーマンス最適化まで、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. rememberrememberSaveableの違いは何ですか?

rememberはリコンポジション間で値を保持しますが、構成変更(画面回転)時には失われます。rememberSaveableSavedInstanceStateメカニズムを使用して構成変更を通じて値を永続化します。

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. 状態ホイスティングとは何ですか?

状態ホイスティングは、状態をComposableから親に移動することを意味します。子のComposableはステートレスになります:状態をパラメータとして受け取り、コールバックを介して変更を通知します。このパターンは再利用可能でテスト可能なコンポーネントを構築するために不可欠です。

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コルーチン)はViewModelからのリアクティブストリームです。State<T>はリコンポジションをトリガーするComposeネイティブのメカニズムです。実際には、StateFlowcollectAsStateWithLifecycle()を介してComposable内で収集されます。コルーチンフローの詳細については、Kotlinコルーチン完全ガイドを参照してください。

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

collectAsState()ではなくcollectAsStateWithLifecycle()の使用が推奨されます。ライフサイクルを尊重し、画面が表示されなくなると収集を停止するためです。

副作用とライフサイクル

8. Composeの主な副作用は何ですか?

副作用により、非Composableコード(ネットワーク呼び出し、ログ記録、ナビゲーション)を制御された方法で実行できます。主な副作用にはLaunchedEffectDisposableEffectSideEffect、そしてCompose 1.12で導入された新しいKeyed SideEffectが含まれます。

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

Compose 1.12アップデート: Keyed SideEffectは、特定のキーが変更されるたびにワンショット副作用を実行するためのキー引数をサポートする新しいオーバーロードを提供します。公式ベンチマークによると、サスペンドしない操作ではLaunchedEffectより最大90%高速になる可能性があります。

9. LaunchedEffectrememberCoroutineScopeはいつ使い分けますか?

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はComposeにおけるRecyclerViewの同等物です。画面に表示される要素のみをコンポーズし、可視ウィンドウからスクロールアウトした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 Navigationはどのように機能しますか?

Compose Navigationはシリアライズ可能な型として宣言されたルートを持つNavHostを使用します(Navigation 2.8以降)。Navigation 2.9以降、ライブラリはカスタムNavType実装を必要とせずに、値クラスとList<Enum>を引数型としてサポートします。

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を使用するか、識別子のみを渡して宛先画面でデータをロードすることです。Navigation 2.9ではリストや配列などのコレクションベースの引数用にCollectionNavType<T>が導入されました。

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を介してデータをロードさせてください。アーキテクチャのガイダンスについては、MVVM vs MVI: どのアーキテクチャを選ぶべきかを参照してください。

パフォーマンスと最適化

15. 不要なリコンポジションを防ぐ方法は?

不要なリコンポジションを最小限に抑えるための3つの主な戦略:

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アプリのパフォーマンスをプロファイリングする方法は?

Android StudioのLayout InspectorはComposableごとのリコンポジション数を表示します。Compose 1.13 alphaでは、リコンポジションの無効化フローを記録するためのRecompositionTracerが導入されました。debugInspectorInfoフラグも診断に役立ちます。

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の外観と動作を変更する命令の順序付きチェーンです。Modifierの順序はレンダリングに直接影響します。

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

ベストプラクティス:親からのカスタマイズを許可するために、再利用可能なComposableでは常にmodifier: Modifier = Modifierパラメータを受け入れてください。

アーキテクチャと高度なパターン

18. ViewModelを使用してCompose画面を構造化する方法は?

推奨パターンは、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はUIテスト用のComposeTestRuleとセマンティックアサーションを持つテストライブラリを提供します。Compose 1.11以降、v2テストAPIがデフォルトとなり、v1は非推奨となりました。主な変更点はUnconfinedTestDispatcher(即時実行)からStandardTestDispatcher(キュー実行)への移行であり、テストが本番環境の動作をより正確に反映するようになりました。

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

    // With v2 APIs, advance the clock explicitly
    composeTestRule.waitForIdle()

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

ステートレスなComposableのユニットテストでは、ViewModelを標準のJUnit/Turbineテストで個別にテストする方が効率的な場合が多いです。

20. Composeを既存のXMLベースアプリに統合する方法は?

相互運用性は双方向です:ComposeViewはXML内にComposeを埋め込み、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、技術テストで練習しましょう。

ソース

Jetpack Compose面接の重要ポイント

これらの20の質問は、Jetpack Compose面接でAndroid開発者が習得すべき基礎をカバーしています。実践的な練習には、Jetpack Compose面接質問モジュールをお試しください。

  • ✅ リコンポジションとその楽観的な動作を理解する
  • rememberrememberSaveablederivedStateOfをマスターする
  • ✅ 状態ホイスティングを体系的に適用する
  • ✅ 副作用を理解する(LaunchedEffectDisposableEffectSideEffect、新しいKeyed SideEffect)
  • ✅ パフォーマンスを最適化する(安定したクラス、キー、ラムダ)
  • ✅ ViewModel + UiState + Eventsで画面を構造化する
  • ✅ v2テストAPIを使用してComposeTestRuleでComposableをテストする
  • ✅ Compose/Views相互運用性を処理する

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

今日のチャレンジ

Android のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月20日 更新

タグ

#jetpack compose
#android
#interview
#kotlin
#ui

共有

関連記事