Decompose
Decompose copied to clipboard
Compose native iOS and macOS support
Latest builds of JetBrains Compose support native iOS and macOS targets. Let's try to add it to extensions-compose-jetbrains
module. Since it's far from stable, a separate branch seems reasonable.
Status update: experimental -native-compose
versions of Decompose with Compose for Darwin are published from the compose-darwin
branch.
Created the branch: compose-darwin.
The compiler crashes on CI, filed: https://github.com/JetBrains/compose-jb/issues/2007
Web (Canvas) support is now enabled. The new branch for all experimental targets is compose-experimental.
Hi @arkivanov, first of all thank you to create this amazing library. I have been trying to integrate Decompose in Kotlin Multiplatform Compose project. I tried my best to thoroughly review your documentation and samples. I am unable to resolve following error on iOS:
Task :shared:linkPodDebugFrameworkIosX64 FAILED error: Following dependencies exported in the podDebugFramework binary are not specified as API-dependencies of a corresponding source set: FAILURE: Build failed with an exception.
- What went wrong: Execution failed for task ':shared:linkPodDebugFrameworkIosX64'.
Following dependencies exported in the podDebugFramework binary are not specified as API-dependencies of a corresponding source set:
Files: [/Users/Admin/.gradle/caches/modules-2/files-2.1/com.arkivanov.decompose/decompose-iosx64/2.0.0-alpha-02/c681ede152c090cec61131c93a90d75bac1c43fc/decompose.klib]
Please add them in the API-dependencies and rerun the build.
Here are the chunks of my build.gradle.kts:
cocoapods {
....
framework {
baseName = "shared"
isStatic = true
export("com.arkivanov.decompose:decompose:2.0.0-alpha-02")
export("com.arkivanov.essenty:lifecycle:1.1.0")
}
extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
}
sourceSets {
val commonMain by getting {
implementation("com.arkivanov.decompose:decompose:2.0.0-alpha-02")
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-compose-experimental-alpha-02")
}
}
I also tried to add dependencies like here, but no luck probably because it doesn't use cocoapods.
Please let me know if I am missing anything. Thank you.
@blueberry404 any module that is exported to the framework must be specific as api
dependency, not implementation
. You also missing the dependencies {}
block. See the example.
Thank you for quick response. Sorry, missing dependency block was pasting typo at my end. Key was to use api
dependencies for both in iOSMain. I am able to compile it now. Thank you!
dependencies {
api("com.arkivanov.decompose:decompose:$decompose")
api("com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-compose-experimental-alpha-02")
}
Awesome! Glad it worked. Most likely you don't need to export extensions-compose-jetbrains
, as it contains extensions specifically for Compose.
Actually, I am going to write the common compose code for Android and iOS using jetbrain's compose, therefore I thought this is also I needed.
I think exporting is only required if you want co use that APIs in the iOS project (Xcode). If you are just using it from Kotlin, then you shouldn't need to export Compose extensions. Just implementation
should enough. You can try at least.
Ok I get your point now. I will try and let you know. Thank you!
Hi @arkivanov, I had a busy weekend so wasn't able to check it out. First of all, I would like to disregard my previous compilation comment because it actually compiled on android studio only, but not on xcode-iOS if I add following to the iOSMain. Sorry, I was in hurry so wasn't able to properly test on iOS at that time.
api("com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-compose-experimental-alpha-02")
As you asked, I removed above dependency and it worked perfectly for the first time. Then I added more code to test the navigation. It's working completely fine on Android, but compilation has been broken on iOS end. LifecycleRegistry
related classes are detected, but not Decompose
ones. Also, somehow class DecomposeDefaultComponentContext
is not generated anymore.
import Foundation
import shared
@objc
class LifecycleManager: NSObject, ObservableObject {
let lifecycle: LifecycleRegistry
let root: MyRootComponent
override init() {
lifecycle = LifecycleRegistryKt.LifecycleRegistry()
root = DefaultMyRootComponent(
componentContext: DecomposeDefaultComponentContext(lifecycle: lifecycle))
LifecycleRegistryExtKt.create(lifecycle)
}
deinit {
LifecycleRegistryExtKt.destroy(lifecycle)
}
}
In the shared.h
, I was only able to find its protocol, but not the implementation.

