- Compose Destinationsの概要
- v1とv2の違い
- 基本的なセットアップと使い方
公式のReadmeの説明にはこう書いてありました。
A KSP library that processes annotations and generates code that uses Official Jetpack Compose Navigation under the hood. It hides the complex, non-type-safe and boilerplate code you would have to write otherwise.
https://github.com/raamcosta/compose-dhttps://github.com/raamcosta/compose-destinations/blob/main/README.mdestinations
No need to learn a whole new framework to navigate – most APIs are either the same as with the Jetpack Components or inspired by them.
要約すると以下のような感じだと思います。
- KSPライブラリです
- 公式のJetpack Compose Navigationを使うために必要な「非タイプセーフ」「定型的なコード」を減らすためのものです
アニメーションの強化と、コンパイル時のチェックの改善が主な変更点のようです。
全体的に拡張性を向上させた代わりに、記述する部分が多少増えた部分もあるようです。
以下のようなナビゲーショングラフの定義などが必須になりました
// v1まで
@Destination
// v2から
@Destination<RootGraph>
まずは依存関係を設定します。
今回は、v2用の設定なのでv1を使いたい場合は使い方が異なる可能性があります。
libs.versions.toml
に以下の記述を追加します。
[versions]
# ........
ksp = "1.9.0-1.0.13"
composeDestinations = "2.1.0-beta06"
[libraries]
# ........
composeDestinations = { group = "io.github.raamcosta.compose-destinations", name = "core", version.ref = "composeDestinations" }
composeDestinationsBottomSheet = { group = "io.github.raamcosta.compose-destinations", name = "bottom-sheet", version.ref = "composeDestinations" }
composeDestinationsKsp = { group = "io.github.raamcosta.compose-destinations", name = "ksp", version.ref = "composeDestinations" }
[bundles]
composeDestinations-bundle = ["composeDestinations", "composeDestinationsBottomSheet"]
[plugins]
# ........
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ここで追加したのは、
- ksp : コンパイル時に処理を追加したい場合に使用するプラグイン
- composeDestinations-core : ComposeDestinationsのコア機能ライブラリ
- composeDestinations-bottom-sheet : ComposeDestinationsでBottomSheetへの遷移を行いたい場合に使用する
- composeDestinations-ksp : コンパイル時に自動でNavGraphを作成するためのKspプラグイン
次に、build.gradle.ksp(Module:)
に以下の設定を追加します。
plugins {
// ......
alias(libs.plugins.ksp)
}
// ......
dependencies {
// ......
implementation(libs.bundles.composeDestinations.bundle)
ksp(libs.composeDestinationsKsp)
}
以上で依存関係の定義は完了したので、コンパイルして問題が発生しなければ導入成功です。
このライブラリは画面をコンポーネントとして完全に分離して書くことができます。
BottomSheetやポップアップを使いたい場合でも、Composableなメソッドに完全に分離して記述することができます。
まずは、画面の作成をします。
以下のようなアノテーションと引数を追加することで画面遷移ができるComposableを作成できます
@Destination<RootGraph>()
@Composable
fun HomeScreen(
navigator: DestinationsNavigator, modifier: Modifier = Modifier
) {
// 画面の実装
}
通常のComposableな画面を作るときと違うのは、以下の点です
- @Destination<RootGraph> : このアノテーションをつけることでRootGraphという名前のグラフに画面を追加する
- navigator: DestinationsNavigator : これはDestinationアノテーションを付けて遷移先として設定している場合は必ず必要です。
実際に以下のようにComposableな画面を2つ作成して、どちらも同じようにボタンを真ん中に配置します。
Destinationアノテーションを付けて同じRootGraph
グループに所属させます。
このときにHomeScreenのDestinationアノテーションに設定しているstart = true
というのがRootGraphを表示するときに最初に表示される画面になります。
ボタンを押したときに画面遷移をするようにしたいので、処理を追加したいのですが、ComposeDestinationsが生成するクラスが必要になるので、一旦コンパイルを走らせます。
@Destination<RootGraph>(start = true)
@Composable
fun HomeScreen(
navigator: DestinationsNavigator, modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxSize()
) {
Button(onClick = {
// 後で実装
}) {
Text(text = "通常画面遷移")
}
}
}
@Destination<RootGraph>()
@Composable
fun DestinationScreen(navigator: DestinationsNavigator, modifier: Modifier = Modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxSize()
) {
Button(onClick = {
// 後で実装
}) {
Text(text = "Back Home")
}
}
}
ここで一度コンパイルを走らせると、画面遷移に必要な情報が自動で生成されます。
コンパイルが正常に終了したあとに、画面遷移の処理を記述します。
以下は、それぞれのComposable関数内のボタン押下時の処理です。
// HomeScreen
Button(onClick = {
// 指定した画面に遷移する
navigator.navigate(DestinationScreenDestination)
}) {
Text(text = "通常画面遷移")
}
// DestinationScreen
Button(onClick = {
// 遷移前の画面に戻る
navigator.navigateUp()
}) {
Text(text = "Back Home")
}
ここでDestinationScreenDestination
という作成していない定義を指定していますが、これは、ComposeDestinationライブラリが自動で生成したクラスになります。
このクラスの生成規則は以下のようになっています。
- @Destinationをつけた@Composable関数についてのクラスを生成する
- 関数名 + Destinationとなるように生成する
このクラスを引数に渡すことで指定したクラスの生成もとの画面に遷移できます。
最後に呼び出し元にのエントリーポイントとして画面を直接制定するのではなく、以下のような記述を追加します。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavhostExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DestinationsNavHost(
navGraph = NavGraphs.root
)
}
}
}
}
}
いつもと違うのは
DestinationsNavHost(
navGraph = NavGraphs.root
)
この部分ですね。
この部分で表示するナビゲーショングラフを指定して画面を表示させます。
この場合、root
という名前のナビゲーショングラフのstart = true
になっている画面を一番最初に表示します
このrootというのは、RootGraph
のことです。
自動生成されます。
internal object NavGraphs {
val root = RootNavGraph
}
/**
* Generated from [RootGraph]
*
* * 🗺️[RootGraph]
* * ∙∙∙∙∙∙∙∙↳📍🏁[HomeScreen]
* * ∙∙∙∙∙∙∙∙↳📍[DestinationScreen]
*/
@Keep
public data object RootNavGraph : BaseRoute(), DirectionNavHostGraphSpec {
override val startRoute: TypedRoute<Unit> = HomeScreenDestination
override val destinations: List<DestinationSpec> get() = listOf(
HomeScreenDestination,
DestinationScreenDestination,
)
override val defaultTransitions: NavHostAnimatedDestinationStyle = NoTransitions
override val route: String = "root"
}
RootGraph
はナビゲーショングラフの一例で、画面をグループ化するのに使います。
ライブラリ内でのRootGraph
定義は以下のようになっています。
@NavHostGraph
annotation class RootGraph
これを見てわかるように、ただの識別子のような使い方をされています。
RootGraphについては、最初から用意されているナビゲーショングラフですが、もちろん自分で作成する事もできます。
オリジナルのナビゲーショングラフの作成方法については、渡しが使う必要が出てくるか、要望が多い場合にまとめ記事を作成する予定です。
通常のBottomSheetを作成しようとすると管理するために冗長な記述が必要になります。
(画面が閉じるまでコルーチンで待機したり..etc)
ComposeDestinationsを使用することで、BottomSheetも通常の画面と同じように使用することができるようになります。
以下は、BottomSheet遷移を追加するための手順です。
- Composable画面の作成
- 画面遷移元の動作を追加
- エントリーポイントの改修
BottomSheetを作成するには通常の画面遷移の設定に加えて、style
の設定が必要になります。
DestinationStyleBottomSheet::class
をstyle
に設定する必要があります。
他の設定は同じで、前の画面に戻る際の処理も同じでOKです
@Destination<RootGraph>(style = DestinationStyleBottomSheet::class)
@Composable
fun DestinationBottomScreen(navigator: DestinationsNavigator, modifier: Modifier = Modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxWidth().padding(vertical = 10.dp)
) {
Button(onClick = {
navigator.navigateUp()
}) {
Text(text = "Close BottomSheet")
}
}
}
画面遷移時に必要なクラスを生成するためにこのタイミングでコンパイルを走らせておいてください
次は遷移元のBottomSheetの呼び出しロジックを追加します。
と、いったものの通常の画面遷移と何も変わりません。
Button(onClick = {
navigator.navigate(DestinationBottomScreenDestination)
}) {
Text(text = "ボトムシートを展開")
}
最後に呼び出し元に少し調整を加える必要があります。
上記2つの設定だけでもエラーは発生しませんが、遷移ボタンを押しても何も起こりません。
「BottomSheet」を画面遷移で使いたい場合は、MainActivity(エントリーポイント)にModalBottomSheetLayoutを追加して、DestinationsNavHost
ラップする必要があります。
更に、navControllerを取得して、bottomSheetのイベントなどを紐づける必要もあります。
navControllerはエントリーポイントに渡す必要があります。
これは、初回の設定をしてしまえばそれ移行は意識することなく使うことができるようになります。
val navController = rememberNavController()
val bottomSheetNavigator = rememberBottomSheetNavigator()
navController.navigatorProvider += bottomSheetNavigator
ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator) {
// DestinationsNavHostなどの通常の設定
// navControllerを渡す必要はある
}
実際に使うときの記述は以下のようになると思います。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavhostExampleTheme {
val navController = rememberNavController()
val bottomSheetNavigator = rememberBottomSheetNavigator()
navController.navigatorProvider += bottomSheetNavigator
ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DestinationsNavHost(
navGraph = NavGraphs.root,
navController = navController
)
}
}
}
}
}
}
最後にこれまで追加したコードをまとめました。
最終形
package com.example.navhostexample
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.navigation.ModalBottomSheetLayout
import androidx.compose.material.navigation.rememberBottomSheetNavigator
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.rememberNavController
import androidx.navigation.plusAssign
import com.example.navhostexample.ui.theme.NavhostExampleTheme
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.bottomsheet.spec.DestinationStyleBottomSheet
import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.generated.destinations.DestinationBottomScreenDestination
import com.ramcosta.composedestinations.generated.destinations.DestinationScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavhostExampleTheme {
val navController = rememberNavController()
val bottomSheetNavigator = rememberBottomSheetNavigator()
navController.navigatorProvider += bottomSheetNavigator
ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DestinationsNavHost(
navGraph = NavGraphs.root,
navController = navController
)
}
}
}
}
}
}
@Destination<RootGraph>(start = true)
@Composable
fun HomeScreen(
navigator: DestinationsNavigator, modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxSize()
) {
Button(onClick = {
navigator.navigate(DestinationScreenDestination)
}) {
Text(text = "通常画面遷移")
}
Button(onClick = {
navigator.navigate(DestinationBottomScreenDestination)
}) {
Text(text = "ボトムシートを展開")
}
}
}
@Destination<RootGraph>()
@Composable
fun DestinationScreen(navigator: DestinationsNavigator, modifier: Modifier = Modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxSize()
) {
Button(onClick = {
navigator.navigateUp()
}) {
Text(text = "Back Home")
}
}
}
@Destination<RootGraph>(style = DestinationStyleBottomSheet::class)
@Composable
fun DestinationBottomScreen(navigator: DestinationsNavigator, modifier: Modifier = Modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxWidth().padding(vertical = 10.dp)
) {
Button(onClick = {
navigator.navigateUp()
}) {
Text(text = "Close BottomSheet")
}
}
}
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun GreetingPreview() {
NavhostExampleTheme {}
}
動作
ComposeDestinationを使うことで、今まで冗長になっていた画面遷移の記述をより簡潔に書くことができるようになりました。