Skip to content

feat(kmp): Phases 0-2 — Compose Multiplatform desktop + shared preference/network foundations#1671

Draft
cuong-tran wants to merge 4 commits into
masterfrom
cursor/kmp-multiplatform-support-4854
Draft

feat(kmp): Phases 0-2 — Compose Multiplatform desktop + shared preference/network foundations#1671
cuong-tran wants to merge 4 commits into
masterfrom
cursor/kmp-multiplatform-support-4854

Conversation

@cuong-tran

@cuong-tran cuong-tran commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Incremental, verifiable work toward making Komikku multiplatform (Android + desktop + iOS) with Kotlin Multiplatform, keeping the Android build green at every step.

Phase 0 — Compose Multiplatform desktop scaffolding

  • Compose Multiplatform 1.10.3 (aligned with Kotlin 2.3.10) added to the libs catalog.
  • :i18n gains a jvm("desktop") target so the shared moko MR compiles for desktop.
  • New :desktopApp Compose for Desktop module consuming :i18n.
  • Works around the compose catalog ↔ org.jetbrains.compose plugin extension name clash by depending on the host-specific desktop-jvm-<os> artifact directly (so the right Skiko native loads).

Phase 1 — Shared multiplatform PreferenceStore

  • New KMP module :core:preference (android + desktop) holding the portable PreferenceStore/Preference/InMemoryPreferenceStore/TriState/CheckboxState, kept in the original tachiyomi.core.common.preference package → zero import churn.
  • DesktopPreferenceStore (java.util.prefs-backed) as the desktop implementation, mirroring AndroidPreferenceStore (including reactive changes()).
  • core:common re-exports it via api(); AndroidPreference(Store) stay in core:common (they need logcat) to avoid a circular dependency.

Phase 2 — Multiplatform networking foundation

  • New KMP module :core:network (android + desktop) introducing the jvmShared intermediate source set pattern: OkHttp (a JVM library) lives in jvmShared shared by both JVM targets, while commonMain holds the platform-agnostic NetworkClient/NetworkResponse API + an expect httpClient() factory.
  • OkHttpNetworkClient is the JVM actual; an iOS target would add an iosMain actual (e.g. Ktor Darwin) without changing the common API.
  • Additive and non-invasive: the existing core:common NetworkHelper and the source-api network ABI are untouched.

Scope / next steps

  • Apple/Kotlin-Native iOS targets cannot be compiled on the Linux CI VM, so iOS actuals are deferred to a macOS runner.
  • Migrating the existing NetworkHelper/Requests/source-api ABI and giving domain/data desktop targets remain entangled (e.g. HttpSource in source-api/commonMain pulls in the Android network stack, and logcat is Android-only). Each phase here delivered the cleanly-separable, non-cascading slice that's fully verifiable on desktop.

Walkthrough

Phase 2 — the shared NetworkClient (OkHttp) performs a live GET example.com returning HTTP 200 on desktop; the Phase 1 counter also persists (now 3):

komikku_desktop_phase2_shared_networkclient.mp4

Desktop app: shared NetworkClient GET example.com HTTP 200

Phase 1 — shared PreferenceStore persists across runs (counter 1 → 2):

komikku_desktop_phase1_preference_persistence.mp4
Persisted counter = 2 on second run

Testing

  • ./gradlew :core:preference:compileKotlinDesktop / :core:network:compileKotlinDesktop / :core:network:compileDebugKotlinAndroid — new modules compile for both targets
  • ./gradlew :desktopApp:compileKotlin — desktop app compiles against the shared preference + network APIs
  • SKIKO_RENDER_API=SOFTWARE ./gradlew :desktopApp:run — desktop app runs; preference persists across runs; shared NetworkClient returns HTTP 200 (see videos/screenshots)
  • ./gradlew spotlessCheck — passes
  • ./gradlew assembleDebug — Android build still green (BUILD SUCCESSFUL) after both extractions

Add a 👍 reaction to pull requests you find important.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Summary by Sourcery

Introduce initial Kotlin Multiplatform desktop scaffolding to validate Compose Multiplatform integration and shared module reuse without impacting the existing Android app.

