Decompose icon indicating copy to clipboard operation
Decompose copied to clipboard

Compose native iOS and macOS support

Open arkivanov opened this issue 2 years ago • 2 comments

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.

arkivanov avatar Apr 14 '22 08:04 arkivanov

Created the branch: compose-darwin.

arkivanov avatar Apr 14 '22 21:04 arkivanov

The compiler crashes on CI, filed: https://github.com/JetBrains/compose-jb/issues/2007

arkivanov avatar Apr 15 '22 09:04 arkivanov

Web (Canvas) support is now enabled. The new branch for all experimental targets is compose-experimental.

arkivanov avatar Feb 02 '23 18:02 arkivanov

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 avatar May 06 '23 17:05 blueberry404

@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.

arkivanov avatar May 06 '23 18:05 arkivanov

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

blueberry404 avatar May 06 '23 18:05 blueberry404

Awesome! Glad it worked. Most likely you don't need to export extensions-compose-jetbrains, as it contains extensions specifically for Compose.

arkivanov avatar May 06 '23 18:05 arkivanov

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.

blueberry404 avatar May 06 '23 18:05 blueberry404

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.

arkivanov avatar May 06 '23 19:05 arkivanov

Ok I get your point now. I will try and let you know. Thank you!

blueberry404 avatar May 06 '23 19:05 blueberry404

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.

Screenshot 2023-05-09 at 1 18 37 AM

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

blueberry404 avatar May 08 '23 20:05 blueberry404

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.

arkivanov avatar May 09 '23 09:05 arkivanov

I ran into a similar issue. Maybe this thread at slack helps: https://kotlinlang.slack.com/archives/C03H3N51SKT/p1683725214780229

wman1980 avatar May 10 '23 20:05 wman1980

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

dQqAn avatar May 30 '23 01:05 dQqAn

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.

arkivanov avatar May 30 '23 08:05 arkivanov

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.

dQqAn avatar May 30 '23 11:05 dQqAn

Let's also track wasm here, from #375.

arkivanov avatar Jun 13 '23 20:06 arkivanov

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?

chrisjenx avatar Jul 06 '23 19:07 chrisjenx

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.

arkivanov avatar Jul 07 '23 07:07 arkivanov

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

rezyfr avatar Sep 13 '23 02:09 rezyfr

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

chrisjenx avatar Nov 22 '23 18:11 chrisjenx

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.

arkivanov avatar Nov 22 '23 18:11 arkivanov

Awesome, thank you for your superb work!

chrisjenx avatar Nov 23 '23 01:11 chrisjenx

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 avatar Dec 04 '23 08:12 markvtailor

@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.

arkivanov avatar Dec 04 '23 09:12 arkivanov

Starting with Decompose 3.0.0-alpha01 all Compose variants are supported without any version suffixes.

arkivanov avatar Dec 09 '23 00:12 arkivanov

🥳 Nice work!

chrisjenx avatar Dec 09 '23 01:12 chrisjenx