【正社員】還元率83%【フリーランス】マージン一律5万円で案件をご紹介させていただきます。 詳細はこちら

【Android】<Kotlin>Jetpack Composeにおける新しいUI構築の考え方と画面遷移について(Single Activity × Navigation Compose)

【Android】<Kotlin>Jetpack Composeにおける新しいUI構築の考え方と画面遷移について(Single Activity × Navigation Compose)
すだ

みなさまこんにちは〜!
メモリアインクのすだです。

本日は、
KotlinのJetpack Composeを使ったUIの構築と画面遷移について
実際のコードを用いて解説していきます。

この記事を読んでわかること…
・Navigation Composeとは?
・Navigation Composeを使った画面遷移の方法

目次

環境

  • Kotlin (ver 1.9.0)
  • Android Studio (Giraffe | 2022.3.1 Patch 3)

XMLベースUIの課題とJetpack Composeを使ったUI構築

Jetpack Compose が登場する以前、Android の UI は主に XML で定義されており、UI の切り替えや再利用を目的として Fragment がよく使われていました。
Fragment は再利用可能な「画面部品」として構成され、Activity はより大きな機能単位で分割されることで、柔軟な画面遷移と細かいライフサイクル管理を両立する設計が主流でした。

しかし、Fragment には以下のような課題がありました:

  • ライフサイクルの管理が複雑でミスが起きやすい
  • Fragment 間の連携が密結合になりやすく、画面構成がこじれやすい
  • View の初期化や状態の保持が煩雑

Jetpack Compose では、UI を @Composable 関数として定義することで Fragment自体が不要になり、
さらに 複数のActivityを使わず「Single Activity + Navigation」構成が一般的となりました。

Composeでは、状態(State)の変化に応じて UI が自動的に再描画されるため、従来のように findViewByIdsetText() で UI を手動更新する必要がなくなります。
これにより、「ライフサイクルを意識してUIを作る」場面は大幅に減少しました(ただし、完全に不要になるわけではありません)。


既存のアプリが Android View + 複数の Activity / Fragment によって構成されている場合、
いきなり Jetpack Compose + Single Activity + Navigation に全面的に移行するのは難易度が高く、段階的な対応や設計見直しが必要です。

それでも、Compose によって実現できる「シンプルで状態管理しやすい UI 構成」や「公式が推奨する開発スタイル」という点を踏まえると、
今後の移行を前提にした開発意識や設計の準備は、非常に重要な考え方だといえるでしょう。

Navigation Composeで実現する画面遷移

Navigation Composeとは?

Jetpack Compose専用に提供されているナビゲーションライブラリで、以下のような用途に使います:
・複数のComposable間の画面遷移
・戻る操作の管理
・画面ごとの状態スコープの分離
・引数付き遷移(パラメータ渡し)

導入時には以下の依存を追加してください。

// build.gradle(:app)
dependencies {
    implementation("androidx.navigation:navigation-compose:2.7.0") // 最新は適宜確認
}

画面遷移の基本的な構成

@Composable
fun AppNavGraph(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") { HomeScreen(navController) }
        composable("detail") { DetailScreen() }
    }
}

NavHost は、Jetpack Compose における 画面遷移(ナビゲーション)を管理する中心的なコンポーネントです。
NavController の現在位置(ルート名)に応じて、対応する Composable 画面を自動的に表示してくれます。

この NavHost の中で定義される composable("...") の集まりを、ナビゲーショングラフ(NavGraph)と呼びます。

この構成では、各画面を composable() 単位で登録しており、
それぞれに文字列のルートID(例:"home""detail" など)を割り当てます。
このルートIDが、画面遷移時の「キー」として使われます。

Jetpack Composeでは、setContent の中で Composable UI を構築し、
その中に NavHost を設置することで、アプリ内の画面切り替えを実現します。▼

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            AppNavGraph(navController)
        }
    }
}

