Compose Multiplatform Migration

GSoC 2026 Proposal: Compose Multiplatform Migration — Anuj

Applicant: Anuj (IRC: anuj_)
Email: aanuj8619@gmail.com
GitHub: anuj990
LinkedIn: anuj990


About Me

I am a sophomore at the Indian Institute of Information Technology Jabalpur
(IIITDM Jabalpur) pursuing B.Tech in Electronics and Communication Engineering.
I started my coding journey with C++ for competitive programming, and through
my college’s open source programme BSOC (BitByte Summer of Code) I discovered
Android development. Since then my primary stack has been Kotlin, Jetpack
Compose, and MVVM and over the past few months I have been working directly
with Kotlin Multiplatform and Compose Multiplatform.

I am particularly drawn to this project because it is not about adding new
features it is about making the existing codebase structurally so that
a completely new group of users (iOS users) can access ListenBrainz for the
first time. That kind of foundational work is what excites me most.

I have been contributing to ListenBrainz Android since December 2025 across
both the main and cmp branches. I have read the code deeply, received
review feedback from jasje, acted on it, and gotten PRs merged.


Project Summary

ListenBrainz Android is currently available only on Android which means iOS
users cannot access this service at all. This project proposes
completing the migration of the existing codebase to Kotlin Multiplatform (KMP)
and Compose Multiplatform (CMP), so that both Android and iOS users can run
ListenBrainz from a single shared codebase.
The migration is already actively in progress on the cmp branch, tracked
under Epic Issue #614
some migrations is already done. My GSoC work focuses on completing the remaining migration in a structured phase-by-phase approach, resulting in a fully working Android and iOS app by the end of the summer.

The final architecture will look like this:

commonMain 
├── Models, Repositories, ViewModels
├── Network (Ktor), DataStore KMP, Room KMP
├── Compose UI (JetBrains CMP), Navigation (Voyager)
│
├── androidMain                    iosMain
│   ├── ExoPlayer              ├── AVPlayer
│   ├── WorkManager            ├── BGTaskScheduler
│   ├── Permissions            ├── Native Permissions
│   └── SplashScreen           └── LaunchScreen
│
├── androidApp                     iosApp

Current State of Migration

Already Done

  • KMP project structure + shared module
  • Gradle KMP plugin configuration
  • Source sets (commonMain, androidMain, iosMain)
  • Dagger/Hilt → Koin
  • Gson → Kotlin Serialization
  • Retrofit → Ktor
  • coil/glide → coil3
  • androidsvg → coil SVG decoder
  • JUnit → kotlin-test
  • turbine (already KMP compatible)
  • androidx.datastore → DataStore KMP
    • AppPreferencesImpl in commonMain
    • PreferenceKeys in commonMain
    • DataStorePreference<T> interface in commonMain
    • PlatformContext expect/actual in place
    • createDataStore expect/actual in place
  • compose ratingbar → custom CMP component
  • threetenabp → kotlinx-datetime

Partial

  • socket.io → ktor-websockets — PR in review
  • Room → Room KMP — infrastructure done but DAOs, entities, and database classes still
    in androidMain
  • androidx.preference → DataStore KMP shared module done, minor
    cleanup remaining in app module
  • androidx.lifecycle → KMP ViewModel PR #732 attempted SocialViewModel
    migration but used plain CoroutineScope instead of proper KMP ViewModel()
    with JetBrains lifecycle-kmp="2.8.0" i will it update soon

Remaining for GSoC

  • Logger-Android → Kermit
  • jsoup → Ksoup
  • ktor-client-okhttp → platform engines (darwin/okhttp)
  • Remove chucker
  • Androidx.paging → paging-common
  • Koin platform-specific setup:
    Move koin-core to commonMain
    • koin-androidx-compose → koin-compose
    • Remove koin-androidx-workmanager (WorkManager is Android-only)
  • lottie/lottie-compose → Compottie
  • androidx.lifecycle → KMP ViewModel (full migration)
  • androidx.compose.* → JetBrains CMP
  • androidx.navigation → Voyager
  • compose-shimmer → KMP shimmer
  • ExoPlayer → interface (AVPlayer for iOS)
  • spotify-app-remote → stub for iOS
  • WorkManager → BGTaskScheduler (expect/actual)
  • accompanist-permissions → platform-specific
  • accompanist-systemuicontroller → platform-specific
  • share-android → platform-specific
  • androidx.browser → platform-specific
  • androidx.core.splashscreen → platform-specific
  • app-update → Android-only
  • androidx.palette → Custom implementation

Migration Plan

Phase 1 — Final Build Infrastructure

Update libs.versions.toml to the final target state adding all KMP/CMP
plugins (jetbrains-compose, lifecycle-kmp, voyager, kermit,
compottie, ksoup, shimmer-kmp) and dependency versions. Set up the
final shared/build.gradle.kts with proper commonMain, androidMain,
and iosMain source sets so that approximately 80% of all code lives in
commonMain by the end of GSoC.

Phase 2 — Domain Models

Move all model classes from androidMain to commonMain. Replace Gson’s
@SerializedName with kotlinx.serialization’s @SerialName, annotate any
missing @Serializable, and replace all Java date types (java.util.Date,
Calendar, SimpleDateFormat) with kotlinx-datetime equivalents
(Clock.System.now(), Instant, LocalDateTime).

Phase 3 — Network Layer