New Features:

  • Add Compose Multiplatform version and Gradle plugin alias to the shared version catalog for desktop and future multiplatform UI targets.
  • Enable a JVM "desktop" target in the :i18n Kotlin Multiplatform module so its shared resources compile for desktop.
  • Add a new :desktopApp Compose Desktop application module wired into the build, consuming the shared :i18n module and launching a minimal desktop window.

cursoragent and others added 2 commits June 1, 2026 17:53
- Add Compose Multiplatform version/library/plugin entries to libs catalog
- Add jvm(desktop) target to :i18n so shared MR resources compile for desktop
- Add :desktopApp Compose for Desktop module sharing the :i18n KMP module

Keeps the Android build untouched; desktop is the first verifiable KMP target
on this Linux VM (iOS/Native targets require a macOS runner).

Co-authored-by: Cuong-Tran <cuong-tran@users.noreply.github.com>
The org.jetbrains.compose plugin is not applied (its 'compose' extension clashes
with the 'compose' version catalog), so select the host OS Compose Desktop
artifact (desktop-jvm-<os>) explicitly to load the matching Skiko native lib.

Co-authored-by: Cuong-Tran <cuong-tran@users.noreply.github.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes the foundational scaffolding for transitioning the project to Kotlin Multiplatform (KMP). It successfully integrates Compose Multiplatform for desktop without impacting the existing Android build, proving that shared modules can be consumed across platforms. This is a preliminary step intended to validate the toolchain and project structure before broader architectural changes are implemented.

Highlights

  • Dependency Management: Added Compose Multiplatform version 1.10.3 and the corresponding Gradle plugin to the version catalog.
  • Shared Module Configuration: Updated the :i18n module to include a jvm("desktop") target, enabling shared resource compilation for desktop.
  • New Desktop Module: Introduced a new :desktopApp module that provides a minimal Compose for Desktop window, verifying the cross-platform integration.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


The code now runs on desktop screens, Beyond the reach of mobile scenes. With KMP the path is clear, To bring the app to all, my dear.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@sourcery-ai

sourcery-ai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces initial Kotlin Multiplatform (KMP) scaffolding for a Compose Multiplatform desktop app by adding a JVM desktop target to the shared i18n module, wiring a new :desktopApp module that directly uses the Compose compiler and host-specific desktop artifact, and updating the version catalog to declare Compose Multiplatform versions and plugin aliasing, all without modifying the existing Android build.

File-Level Changes

Change Details Files
Declare Compose Multiplatform versions and Gradle plugin alias in the shared version catalog for use by desktop/KMP modules.
  • Add compose-multiplatform library version aligned with Kotlin 2.3.10 to the libs.versions.toml catalog
  • Register org.jetbrains.compose Gradle plugin alias in the version catalog referencing the compose-multiplatform version
gradle/libs.versions.toml
Enable a JVM desktop compilation target on the shared i18n KMP module so moko-resources MR is generated for desktop.
  • Add jvm("desktop") target alongside the existing androidTarget in the i18n Kotlin multi-platform configuration
  • Keep the default hierarchy template, ensuring the new target integrates with the existing source set layout
i18n/build.gradle.kts
Register the new Compose Desktop application module in the Gradle settings so it participates in the build.
  • Include :desktopApp module in settings.gradle.kts with a scoped comment describing it as Phase 0 KMP scaffolding
settings.gradle.kts
Add a new :desktopApp JVM module configured to use the Compose compiler plugin and host-specific Compose Desktop artifact without the org.jetbrains.compose Gradle plugin.
  • Apply kotlin("jvm"), mihon.code.lint, org.jetbrains.kotlin.plugin.compose, and application plugins to the desktopApp module
  • Set Kotlin compiler JVM target and Java source/target compatibility to 17
  • Resolve the compose-multiplatform version from the libs version catalog at configuration time
  • Compute a host-specific Compose Desktop target string from os.name and os.arch (macOS/Windows/Linux, x64/ARM) to select the appropriate desktop-jvm- artifact
  • Add implementation dependency on org.jetbrains.compose.desktop:desktop-jvm-$hostComposeDesktopTarget and on the shared projects.i18n module
  • Configure the application plugin mainClass to app.komikku.desktop.MainKt
  • Document the decision not to apply org.jetbrains.compose plugin due to the existing compose catalog extension name clash