I commented out all the code related to shared and Decompose, stashed my newly added code to revert to last working state in the hope that correct files would be generated. Even cleared Derived Data, build folder multiple times of both KMM and iOS but it's not generating required files in framework. I am wondering how it worked for first time now 😄
Again, here are chunks of build.gradle
in shared module:
cocoapods {
...
framework {
baseName = "shared"
isStatic = true
export("com.arkivanov.decompose:decompose:$decompose")
export("com.arkivanov.essenty:lifecycle:1.1.0")
}
extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
}
val iosMain by creating {
dependsOn(commonMain)
dependencies {
api("com.arkivanov.decompose:decompose:$decompose")
}
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
It's hard to determine what's the cause. You certainly need api("com.arkivanov.essenty:lifecycle:1.1.0")
in your iosMain
dependencies. Also you can try cleaning your project and rebuilding it with --rerun-tasks
flag. Otherwise, it could be something related to cocoapods integration. It doesn't look specific to Decompose actually.
I ran into a similar issue. Maybe this thread at slack helps: https://kotlinlang.slack.com/archives/C03H3N51SKT/p1683725214780229
Hello, i have a error for implementation("com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02") in the compose multiplatform. Can u help me? @arkivanov
Error:
A problem occurred configuring project ':common'.
Could not resolve all dependencies for configuration ':common:iosArm64CompileKlibraries'. Could not resolve com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02. Required by: project :common > No matching variant of com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 was found. The consumer was configured to find a usage of 'kotlin-api' of a library, preferably optimized for non-jvm, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native', attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm64' but: - Variant 'debugApiElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares an API of a library: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'debugRuntimeElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a runtime of a library: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'debugSourcesElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a runtime of a component: - Incompatible because this component declares documentation, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a library, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'jvmApiElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares an API of a library: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'jvmRuntimeElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a runtime of a library: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'jvmSourcesElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a runtime of a component: - Incompatible because this component declares documentation, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a library, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'metadataApiElements' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a library: - Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'metadataSourcesElements' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02: - Incompatible because this component declares a usage of 'kotlin-runtime' of documentation, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a usage of 'kotlin-api' of a library, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'releaseApiElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares an API of a library: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'releaseRuntimeElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a runtime of a library: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64') - Variant 'releaseSourcesElements-published' capability com.arkivanov.decompose:extensions-compose-jetbrains:2.0.0-alpha-02 declares a runtime of a component: - Incompatible because this component declares documentation, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a library, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native' - Other compatible attributes: - Doesn't say anything about its target Java environment (preferred optimized for non-jvm) - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64')
--In addition, when i update the versions of implementation("com.arkivanov.decompose:decompose:2.1.0-compose-experimental-alpha-01") and implementation("com.arkivanov.decompose:extensions-compose-jetbrains:2.1.0-compose-experimental-alpha-01"), I have a different error and the above error is gone. The new error: (in terminal with ./gradlew build --warning-mode all --stacktrace)
Execution failed for task ':android:checkDebugAarMetadata'.
A failure occurred while executing com.android.build.gradle.internal.tasks.CheckAarMetadataWorkAction 4 issues were found when checking AAR metadata:
-
Dependency 'com.arkivanov.essenty:back-handler-android-debug:1.2.0-alpha-01' requires libraries and applications that depend on it to compile against codename "UpsideDownCake" of the Android APIs.
:android is currently compiled against android-33.
Recommended action: Use a different version of dependency 'com.arkivanov.essenty:back-handler-android-debug:1.2.0-alpha-01', or set compileSdkPreview to "UpsideDownCake" in your build.gradle file if you intend to experiment with that preview SDK.
-
Dependency 'androidx.activity:activity:1.8.0-alpha04' requires libraries and applications that depend on it to compile against codename "UpsideDownCake" of the Android APIs.
:android is currently compiled against android-33.
Recommended action: Use a different version of dependency 'androidx.activity:activity:1.8.0-alpha04', or set compileSdkPreview to "UpsideDownCake" in your build.gradle file if you intend to experiment with that preview SDK.
-
Dependency 'androidx.activity:activity-ktx:1.8.0-alpha04' requires libraries and applications that depend on it to compile against codename "UpsideDownCake" of the Android APIs.
:android is currently compiled against android-33.
Recommended action: Use a different version of dependency 'androidx.activity:activity-ktx:1.8.0-alpha04', or set compileSdkPreview to "UpsideDownCake" in your build.gradle file if you intend to experiment with that preview SDK.
-
Dependency 'androidx.activity:activity-compose:1.8.0-alpha04' requires libraries and applications that depend on it to compile against codename "UpsideDownCake" of the Android APIs.
:android is currently compiled against android-33.
Regarding the first error, I think you should use experiment-compose
version if you support iOS. Try using 2.0.0-experimental-compose-beta-01
.
Regarding the second error, as mentioned in the release notes, 2.1.0-alpha
versions are compiled with compileSdkPreview = UpsideDownCake
. This is currently required for the new predictive back gesture on Android U. So, either enable that in your project as well, or use 2.0.0-beta-01
.
Thanks for your answer. Decompose version with "2.0.0-compose-experimental-beta-01" is perfect. Everything is perfect.
- with "2.0.0-beta-01", i got error for decompose-extensions.
- with "2.1.0-compose-experimental-alpha-01", I just discovered the documentation. (https://developer.android.com/about/versions/14/setup-sdk) it is working. Thank you.
Let's also track wasm
here, from #375.
Just checking if my lifecycle hook into iOS compose is correct, I adapted the SwiftUI to the iOS compose example:
struct iOSApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self)
var appDelegate: AppDelegate
var rootHolder: AppHolder { appDelegate.appHolder }
var body: some Scene {
WindowGroup {
ContentView(root: rootHolder.root)
}
}
}
import Foundation
import shared
class AppHolder : ObservableObject {
let lifecycle: LifecycleRegistry
let root: AppComponent
init() {
lifecycle = LifecycleRegistryKt.LifecycleRegistry()
root = DefaultAppComponent(
componentContext: DefaultComponentContext(lifecycle: lifecycle)
)
LifecycleRegistryExtKt.create(lifecycle)
}
deinit {
// Destroy the root component before it is deallocated
LifecycleRegistryExtKt.destroy(lifecycle)
}
}
struct ComposeView: UIViewControllerRepresentable {
var root: AppComponent
func makeUIViewController(context: Context) -> UIViewController {
Main_iosKt.MainViewController(root: root)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var root: AppComponent
var body: some View {
ComposeView(root: root)
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler
}
}
import Foundation
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
let appHolder: AppHolder = AppHolder()
}
It seems to compile and work, I obviously missed out this part:
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .background: LifecycleRegistryExtKt.stop(rootHolder.lifecycle)
case .inactive: LifecycleRegistryExtKt.pause(rootHolder.lifecycle)
case .active: LifecycleRegistryExtKt.resume(rootHolder.lifecycle)
@unknown default: break
}
}
Not sure if thats needed for iOS Compose?
Looks correct @chrisjenx. Just one possible point to improve in case you find it useful. I find it cleaner to destroy the root component on willTerminateNotification
, instead of the deinit
block. Check out the example here.
linkPodDebugFrameworkIosX64 FAILED
I also had above reported error for several hours with the correct setup like on the example
turns out I use the 1.0.0 version for the essenty dependency, and it finally can compile after I update it to 1.1.0, just in case anyone has the same issue as me :)
I changed to using the new AutomaticLifecycle, also not tried web yet, but we're using iOS in production now, not seen anything major that jumps out, need to test out persistance, but imo it seems pretty stable, iOS can probably not be considered "experimental" esp after moving to serilization plugin
Oh, it was only considered experimental because it was defined so by JetBrains. Now since we don't need org.jetbrains.compose.experimental.uikit.enabled=true
anymore, the support of Compose for iOS will be moved to the stable branch in v3.0. Being tracked here: https://github.com/arkivanov/Decompose/issues/451.
Awesome, thank you for your superb work!
Hi! I'm trying to implement shared navigation in my KMP project with shared UI. It works fine with Android, but I encountered an error with iOS:
error: Overload resolution ambiguity: public fun <C : Any, T : Any> Children(stack: ChildStack<TypeVariable(C), TypeVariable(T)>, modifier: Modifier = ..., animation: StackAnimation<TypeVariable(C), TypeVariable(T)>? = ..., content: (child: Child.Created<TypeVariable(C), TypeVariable(T)>) -> Unit): Unit defined in com.arkivanov.decompose.extensions.compose.jetbrains.stack public fun <C : Any, T : Any> Children(stack: Value<ChildStack<TypeVariable(C), TypeVariable(T)>>, modifier: Modifier = ..., animation: StackAnimation<TypeVariable(C), TypeVariable(T)>? = ..., content: (child: Child.Created<TypeVariable(C), TypeVariable(T)>) -> Unit): Unit defined in com.arkivanov.decompose.extensions.compose.jetbrains.stack
App.kt:34:9: error: Cannot infer a type for this parameter. Please specify it explicitly.
App.kt:36:50: error: @Composable invocations can only happen from the context of a @Composable function
App.kt:36:50: error: @Composable invocations can only happen from the context of a @Composable function
RootComponent.kt:24:32: error: Type mismatch: inferred type is Any but com.arkivanov.essenty.parcelable.Parcelable /* = com.arkivanov.parcelize.darwin.Parcelable */ was expected
RootComponent.kt:24:32: error: Type mismatch: inferred type is Any but RootComponent.Configuration was expected
I suspect there is some problems with iOS dependencies, but I was unable to track the exact cause. My build.gradle looks like this. Please let me know if I am missing anything. Thank you.
kotlin {
targetHierarchy.default()
android {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
```
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
isStatic = true
val decompose_version = "2.2.0-compose-experimental"
val essenty_version = "1.3.0"
export("com.arkivanov.decompose:decompose:$decompose_version")
export("com.arkivanov.essenty:lifecycle:$essenty_version")
// Optional, only if you need state preservation on Darwin (Apple) targets
export("com.arkivanov.essenty:state-keeper:$essenty_version")
}
}
sourceSets {
val androidMain by getting {
dependencies {
implementation ("com.google.accompanist:accompanist-systemuicontroller:0.27.0")
}
dependsOn(commonMain.get())
}
named("commonMain") {
dependencies {
api(compose.foundation)
api(compose.animation)
val decomposeVersion = "2.2.0"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.arkivanov.decompose:decompose:$decomposeVersion-compose-experimental")
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:$decomposeVersion-compose-experimental")
implementation("com.russhwolf:multiplatform-settings:1.1.1")
implementation("com.russhwolf:multiplatform-settings-no-arg:1.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1")
implementation("com.moriatsushi.insetsx:insetsx:0.1.0-alpha10")
//put your multiplatform dependencies here
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
implementation("dev.icerock.moko:resources:0.23.0")
implementation("dev.icerock.moko:resources-compose:0.23.0")
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
}
}
}
sourceSets {
named("iosMain") {
dependencies {
val decompose_version = "2.2.0-compose-experimental"
val essenty_version = "1.3.0"
api("com.arkivanov.decompose:decompose:$decompose_version")
api("com.arkivanov.essenty:lifecycle:$essenty_version")
}
}
}
@markvtailor it's difficult to help without the reproducer or at least the code. Feel free to file a separate issue with the additional information.
Starting with Decompose 3.0.0-alpha01 all Compose variants are supported without any version suffixes.
🥳 Nice work!