kmp-navigation
KMP(Kotlin Multiplatform)でVoyagerやDecomposeといったナビゲーションライブラリを使い、ComposeやSwiftUIなど各プラットフォームに合わせた画面遷移を実装するSkill。
📜 元の英語説明(参考)
Navigation libraries for KMP. Voyager, Decompose, and platform-specific navigation (Compose Navigation, SwiftUI).
🇯🇵 日本人クリエイター向け解説
KMP(Kotlin Multiplatform)でVoyagerやDecomposeといったナビゲーションライブラリを使い、ComposeやSwiftUIなど各プラットフォームに合わせた画面遷移を実装するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o kmp-navigation.zip https://jpskill.com/download/10672.zip && unzip -o kmp-navigation.zip && rm kmp-navigation.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10672.zip -OutFile "$d\kmp-navigation.zip"; Expand-Archive "$d\kmp-navigation.zip" -DestinationPath $d -Force; ri "$d\kmp-navigation.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
kmp-navigation.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
kmp-navigationフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
KMP Navigation
Kotlin Multiplatform向けのクロスプラットフォームなナビゲーション戦略です。
ナビゲーションの選択肢
1. Voyager (推奨)
// build.gradle.kts
dependencies {
implementation("cafe.adriel.voyager:voyager-navigator:1.0.0")
implementation("cafe.adriel.voyager:voyager-screen-model:1.0.0")
}
// commonMain/kotlin/navigation/VoyagerNavigation.kt
@Serializable
data object HomeScreen : Screen
@Serializable
data class DetailScreen(val id: String) : Screen
@Composable
fun AppNavigation() {
Navigator(HomeScreen) { navigator ->
VoyagerNavigation(
navigator = navigator,
screenModels = { rememberScreenModel { AppScreenModel() } }
)
}
}
@Composable
fun HomeScreen() {
Screen { // VoyagerのScreen composable
val navigator = LocalNavigator.currentOrThrow
Button(onClick = { navigator.push(DetailScreen("123")) }) {
Text("Go to Detail")
}
}
}
2. Decompose
// build.gradle.kts
dependencies {
implementation("com.arkivanov.decompose:decompose:2.1.0")
implementation("com.arkivanov.decompose:extensions-compose-jetpack:2.1.0")
}
// commonMain/kotlin/navigation/DecomposeNavigation.kt
sealed class Config : Parcelable {
@Parcelize
data object Home : Config()
@Parcelize
data class Detail(val id: String) : Config()
}
@Composable
fun AppNavigation() {
val navigator = rememberNavigator()
Decomanavigation(
navigator = navigator,
initialConfiguration = Config.Home
) {
when (val config = it.configuration) {
is Config.Home -> HomeScreen(
onNavigateToDetail = { navigator.push(Config.Detail(it)) }
)
is Config.Detail -> DetailScreen(
id = config.id,
onBack = { navigator.pop() }
)
}
}
}
3. プラットフォームネイティブブリッジ
// commonMain/kotlin/navigation/Navigator.kt
sealed class Screen : Parcelable {
@Parcelize
data object Home : Screen()
@Parcelize
data class Detail(val id: String) : Screen()
}
interface Navigator {
val navigationStack: StateFlow<List<Screen>>
fun navigateTo(screen: Screen)
fun navigateBack()
}
expect class Navigator() : Navigator
// androidMain - Compose Navigation
actual class Navigator : Navigator {
private val _stack = MutableStateFlow(listOf(Screen.Home))
override val navigationStack: StateFlow<List<Screen>> = _stack.asStateFlow()
@Composable
fun SetupNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(
onNavigateToDetail = { navController.navigate("detail/$it") }
)
}
composable("detail/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id") ?: ""
DetailScreen(id, onBack = { navController.popBackStack() })
}
}
}
}
// iosMain - SwiftUI Navigation wrapper
actual class Navigator : Navigator {
private val _stack = MutableStateFlow(listOf(Screen.Home))
override val navigationStack: StateFlow<List<Screen>> = _stack.asStateFlow()
fun toSwiftUI() -> some View {
// SwiftUI NavigationStackへのブリッジ
}
}
タブナビゲーション
Voyager Tabs
// commonMain/kotlin/navigation/TabNavigation.kt
enum class Tab {
HOME,
SEARCH,
PROFILE
}
@Composable
fun TabNavigation() {
val navigator = rememberTabNavigator()
TabNavigator(tab = navigator.current) {
// タブのコンテンツ
}
}
Decompose Tabs
// commonMain/kotlin/navigation/Tabs.kt
enum class Tab {
HOME,
SEARCH,
PROFILE
}
@Composable
fun Tabs(
navigator: StackNavigator,
selectedTab: MutableState<Tab>
) {
Row {
Tab.values().forEach { tab ->
Button(
onClick = { selectedTab.value = tab }
) {
Text(tab.name)
}
}
}
}
ディープリンク
// commonMain/kotlin/navigation/DeepLink.kt
sealed class Screen : Parcelable {
@Parcelize
data object Home : Screen()
@Parcelize
data class Detail(val id: String) : Screen()
companion object {
fun fromDeepLink(url: String): Screen? {
return when {
url.contains("/home") -> Home
"/detail/([a-z0-9]+)".toRegex().find(url) != null -> {
val id = "/detail/([a-z0-9]+)".toRegex().find(url)!!.groupValues[1]
Detail(id)
}
else -> null
}
}
}
}
ナビゲーション引数
型安全な引数
// ✅ 引数を持つSealed class
@Serializable
sealed class Screen : Parcelable {
@Parcelize
data object Home : Screen()
@Parcelize
data class UserDetail(
val userId: String,
val section: String? = null
) : Screen()
@Parcelize
data class EditItem(
val itemId: String,
val mode: EditMode = EditMode.View
) : Screen()
}
@Serializable
enum class EditMode {
View, Edit, Create
}
状態の保持
Voyager ScreenModel
class DetailScreenModel(
private val userId: String,
private val repository: UserRepository
) : ScreenModel {
val user = mutableStateOf<User?>(null)
val isLoading = mutableStateOf(false)
init {
loadUser()
}
private fun loadUser() {
viewModelScope.launch {
isLoading.value = true
user.value = repository.getUser(userId)
isLoading.value = false
}
}
}
@Composable
fun DetailScreen(
userId: String,
onBack: () -> Unit
) {
val model = rememberScreenModel { DetailScreenModel(userId) }
if (model.isLoading.value) {
Cir 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
KMP Navigation
Cross-platform navigation strategies for Kotlin Multiplatform.
Navigation Options
1. Voyager (Recommended)
// build.gradle.kts
dependencies {
implementation("cafe.adriel.voyager:voyager-navigator:1.0.0")
implementation("cafe.adriel.voyager:voyager-screen-model:1.0.0")
}
// commonMain/kotlin/navigation/VoyagerNavigation.kt
@Serializable
data object HomeScreen : Screen
@Serializable
data class DetailScreen(val id: String) : Screen
@Composable
fun AppNavigation() {
Navigator(HomeScreen) { navigator ->
VoyagerNavigation(
navigator = navigator,
screenModels = { rememberScreenModel { AppScreenModel() } }
)
}
}
@Composable
fun HomeScreen() {
Screen { // Voyager's Screen composable
val navigator = LocalNavigator.currentOrThrow
Button(onClick = { navigator.push(DetailScreen("123")) }) {
Text("Go to Detail")
}
}
}
2. Decompose
// build.gradle.kts
dependencies {
implementation("com.arkivanov.decompose:decompose:2.1.0")
implementation("com.arkivanov.decompose:extensions-compose-jetpack:2.1.0")
}
// commonMain/kotlin/navigation/DecomposeNavigation.kt
sealed class Config : Parcelable {
@Parcelize
data object Home : Config()
@Parcelize
data class Detail(val id: String) : Config()
}
@Composable
fun AppNavigation() {
val navigator = rememberNavigator()
Decomanavigation(
navigator = navigator,
initialConfiguration = Config.Home
) {
when (val config = it.configuration) {
is Config.Home -> HomeScreen(
onNavigateToDetail = { navigator.push(Config.Detail(it)) }
)
is Config.Detail -> DetailScreen(
id = config.id,
onBack = { navigator.pop() }
)
}
}
}
3. Platform-Native Bridge
// commonMain/kotlin/navigation/Navigator.kt
sealed class Screen : Parcelable {
@Parcelize
data object Home : Screen()
@Parcelize
data class Detail(val id: String) : Screen()
}
interface Navigator {
val navigationStack: StateFlow<List<Screen>>
fun navigateTo(screen: Screen)
fun navigateBack()
}
expect class Navigator() : Navigator
// androidMain - Compose Navigation
actual class Navigator : Navigator {
private val _stack = MutableStateFlow(listOf(Screen.Home))
override val navigationStack: StateFlow<List<Screen>> = _stack.asStateFlow()
@Composable
fun SetupNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(
onNavigateToDetail = { navController.navigate("detail/$it") }
)
}
composable("detail/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id") ?: ""
DetailScreen(id, onBack = { navController.popBackStack() })
}
}
}
}
// iosMain - SwiftUI Navigation wrapper
actual class Navigator : Navigator {
private val _stack = MutableStateFlow(listOf(Screen.Home))
override val navigationStack: StateFlow<List<Screen>> = _stack.asStateFlow()
fun toSwiftUI() -> some View {
// Bridge to SwiftUI NavigationStack
}
}
Tab Navigation
Voyager Tabs
// commonMain/kotlin/navigation/TabNavigation.kt
enum class Tab {
HOME,
SEARCH,
PROFILE
}
@Composable
fun TabNavigation() {
val navigator = rememberTabNavigator()
TabNavigator(tab = navigator.current) {
// Tab content
}
}
Decompose Tabs
// commonMain/kotlin/navigation/Tabs.kt
enum class Tab {
HOME,
SEARCH,
PROFILE
}
@Composable
fun Tabs(
navigator: StackNavigator,
selectedTab: MutableState<Tab>
) {
Row {
Tab.values().forEach { tab ->
Button(
onClick = { selectedTab.value = tab }
) {
Text(tab.name)
}
}
}
}
Deep Linking
// commonMain/kotlin/navigation/DeepLink.kt
sealed class Screen : Parcelable {
@Parcelize
data object Home : Screen()
@Parcelize
data class Detail(val id: String) : Screen()
companion object {
fun fromDeepLink(url: String): Screen? {
return when {
url.contains("/home") -> Home
"/detail/([a-z0-9]+)".toRegex().find(url) != null -> {
val id = "/detail/([a-z0-9]+)".toRegex().find(url)!!.groupValues[1]
Detail(id)
}
else -> null
}
}
}
}
Navigation Arguments
Type-Safe Arguments
// ✅ Sealed class with arguments
@Serializable
sealed class Screen : Parcelable {
@Parcelize
data object Home : Screen()
@Parcelize
data class UserDetail(
val userId: String,
val section: String? = null
) : Screen()
@Parcelize
data class EditItem(
val itemId: String,
val mode: EditMode = EditMode.View
) : Screen()
}
@Serializable
enum class EditMode {
View, Edit, Create
}
State Preservation
Voyager ScreenModel
class DetailScreenModel(
private val userId: String,
private val repository: UserRepository
) : ScreenModel {
val user = mutableStateOf<User?>(null)
val isLoading = mutableStateOf(false)
init {
loadUser()
}
private fun loadUser() {
viewModelScope.launch {
isLoading.value = true
user.value = repository.getUser(userId)
isLoading.value = false
}
}
}
@Composable
fun DetailScreen(
userId: String,
onBack: () -> Unit
) {
val model = rememberScreenModel { DetailScreenModel(userId) }
if (model.isLoading.value) {
CircularProgressIndicator()
} else {
model.user.value?.let { user ->
UserContent(user, onBack)
}
}
}
Remember: Choose navigation library based on project needs. Voyager for simplicity, Decompose for control, platform-native for full platform feature support.