desktopApp/build.gradle.kts
Implement a minimal Compose Desktop entry point that opens a window and verifies access to the shared i18n MR class.
  • Define main() using application { Window { ... } } from androidx.compose.ui.window as the desktop entry point
  • Create an App() composable that uses MaterialTheme and a centered Column layout
  • Render static text identifying this as Komikku Kotlin Multiplatform desktop Phase 0
  • Reference tachiyomi.i18n.MR.strings via reflection in a Text composable to assert that shared i18n resources are available on the desktop classpath
desktopApp/src/main/kotlin/app/komikku/desktop/Main.kt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Phase 0 scaffolding for a Compose Multiplatform desktop application (:desktopApp), including the main entry point, build configuration, and adding the JVM desktop target to the shared :i18n module. The feedback suggests using type-safe version catalog accessors in build.gradle.kts to make the configuration cleaner and remove an unnecessary import. Additionally, it is recommended to handle potential null values from System.getProperty when determining the host OS and architecture to prevent potential NullPointerExceptions.

@@ -0,0 +1,58 @@
import org.gradle.api.artifacts.VersionCatalogsExtension

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

This import is no longer needed if we use the type-safe version catalog accessor libs.versions.compose.multiplatform.get().

Comment on lines +32 to +36
val composeMultiplatformVersion = the<VersionCatalogsExtension>()
.named("libs")
.findVersion("compose-multiplatform")
.get()
.requiredVersion

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Using the type-safe accessors generated by Gradle for the version catalog is cleaner, safer, and more idiomatic than retrieving the catalog by name and using string lookups.

val composeMultiplatformVersion = libs.versions.compose.multiplatform.get()

Comment on lines +38 to +41
val hostComposeDesktopTarget: String = run {
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
val isArm = osArch.contains("aarch64") || osArch.contains("arm")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

System.getProperty can theoretically return null if the property is not set or if a security manager restricts access. Using orEmpty() before calling lowercase() prevents potential NullPointerExceptions and ensures safer execution.

val hostComposeDesktopTarget: String = run {
    val osName = System.getProperty("os.name").orEmpty().lowercase()
    val osArch = System.getProperty("os.arch").orEmpty().lowercase()
    val isArm = osArch.contains("aarch64") || osArch.contains("arm")

- New KMP module :core:preference (android + desktop) holding the portable
  PreferenceStore/Preference/InMemoryPreferenceStore/TriState/CheckboxState
  abstractions, kept in the original tachiyomi.core.common.preference package
  so no imports change across the codebase.
- Add DesktopPreferenceStore (java.util.prefs-backed) as the desktop actual of
  the shared PreferenceStore, mirroring AndroidPreferenceStore semantics.
- core:common re-exports :core:preference via api(); AndroidPreference(Store)
  stay in core:common (they depend on logcat) to avoid a circular dependency.
- desktopApp now persists a launch counter through the shared PreferenceStore.

Co-authored-by: Cuong-Tran <cuong-tran@users.noreply.github.com>
@cursor cursor Bot changed the title feat(kmp): Phase 0 — Compose Multiplatform desktop scaffolding feat(kmp): Phases 0-1 — Compose Multiplatform desktop + shared PreferenceStore Jun 1, 2026
- New KMP module :core:network (android + desktop) introducing the jvmShared
  intermediate source set pattern: OkHttp (a JVM library) lives in jvmShared and
  is shared by both JVM targets, while commonMain holds the platform-agnostic
  NetworkClient/NetworkResponse API + an expect httpClient() factory.
- OkHttpNetworkClient provides the JVM actual; an iOS (Kotlin/Native) target would
  add an iosMain actual (e.g. Ktor Darwin) without touching this API.
- desktopApp now performs a real HTTP GET through the shared NetworkClient.

Additive and non-invasive: the existing core:common NetworkHelper and the
source-api network ABI are untouched (migrating them onto this foundation is
follow-on work, entangled with the source-api commonMain ABI).

Co-authored-by: Cuong-Tran <cuong-tran@users.noreply.github.com>
@cursor cursor Bot changed the title feat(kmp): Phases 0-1 — Compose Multiplatform desktop + shared PreferenceStore feat(kmp): Phases 0-2 — Compose Multiplatform desktop + shared preference/network foundations Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants