Introduction
Contact Information
- Name: Nirvan Jain
- IRC nick: nirvan_jain
- Email: nirvanj73@gmail.com
- GitHub: nirvan73
- LinkedIn: Nirvan Jain
About Me
I am a sophomore at IIITDM Jabalpur pursuing B.Tech in Computer Science and Engineering. Music runs through most of my day, while coding, while commuting, which is part of why ListenBrainz caught my attention in the first place. I started Android development in my second semester, built small apps, participated in intra-college Android events, and have been part of collaborative projects since. I also do competitive programming and mentor juniors through the institute’s Programming Club.
I have been contributing to ListenBrainz since December 2025. So far I have implemented the shimmer effect across the Feed and Profile screens, extended search to cover playlists, artists, albums and tracks, and fixed several bugs along the way.
- All my PRs: Click here
- All my commits: Click here
Project Summary
ListenBrainz is Android-only right now. An earlier iOS attempt using Swift and SwiftUI did not work out well, it split the codebase in two, which meant every feature and bug fix had to be done twice. This project completes the migration to Kotlin Multiplatform (KMP) and Compose Multiplatform (CMP) so both platforms run from a single shared codebase. The approach is incremental: shared logic moves into commonMain in stages, the Android app stays functional throughout, and expect/actual is used only where the platforms genuinely differ.
What I Plan to Deliver
Compose Multiplatform UI: Migrate all screens, components, and theme from Android Jetpack Compose to JetBrains CMP in commonMain. Android keeps its Activity-based entry point and iOS gets a ComposeUIViewController.
Navigation 3: Replace the string-based Navigation 2.0 graph with a type-safe, user-owned @Serializable Screen backstack in commonMain. Both platforms get identical navigation behavior from the same code.
Dependency Injection: Swap koin-android/koin-androidx-compose for koin-compose/koin-compose-viewmodel. The shared initKoin and expect val platformModule live in commonMain, with OkHttp on Android and Darwin on iOS as platform actuals.
ViewModel and Lifecycle: Replace androidx.lifecycle with JetBrains KMP equivalents. All ViewModels move to commonMain using proper ViewModel() and viewModelScope. Android-specific dependencies like RemoteMediaHandler get abstracted behind shared interfaces.
Paging: Replace paging-runtime-ktx/paging-compose with the official KMP-compatible paging library. PagingSource, Pager, and collectAsLazyPagingItems all move to commonMain.
WebView: Replace WebViewClient subclasses with compose-webview-multiplatform by KevinnZou. The JS bridge (addJavascriptInterface on Android vs WKScriptMessageHandler on iOS) gets abstracted via expect/actual. All JS scripts stay in commonMain unchanged.
Media Player: A shared BrainzPlayer interface lives in commonMain. Android backs it with ExoPlayerController via MediaBrowserCompat. iOS backs it with AVPlayerController using AVPlayer, AVAudioSession, and MPRemoteCommandCenter.
Background Tasks: A shared BackgroundTaskScheduler interface in commonMain. Android implements it with WorkManager, iOS implements it with BGTaskScheduler.
Permissions: Replace accompanist-permissions with MOKO Permissions, which covers both platforms with a unified API.
UI Libraries: Replace com.valentinilk.shimmer with kmp-shimmer-compose, lottie-compose with Compottie, accompanist-systemuicontroller with a shared expect fun setStatusBarIconColor, and androidx.browser with expect fun openUrlInBrowser.
Onboarding: The first three screens (Introduction, Login and Create Account, Permissions) move to commonMain. The last two (Listen Submission setup and third-party app selector) depend on NotificationListenerService, which iOS does not support, so they stay in androidMain.
Spotify App Remote: Spotify has no iOS SDK. The integration stays scoped to androidMain and a minimal no-op stub lives in iosMain to keep the build green.
Testing: All platform-independent tests move to commonTest using Kotlin Test. Espresso and Compose UI tests stay in androidTest.
Timeline
| Period | Work |
|---|---|
| May 8 - Jun 1 | Community bonding: dependency audit, migration order with mentor, iOS CI compile check |
| Jun 2 - Jun 8 | Lifecycle and DI migration |
| Jun 9 - Jun 15 | Full ViewModel migration + shared app entry points |
| Jun 16 - Jun 22 | Navigation 3 migration |
| Jun 23 - Jun 29 | Shared UI: shimmer, Lottie, system bar, browser, CMP composables |
| Jun 30 - Jul 6 | Stabilization + pre-midterm QA, iOS simulator smoke test |
| Jul 7 - Jul 11 | Midterm Evaluation |
| Jul 12 - Jul 18 | KMP Paging + midterm feedback fixes |
| Jul 19 - Jul 25 | WebView migration |
| Jul 26 - Aug 1 | Media player abstraction |
| Aug 2 - Aug 8 | WorkManager, permissions, onboarding, Spotify stub |
| Aug 9 - Aug 15 | Full iOS QA + regression pass |
| Aug 16 - Aug 22 | Documentation + cleanup |
| Aug 23 - Aug 25 | Final submission |
My college semester ends in late April, so I will be free from the start of community bonding with no competing commitments. I plan to put in 35 to 50 hours per week throughout the project.
Full Proposal
Due to the community post character limit, the complete proposal with technical details, code examples, full expect/actual implementations, and the week-by-week breakdown is here:
Any feedback or questions from mentors and the community would be helpful before I finalize the plan.