This is one of the most impactful phases. Migrate createBaseHttpClient
to commonMain via expect/actual platform engines (OkHttp for Android,
Darwin for iOS). Remove ChuckerInterceptor (Android-only debug tool) and
replace it with Ktor’s built-in Logging plugin using Kermit as the
underlying logger. Replace Logger-Android with Kermit throughout.
Replace socket.io-client with ktor-client-websockets, handling the
Engine.IO handshake manually (0 → 40 → subscribe, ping/pong 2 → 3) with
automatic reconnect logic. Replace jsoup with Ksoup for HTML parsing.
Migrate all Ktorfit service interfaces and Koin networkModule to shared.

Phase 4 — Room KMP

Move all entities (SongEntity, AlbumEntity, ArtistEntity,
PlaylistEntity, ListenSubmitBody.Payload), all DAOs, and both database
classes (BrainzPlayerDatabase, ListensSubmissionDatabase) from
androidMain to commonMain. Add expect/actual database builders to
handle platform-specific file paths — Android uses Context.getDatabasePath()
while iOS uses NSFileManager + NSDocumentDirectory.

Phase 5 — Repository Layer + Paging

Migrate all repository interfaces and implementations to commonMain.
Replace all java.util.Calendar and SimpleDateFormat usages with
kotlinx-datetime. Migrate all paging sources from androidx.paging
to paging-common so they work on both platforms. Keep paging-runtime
and paging-compose in androidMain only. Migrate Koin repositoryModule
to commonMain.

Phase 6 — ViewModel Migration

Add JetBrains lifecycle-kmp="2.8.0" to commonMain — this is critical.
ViewModels must extend KMP ViewModel() and use viewModelScope which
auto-cancels on both platforms. Plain CoroutineScope is not lifecycle-aware
and must not be used (PR #732 needs updating for this reason). Keep thin
Android wrappers in androidMain only for Android-specific dependencies
like RemotePlaybackHandler and android.net.Uri. Migrate Koin
viewModelModule to commonMain.

Phase 7 — Compose UI Migration

Replace androidx.compose.* with JetBrains CMP. Migrate app theme, colors,
and typography to commonMain. Move all resources from Android res/ to
shared composeResources/ — updating all painterResource(R.drawable.x)
calls to painterResource(Res.drawable.x). Replace lottie-compose with
Compottie for KMP animations. Replace compose-shimmer with KMP shimmer
(same API, different artifact). Replace both androidx.navigation.compose
and androidx.navigation3 with Voyager — converting string-based routes to
type-safe Screen data classes. Wire platform entry points
(MainActivity.setContent { App() } on Android,
ComposeUIViewController { App() } on iOS).

Phase 8 — Platform-Specific Boundaries

Implement expect/actual for the four major platform boundaries:
ScrobblerManagerNotificationListenerService on Android, permanent
no-op stub on iOS (iOS fundamentally cannot read other apps’ notifications);
AudioPlayer — ExoPlayer on Android wrapped with MediaBrowserServiceCompat
for lock screen + Bluetooth integration, AVPlayer on iOS;
scheduleListenSubmission — WorkManager on Android (existing
ListenSubmissionWorker), BGTaskScheduler on iOS;
PermissionChecker — Accompanist permissions on Android (notification
listener + battery optimization), native iOS permission APIs.


Timeline

Period Milestone
May 1–24 Community bonding — study cmp branch deeply, confirm all decisions with mentor
May 25–Jun 7 Phase 1 + Phase 2 — final build setup + all domain models to commonMain
Jun 8–Jun 21 Phase 3 — complete network layer migration (both parts)
Jun 22–Jun 28 Phase 4 — Room KMP complete
Jun 29–Jul 5 Phase 5 — all repositories + paging to commonMain
Jul 6–10 Midterm Evaluation — models, network, Room, repositories all in commonMain
Jul 11–Jul 17 Tests + midterm bug fixes, move tests to commonTest
Jul 18–Jul 24 Phase 6 — full ViewModel migration with proper KMP ViewModel
Jul 25–Jul 31 Phase 8 — all platform boundaries via expect/actual
Aug 1–Aug 7 Phase 7 Part 1 — CMP core, theme, resources, Compottie
Aug 8–Aug 14 Phase 7 Part 2 — Voyager navigation, shimmer, platform entry points
Aug 15–Aug 24 Full Android + iOS testing, polish, final submission

My Contributions

PR Branch Description
#639 main Fixed search state mismatch between user and BrainzPlayer search
#648 main Fixed broken overflow menu on BrainzPlayer Albums screen
#727 main Implemented Delete Listen feature
#732 cmp Migrating SocialViewModel to shared/commonMain for KMP

All PRs: github.com/metabrainz/listenbrainz-android/pulls/anuj990


Full Proposal

Due to the community post character limit, the complete proposal including
all technical details, actual ListenBrainz code examples showing exact
before/after migration for every phase, full dependency configurations,
expect/actual implementations, and the complete week-by-week timeline
is available here:

Full Proposal on GitHub


Looking forward to feedback from the community and mentors!

1 Like

@anuj990

I have read your proposal. It is very informative, and you have addressed every aspect of the project well.

1 Like

Hi @anuj990, thanks for writing up a contribution plan / GSoC proposal.

Great work on writing a detailed proposal, but it is way too verbose for me to review. I know that all proposals for GSoC are AI-assisted but you need to be subtle with it. Compacting the proposal is the first suggestion I would like give you before proceeding further.

Also, a lot of the coding part is already included in the proposal itself. The code should be short and concise. Coding should be done once coding period starts itself and not in the proposal period itself.

1 Like

@Jasjeet Thanks for your feedback. I will ensure alignment with the guidelines
I will refine and make the proposal more concise, and update it asap