NavController は現在の画面(ルート)を管理し、navigate()popBackStack() などの命令で、遷移や戻る操作を制御できます。remember によって状態が保持されます。

そしてこのNavControllerを使って、画面ごとのComposableで画面遷移のためのコードを書きます。

@Composable
fun HomeScreen(navController: NavController) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("これはホーム画面です", fontSize = 20.sp)

        Spacer(modifier = Modifier.height(16.dp))

        Button(onClick = { navController.navigate("detail") }) {
            Text("詳細画面へ移動")
        }
    }
}

navController.navigate("detail") で “detail” 画面に遷移します。
(この "detail" は、NavGraph 内で定義したルート名と一致している必要があります。)

引数付きの画面遷移

画面遷移時の値の受け渡し(共有)の仕方については他にもいくつかありますが、
ここでは基本的な”ルートに値を埋め込むパスパラメータ”の形をお伝えします。

// 遷移元で渡す
navController.navigate("detail/123")

URLのように値を埋め込む形式です。

// 遷移先ルートの定義
composable("detail/{id}") { backStackEntry ->
    val id = backStackEntry.arguments?.getString("id")
    DetailScreen(id)
}

backStackEntry は、現在表示されている画面(Composable)に関するナビゲーション情報を持っているオブジェクトです。
正式には NavBackStackEntry というクラスで、ルートに渡された引数(パラメータ)を取り出すときに使うのが主な用途です。

NavBackStackEntry のイメージ図▼

NavHost(ナビゲーション全体)
├─ backStack[0]: home
├─ backStack[1]: detail/{id=123} ← これが backStackEntry

画面が切り替わるたびに、NavBackStackEntry がスタックされ、現在表示中の画面の backStackEntry を参照して、引数などを取り出します。

拡張関数でNavGraphを分けて整理する

アプリが成長して画面数が増えると、すべてのルートを1つの NavHost 内に composable("〜") で直接書いていくのは可読性や保守性が悪くなりやすいです。

そこで、ナビゲーショングラフの一部を関数として分割して定義する方法がおすすめです。

fun NavGraphBuilder.mypageGraph(navController: NavController) {
    composable("mypage/coupon") {
        MypageCouponScreen()
    }
    composable("mypage/coupon/detail/{id}") { backStackEntry ->
        val id = backStackEntry.arguments?.getString("id") ?: ""
        MypageCouponDetailScreen(videoId = id)
    }
}

このように、NavGraphBuilder をレシーバーとした関数を作ることで、 その中に composable() を自由に定義できます。
mypage/coupon", mypage/coupon/detail/{id} のように機能ごとのルートに名前空間をつけるのも整理に効果的です。

NavHost内で、上記のように定義したNavGraphを使います。

@Composable
fun AppNavGraph(navController: NavHostController) {
    NavHost(navController = navController, startDestination = "video/home") {
        mypageGraph(navController) // ← グラフを所属させる
    }
}

Fragmentを完全に手放すべき?

基本的に、Jetpack Compose でアプリを全部構築するなら Fragment は不要です。
ただし、以下のような場合は使用を検討しましょう:

  • Google Map / WebView など、View基盤のライブラリを含む場合
  • 現行アプリに XML ベースの UI が残っている
  • ComposeとXMLの 段階的な切り替え期間

まとめ

おつかれさまでした。いかがでしたでしょうか!

Jetpack Compose の登場によって、Android開発はよりシンプルで直感的なコードで構築できるようになりました。
今後は、「Fragmentを削除する」のではなく「使わずに済む」のが自然なデザインになるといえるでしょう。

すだ

技術者としてのキャリアパスを次のレベルへと進めたい皆様、
未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

それではまた次の記事でお会いしましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社テックブログをご愛読いただきありがとうございます。
当テックブログを運用している株式会社メモリアインクは、
【正社員】還元率83%
【フリーランス】マージン一律5万円で案件のご紹介
と、エンジニアの皆様に分かりやすい形で稼げる仕組みを構築し提供させていただいております。

コメント

コメントする

目次