Choosing local storage often starts with a misleading question:
“Should I use Room, SQLDelight, or DataStore?”
After using all three, I no longer see them as direct competitors. The right choice depends less on the library API and more on the shape of the data.
I was recently reminded of this while adding shared persistence to a Kotlin Multiplatform weather application.

The real-world problem
The application needed to store two very different kinds of data:
- saved locations and cached weather results,
- preferences such as temperature units and the selected map layer.
The iOS app already stored saved locations in Core Data. Android needed the same functionality, and we wanted both platforms to behave consistently.
That gave us several requirements:
- one persistence API for Android and iOS,
- shared entities and queries,
- explicit migrations,
- minimal platform-specific code,
- no loss of existing iOS data.
This was not just a database-library comparison. It was also a migration and architecture decision.
Where Room worked best for me
Room is still my natural choice for Android-only relational data.
Its entities and DAOs communicate intent well, integration with coroutines and Flow is excellent, and most Android developers already understand its conventions.
The abstraction is productive, but it can also hide how much the application depends on Android-oriented concepts. Room KMP improves this significantly, although adopting it still requires evaluating the maturity and ergonomics of the non-Android targets.
My practical rule:
“If the application is Android-only and the data is relational, start with Room unless you have a specific reason not to.”

Why we chose SQLDelight for KMP
SQLDelight makes SQL the source of truth and generates Kotlin APIs from it.
For our application, that was a good fit. Saved locations and weather cache require filtering, ordering, deletion, expiration, and future migrations. These are database operations rather than preference updates.
The shared module now owns:
- database tables,
- queries,
- persistence models,
- mappings to domain models,
- cache behavior.
Android and iOS only create their SQLite driver.
The trade-off is additional explicit work. SQL needs to be maintained, generated rows should not leak into domain code, and mapping functions add some boilerplate.
For us, this visibility was a benefit rather than a cost.
One particularly real-world lesson appeared on iOS: the shared Kotlin module compiled successfully, but the final Xcode build failed until the static framework explicitly linked the system SQLite library.
It was a useful reminder that compiling the KMP module is not enough. Cross-platform persistence should also be verified through complete Android and iOS application builds.

Where DataStore belongs
DataStore is excellent for small preference-like state:
- selected temperature unit,
- map style,
- onboarding completion,
- the last selected location ID.
It is not a smaller or simpler relational database.
A list of saved locations can technically be serialized into DataStore, but filtering, deleting one item, deduplicating coordinates, and migrating the structure become application-managed operations.
My practical rule is:
“If I usually read and replace the value as one small unit, DataStore is a good fit. If I need to query individual records, I want a database.”
The migration mattered more than the library choice
The existing iOS application already had user data in Core Data. Simply replacing Core Data with SQLDelight would have produced a clean architecture and a bad user experience.
Instead, the old implementation was isolated and retained as a temporary migration source.
The planned rollout is:
- New data is stored in SQLDelight.
- Existing Core Data locations are imported once.
- Completion is recorded only after a successful import.
- The legacy store remains available temporarily for rollback.
- Core Data is removed in a later release.
This is the part persistence comparisons often omit. Selecting a library is easy when the database is empty. Production decisions must account for data that already belongs to users.

What I would choose today
Android-only relational data: Room
Shared KMP relational data: SQLDelight or Room KMP
SQL-first design and explicit migrations: SQLDelight
Small settings and preferences: DataStore
Queryable collections or caches: Room or SQLDelight
Room and SQLDelight can both be good database choices. DataStore usually complements one of them rather than replacing it.
For our weather application, the resulting split is:
- SQLDelight for saved locations and weather cache,
- DataStore for user preferences,
- Core Data retained temporarily for migration only.
How we combine them in practice (Weather app)
In our weather application, persistence is split by responsibility:
- SQLDelight stores saved locations and cached weather data.
- DataStore is planned for small user preferences such as temperature units and map settings,
- Core Data remains on iOS only as a temporary source for migrating existing locations.
- The shared KMP module owns the database schema, queries, mappings, and cache behavior.
- Platform code is responsible only for creating the database driver and handling the legacy iOS migration source.
This separation keeps queryable data in a relational database, preferences in a key-value store, and platform-specific migration code outside the shared persistence API.

Final takeaway
After using all three, I would not choose based on which API looks shortest in a tutorial.
I would ask:
- Is the data relational or preference-like?
- Do I need to query individual records?
- Which platforms own the data?
- How will schema changes be migrated?
- Is there existing user data that must survive the transition?
Those questions usually make the technology choice much clearer.




Leave a Reply