Modules and the iOS path
Folio ships on Android only. The code is still a Kotlin Multiplatform project, so an iPad version can reuse as much as possible later.
Today
| Module | Source set | What lives there | May import |
|---|---|---|---|
shared |
commonMain |
Home screens (Compose Multiplatform), folder editor, backup settings UI, templates, reader models, brand (FolioBrand), pure utils (TextUtils.kt) |
Kotlin stdlib, Compose Multiplatform, lifecycle. No android.* or java.* |
shared |
androidMain |
Android-only helpers for the shared UI | Android |
shared |
iosMain |
MainViewController(), a placeholder screen |
UIKit / Foundation |
androidApp |
main |
Activities, editor, ink engine, storage (SQLite + org.json), files and backups, PDF/EPUB engines, OCR, in-app updates |
Anything |
iosApp |
– | Xcode shell that hosts MainViewController() |
– |
The iOS targets (iosArm64, iosSimulatorArm64) stay enabled in shared/build.gradle.kts. Android builds don’t compile them, but ./gradlew :shared:compileKotlinIosSimulatorArm64 fails as soon as common code uses a JVM or Android API. Run it before merging changes to shared.
Rules
- Put new logic in
commonMainwhen it doesn’t need the platform. Models, validation, sorting, search, text and geometry helpers belong there. Platform code calls them. - Keep platform code behind small interfaces. When common code needs a platform service, declare an interface (or
expect) in common and implement it per platform. Avoid passingContextor Android types through common APIs. - One helper, one place. Shared pure helpers go in
shared/commonMain(TextUtils.kt); Android view helpers go inandroidApp/ViewUtils.kt(dp(),ReaderChromeBands). Search before adding a new one. - Lifetimes. ViewModels hold the
Application, never an Activity or View. Objects with listeners (PlayUpdates,PenSettings, preference listeners) register once and unregister inonDestroy/onCleared. Views cancel their coroutine scopes inonDetachedFromWindow. Native handles (Pdfium, MediaRecorder, Readium publications) are closed by their owner. - No work on the main thread beyond layout and drawing: storage, JSON, rendering PDFs and OCR run on worker threads or
Dispatchers.IO.
Performance notes
- Release builds run R8 (
isMinifyEnabled,isShrinkResources). Keep rules for JNI and reflective libraries (Pdfium, zstd, Jetpack Ink, PDFBox, Readium, ML Kit) are inandroidApp/proguard-rules.pro. After adding a library, test the release build: R8 problems only show up there (ML Kit OCR broke until its rules were added). - Stroke points are stored with 5 decimals. Full float precision made pages about twice as large and pushed
org.jsononto its slowBigIntegerpath; a debug build took ~40 s to open a 1.6 MB library on a loaded emulator. - Debug builds are much slower than release on ART. Judge performance on
just release. - Known hot spots, in order of payoff:
NotebookStorage.load()parses every page of every notebook at startup. Load notebook summaries first and pages on open.NotebookEditoris one large composable; splitting the toolbar, rail and sheets would limit recomposition.InkCanvasView.onDrawallocates per frame (PageOrder.objects); cache per page revision.- Library thumbnails render on every launch; cache them on disk by page revision.
Order for an iOS port
Each step is useful on Android by itself, and none needs the next.
- Models and JSON format →
commonMain. MoveNotebook, pages, strokes and blocks, and replaceorg.jsonwithkotlinx.serialization(the.folioschema inpublic/schemasis the contract). - Storage → common. SQLDelight or Room KMP for
knotes.dbandknotes-reader.db, keeping the current schema and migrations. - Files and backups → Okio for ZIP, Zstandard and paths, with a small platform interface for the document picker and the backup folder.
- ViewModels →
commonMain(lifecycle-viewmodel is already multiplatform), with storage injected. - Ink stays native per platform behind an
InkSurfaceinterface: Jetpack Ink on Android, PencilKit or Metal on iPadOS. The stroke model is shared, so notebooks open on both. - Reader engines stay native behind a
ReaderEngineinterface: Pdfium and Readium on Android, PDFKit and the Readium Swift toolkit on iOS. Annotations and positions are already common models.