Kotlin Multiplatform Compose + Desktop + Web + Mobile
Asked Answered
L

4

9

Is it possible at the moment to have a kotlin multiplatform project using compose for sharing the ui code for desktop, web, and mobile at the same time? All the examples i found only cover multiplatform with JS Front + Jvm Backend, or JVM Android + Desktop + Common Module, and I'm having trouble setting a project with all those at the same time.

I tried doing:

plugins {
    kotlin("multiplatform")
    id("org.jetbrains.compose") version "1.0.1-rc2"
    id("com.android.library")
}
kotlin {
    android()
    jvm("desktop") {
       ...
    }
    js{
       ...
    }
    sourceSets {
        val commonMain by getting {
            dependencies {
               ...
            }
        }
        val commonTest by getting {
            dependencies {
               ...
            }
        }
        val androidMain by getting {
            dependencies {
                ...
            }
        }
        val androidTest by getting {
            dependencies {
                ...
            }
        }
        val desktopMain by getting {
            dependencies {
                ...
            }
        }
        val desktopTest by getting
        val jsMain by getting{
            dependencies{
                ...
            }
        }
        val jsTest by getting {
            dependencies {
                ...
            }
        }
    }
}

But it produces the error:

:common:jsMain: Could not resolve org.jetbrains.compose.runtime:runtime:1.0.1-rc2.
Required by:
    project :common

If i comment the JS related sections it works, or if i comment all the non-js related stuff it also works

Commenting everything compose-related also works

The problem is only when combining everything

Leech answered 8/2, 2022 at 22:33 Comment(0)
L
2

Jetbrains updated the github repo with some samples:

https://github.com/JetBrains/compose-jb/tree/master/experimental/examples

It is possible now with version "1.2.0-alpha01-dev675" but not all components work and it is still in a somewhat early alpha stage.

Leech answered 2/6, 2022 at 15:16 Comment(0)
L
8

The short answer is: No, this is not possible at this time.

The JB team is working on such support, which can be tested in these examples, but for now it is experimental and there is no guarantee that it will be released any time soon. Compose JB version is now synced with Android Compose, so I expect that 1.2.0 will be released around the same time by both of them, even if Web support is not yet complete.


I have not been able to reproduce your error, but I assume that you have not removed compose.foundation and compose.material from the common dependencies.

At the moment, only compose.runtime is available for the common module, and this makes it almost impossible to do any layout at this point: even Button and Text are not available.

As you can see in the JS application example, Text is not imported from androidx.compose.material.Text, but from org.jetbrains.compose.web.dom.Text, which is a completely different element, so it cannot be used in a common module.

At this point, I would say that Compose JS is another framework that allows you to write UI in Compose style.

Liborio answered 9/2, 2022 at 5:22 Comment(1)
Found a way to do it :) Check my answerProclitic
L
2

Jetbrains updated the github repo with some samples:

https://github.com/JetBrains/compose-jb/tree/master/experimental/examples

It is possible now with version "1.2.0-alpha01-dev675" but not all components work and it is still in a somewhat early alpha stage.

Leech answered 2/6, 2022 at 15:16 Comment(0)
P
1

**

This answer is not an exact answer to this question. The correct answer for this is the answer by @PhilipDukhov. However, this might help others who land on this page for finding a solution to the other problem I am just leaving it here. For more info read comments between me and @PhilipDukhov for this answer.

**

Yes, it is possible to have all three modules in a single project. TLDR

I did some workarounds and managed to get all things in a single project i.e

  1. Compose for Desktop
  2. Compose for Android
  3. Compose for Web

Suggestion Before You Go Further

Although below code snippets works and by going through you will be able to achieve what you want actually. I personally recommend you to just go to the Github repository here . I think it will save time if you are creating a new project. However if you are adding web module to current KMM project continue reading.

The problem can be fixed by keeping the correct dependencies

So the project structure should be something like this

enter image description here

Note: If you are creating a new KMM project you will have only common, desktop, and android modules.(As you already know)

Step 1: You need to add the a folder to root (where the android, common,desktop folder is located) call it as web(should not matter)

Step 2: Add several folders to this newly created directory web your web directory should look something like this

enter image description here

Note : Dont add build (its autogenerated) add only

src/jsMain
   -kotlin
   -resources
src/jsTest 
    -kotlin
   -resources

and create a file called build.gradle.kts

Step 3: Change the content of this newly added Gradle file to something like this

import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
    kotlin("multiplatform")
    id("org.jetbrains.compose") version "1.0.0"
}

group = "com.example"
version = "1.0"

