π Material Notes is a clean, Material 3 note-taking app built with Compose Multiplatform β running on Android, iOS, and Desktop from a single shared, multi-module codebase, with Navigation 3, SQLDelight, Metro DI, Coroutines, Flow, optional cloud sync via a Supabase KMP SDK, and a card-to-detail shared-element transition based on MVVM architecture.
Tip
The same Compose UI and logic run natively on Android, iOS, and Desktop (JVM) β split across :core and :feature Kotlin Multiplatform modules.
- Android β
./gradlew :composeApp:assembleDebug, or run thecomposeAppconfiguration in Android Studio. - Desktop β
./gradlew :composeApp:run - iOS β open
iosApp/iosApp.xcodeprojin Xcode, set your signing TEAM_ID iniosApp/Configuration/Config.xcconfig, pick a simulator, and run. The "Compile Kotlin Framework" build phase invokes Gradle to build the shared framework.
- Minimum SDK level 24 Β· targets Android, iOS, and Desktop (JVM).
- Kotlin based, with Coroutines and Flow for async streams.
- Compose Multiplatform Libraries:
- Compose Multiplatform β declarative UI + Material 3.
- Lifecycle & ViewModel β
org.jetbrains.androidx.lifecycle(multiplatform). - Navigation 3 β
NavDisplay+entryProviderwith type-safeNavKeyroutes and entry-scoped ViewModels (org.jetbrains.androidx.navigation3). - SQLDelight β typesafe SQL with platform drivers (Android / native / JDBC).
- Supabase KMP SDK β
io.github.androidpoet:supabase-*β optional two-way cloud note sync (push + pull), offline-first. - Metro β compile-time dependency injection (a Kotlin compiler plugin), wired across modules.
- Architecture:
- MVVM Architecture (View β ViewModel β Repository β SQLDelight queries).
- Repository Pattern.
- Multi-module:
:core:data,:core:designsystem,:feature:*,:composeApp.
- kotlinx-datetime β multiplatform date/time formatting.
- Shared-element transition β
SharedTransitionLayout+sharedBoundsmorph the note card into the detail screen (theAnimatedVisibilityScopecomes from Nav3'sLocalNavAnimatedContentScope).
Material Notes follows MVVM across a multi-module Kotlin Multiplatform setup. Each module targets Android, Desktop (JVM), and iOS; the app shell assembles them and the Metro graph wires dependencies across module boundaries at compile time.
:composeApp # App shell: App, AppNavHost (Nav3), Metro AppGraph, platform entry points
:core:data # Note, MainRepository, SQLDelight schema + drivers (Android / native / JDBC)
:core:designsystem # Theme, palette, icons, shared-element helpers, date util
:feature:home # HomeScreen + NotesViewModel
:feature:addnote # AddNoteScreen + AddNoteViewModel
:feature:detail # NoteDetailScreen + NoteDetailViewModel
iosApp/ # Xcode project (SwiftUI shell hosting the Compose UI)
Dependencies flow one way β :composeApp β :feature:* β :core:* β so the graph stays acyclic. Feature
screens receive their ViewModel as a parameter; AppNavHost (which can see both the Metro graph and the
screens) creates each entry-scoped ViewModel and passes it down. Each platform supplies a SQLDelight
SqlDriver (:core:data/.../DatabaseDriver.*.kt) into buildAppGraph(driver, ioDispatcher). Because Metro
validates the graph at compile time, a wiring mistake fails the build instead of crashing at runtime.
Notes live locally in SQLDelight and the app is fully usable offline. The Sync button in the home
header does an explicit two-way sync through AndroidPoet's Supabase KMP SDK:
it upserts every local note up to a Supabase notes table, then pulls the table back down β so a note
saved on any device converges everywhere.
To enable it:
- Create a Supabase project and run
supabase/schema.sqlin its SQL Editor. - Put your project URL and anon key in
core/data/src/commonMain/kotlin/com/androidpoet/materialnotes/data/sync/SupabaseConfig.kt.
Until those are set, SupabaseConfig.isConfigured is false and the Sync button simply reports that
cloud sync isn't configured. The app ships the anon key only β the service_role key bypasses Row
Level Security and must never be embedded in a client. The default schema is one shared notebook;
supabase/schema.sql documents the per-user (auth + RLS) upgrade path.
Note
The sync code lives in :core:data under .../data/sync/ (SupabaseConfig, RemoteNote,
NoteSyncService) and is wired into NotesViewModel via Metro.
Kotlin 2.3.10 Β· Compose Multiplatform 1.10.3 Β· Navigation 3 1.1.0 Β· AGP 8.11.1 Β· SQLDelight 2.3.2 Β·
Metro 1.1.0 Β· Supabase KMP 0.3.5 Β· Lifecycle (JB) 2.10.0 Β· Gradle 8.14 Β· JDK 21 (see gradle/libs.versions.toml).
Note
Toolchain notes. Metro 1.1.0's Gradle plugin pins the Kotlin Gradle plugin to 2.3.x and requires
JDK 21 + Gradle 8.13+ β gradle/gradle-daemon-jvm.properties selects the JDK 21 toolchain
automatically. On iOS, SQLDelight's native driver (SQLiter) needs the app to link -lsqlite3
(in OTHER_LDFLAGS), and CADisableMinimumFrameDurationOnPhone=true must be set in
iosApp/iosApp/Info.plist. material-icons-extended is no longer published for CMP 1.8+, so the few
icons used are hand-authored ImageVectors.
Support it by joining stargazers for this repository. β
Also, follow me on GitHub for my next creations! π€©
Designed and developed by 2026 AndroidPoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.