kotlin {
    js(IR) {
        browser {
            testTask {
                testLogging.showStandardStreams = true
                useKarma {
                    useChromeHeadless()
                    useFirefox()
                }
            }
        }
        binaries.executable()
    }
    sourceSets {
        val jsMain by getting {
            dependencies {
                implementation(compose.web.core)
                implementation(compose.runtime)
            }
        }
        val jsTest by getting {
            dependencies {
                implementation(kotlin("test-js"))
            }
        }
    }
}

Step 4: And Add Main.kt to jsMain/kotlin with following content

 renderComposable(rootElementId = "root") {
    Div(
        attrs = {
            // specify attributes here
            style {
                // specify inline style here
            }
        }
    ) {
        Text("A text in <div>")
    }
}

Step 4 : Add index.html to jsMain/resources with content

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MultiplatformTest Sample</title>
</head>
<body>
<div id="root"></div>
<script src="web.js"></script>
</body>
</html>

Note: web.js file mentioned in the above snippet is sensitive and the project generated you need to make sure it is web.js only or else the folder_name.js which you created in step 1

Step 5: Finally add web module to settings.gradle.kts file

pluginManagement {
    repositories {
        google()
        jcenter()
        gradlePluginPortal()
        mavenCentral()
        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    }
    
}
rootProject.name = "MultiplatformTest"


include(":android")
include(":desktop")
include(":common")
include(":web")  // Note web module is included here 

I have created a repo check this repo and you can use it as a template for your projects

Link to the repo: https://github.com/PSPanishetti/ComposeMultiplatform

If you get any doubts feel free to ask them.

Proclitic answered 25/2, 2022 at 18:18 Comment(4)
Link only answer is rarely can help. Please provide significant parts of your solution. The question was about sharing composable views between web, desktop and android, but your shared module only supports desktop/android platforms, which means you cannot import any of composables declared in common module into your web module.Liborio
@PhilipDukhov yes agree... Will edit it laterProclitic
as I said in my first comment, you are answering some other question. You have different modules for each platform, and you have to build the UI in the web/desktop modules independently of each other, but the author is asking how to build the UI from one common multiplatform module.Liborio
Oh, I got it ... @PhilipDukhovProclitic
B
0

Yes, it is possible now. The support for IOS has been moved to beta and Web is in alpha. For new projects, you can use the KMP wizard https://kmp.jetbrains.com/.

For existing projects, you can make changes in your current project by following the above startup project's structure. But I would recommend moving your old code to this new project.

Basically, you need to have the following in your build.gradle file of the composeApp (renamed from shared) module. Note that this project uses WASM for web build. You can use JS and WASM at the same time. This uses canvas-based implementation for compose and not the HTML-based one.

import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsCompose)
    alias(libs.plugins.compose.compiler)
}

kotlin {
    @OptIn(ExperimentalWasmDsl::class)
    wasmJs {
        moduleName = "composeApp"
        browser {
            commonWebpackConfig {
                outputFileName = "composeApp.js"
                devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply {
                    static = (static ?: mutableListOf()).apply {
                        // Serve sources to debug inside browser
                        add(project.projectDir.path)
                    }
                }
            }
        }
        binaries.executable()
    }
    
    androidTarget {
        @OptIn(ExperimentalKotlinGradlePluginApi::class)
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_11)
        }
    }
    
    jvm("desktop")
    
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "ComposeApp"
            isStatic = true
        }
    }
    
    sourceSets {
        val desktopMain by getting
        
        androidMain.dependencies {
            implementation(compose.preview)
            implementation(libs.androidx.activity.compose)
        }
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material)
            implementation(compose.ui)
            implementation(compose.components.resources)
            implementation(compose.components.uiToolingPreview)
        }
        desktopMain.dependencies {
            implementation(compose.desktop.currentOs)
        }
    }
}

android {
    namespace = "com.shreyashkore.kmpwizard"
    compileSdk = libs.versions.android.compileSdk.get().toInt()

    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    sourceSets["main"].res.srcDirs("src/androidMain/res")
    sourceSets["main"].resources.srcDirs("src/commonMain/resources")

    defaultConfig {
        applicationId = "com.shreyashkore.kmpwizard"
        minSdk = libs.versions.android.minSdk.get().toInt()
        targetSdk = libs.versions.android.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    buildFeatures {
        compose = true
    }
    dependencies {
        debugImplementation(compose.uiTooling)
    }
}

compose.desktop {
    application {
        mainClass = "MainKt"

        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "com.shreyashkore.kmpwizard"
            packageVersion = "1.0.0"
        }
    }
}
Beastings answered 24/5 at 20:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.