Module 14 of 20

Local Data Persistence (Room, SQLite, DataStore & Offline-First)

Persist data locally with Room Database, SQLite, DataStore key-value pairs, and design offline-first app architectures.

Module 14: Local Data Persistence (Room, SQLite, DataStore & Offline-First)

Learning Objectives

By the end of this module, you’ll understand:

  • Android storage architecture
  • Internal vs External storage
  • App sandbox
  • Files
  • SharedPreferences
  • Jetpack DataStore
  • SQLite
  • Relational databases
  • SQL basics
  • Room ORM
  • Entities
  • DAOs
  • Relationships
  • Transactions
  • Migrations
  • Room + Coroutines + Flow
  • Offline-first architecture
  • Synchronization strategies
  • Caching patterns
  • Best practices

Part 1 — Why Do Apps Need Local Storage?

Imagine you’re building a Notes app.

User creates:

Shopping List

Milk
Eggs
Bread

Where is this stored?

If it’s only in memory:

RAM



App Closed



Everything Lost

Bad.

Instead:

Disk Storage



App Closed



Data Still Exists

Persistent storage survives process death, device restarts, and (depending on the storage location) even app updates.


Part 2 — Android Storage Architecture

Android provides several storage options.

                Android Storage

     ┌─────────────────┼─────────────────┐
     ▼                 ▼                 ▼
 Preferences        Files           Databases
     │                 │                 │
 DataStore      Images/PDFs        SQLite/Room

Choosing the right option is critical.

Not every piece of data belongs in a database.


Part 3 — The Android Sandbox

Every Android app runs in its own sandbox.

Conceptually:

Phone

├── App A
│      └── Private Files

├── App B
│      └── Private Files

└── App C
       └── Private Files

By default:

  • App A cannot directly read App B’s private storage.
  • Each app has its own Linux user and private directory.

This isolation is a major security feature.


Internal Storage

Internal storage is private to your app.

Characteristics:

  • App-only access
  • Removed when the app is uninstalled
  • No storage permission required for your own files

Good for:

  • User settings
  • Databases
  • Caches
  • Configuration
  • Sensitive files

External Storage

Historically, external storage referred to shared device storage (not necessarily an SD card).

Good for:

  • Photos
  • Videos
  • Documents
  • User-exported files

Modern Android uses scoped storage, limiting broad filesystem access and encouraging app-specific directories or system pickers.


Part 4 — Files

Sometimes you don’t need a database.

Example:

Resume.pdf

Invoice.pdf

Profile.jpg

These are files.

Store them as files—not database rows.

Databases are optimized for structured data, not large binary assets.


Part 5 — SharedPreferences

For many years Android developers used:

SharedPreferences

Example:

Dark Mode = true

Language = English

LoggedIn = true

Perfect?

Not quite.

Problems:

  • Synchronous API (easy to misuse on the main thread)
  • Limited type safety
  • XML-based storage
  • No built-in transactional guarantees
  • Can become cumbersome as settings grow

Google now recommends DataStore for new apps.


Part 6 — Jetpack DataStore

DataStore is the modern replacement.

Two variants:

DataStore

├── Preferences DataStore
└── Proto DataStore

Preferences DataStore

Stores key-value pairs.

Example:

Theme = Dark

Language = English

Notifications = Enabled

Similar conceptually to SharedPreferences.


Proto DataStore

Stores strongly typed objects using Protocol Buffers.

Example:

UserSettings



Theme



Language



Font Size

Advantages:

  • Strong typing
  • Schema evolution
  • Better maintainability for complex settings

DataStore Characteristics

Unlike SharedPreferences:

  • Asynchronous
  • Coroutine-friendly
  • Exposes data as a Flow
  • Transactional updates
  • Better error handling

Example flow:

DataStore



Flow



ViewModel



Compose UI

When to Use DataStore

Use DataStore for:

  • Theme
  • User preferences
  • Small configuration values
  • Feature flags
  • Simple app settings

Don’t use it for thousands of rows of relational data.


Part 7 — SQLite

Android ships with SQLite.

SQLite is:

A lightweight relational database embedded inside the application.

No separate database server is required.

Everything is stored in local database files.


Imagine:

Notes



SQLite Database



Disk

SQLite manages:

  • Tables
  • Indexes
  • Queries
  • Transactions

Relational Database

Imagine an e-commerce app.

Users

Orders

Products

Instead of:

Huge File

Data is organized into tables with relationships.


Tables

Example:

Users

IDName
1Alice
2Bob

Products

IDName
1Laptop
2Phone

Each table stores one type of entity.


Rows

Each record is one row.

Example:

ID = 5

Name = Alice

One user.


Columns

Columns describe attributes.

ID

Name

Age

Email

Every row follows the same structure.


Primary Key

Every table needs a unique identifier.

Example:

ID = 15

No two rows share the same primary key.


Foreign Key

Suppose:

Orders table.

Order



User ID

The User ID references the Users table.

This creates a relationship.


Part 8 — SQL Basics

SQLite uses SQL.

Common operations:


INSERT

Create data.

INSERT INTO users ...

SELECT

Read data.

SELECT * FROM users

UPDATE

Modify data.

UPDATE users

DELETE

Remove data.

DELETE FROM users

These correspond closely to CRUD operations.


Part 9 — Why Room Exists

Using SQLite directly is verbose and error-prone.

Example:

Open Database



Create Query



Execute



Cursor



Convert Objects



Close Cursor

Lots of boilerplate.

Google created Room.


Room

Room is an ORM (Object Relational Mapping) library.

Think:

Kotlin Objects



Room



SQLite

Room maps objects to database rows and generates much of the boilerplate code.


Room Architecture

Entity



DAO



Room



SQLite

These are the three core concepts.


Part 10 — Entity

An Entity represents a database table.

Conceptually:

User



Table

Each property maps to a column.


Example:

User

id

name

email

becomes

idnameemail

Room handles the mapping.


Primary Key in Room

Every entity should define its primary key.

This uniquely identifies each row and enables updates and deletes.


Part 11 — DAO (Data Access Object)

DAO is where queries live.

Responsibilities:

  • Insert
  • Update
  • Delete
  • Query

The DAO abstracts database access from the rest of your app.

Think of it as the database equivalent of a Repository—but at a lower level.


Flow:

Repository



DAO



Room



SQLite

Repositories coordinate data sources; DAOs talk to the database.


Part 12 — Database Class

The database class ties everything together.

Conceptually:

AppDatabase



UserDao



ProductDao



OrderDao

One database can expose multiple DAOs.


Part 13 — Room + Coroutines

Database work can take time.

Never block the Main Thread.

Room integrates naturally with coroutines.

Example flow:

ViewModel



Repository



DAO



Room



SQLite

All executed without freezing the UI.


Part 14 — Room + Flow

One of Room’s most powerful features.

Imagine:

Database



Data Changes



Flow Emits



UI Updates

You don’t need to manually refresh the UI.

Whenever the underlying table changes, Room emits new values through the Flow.

This works exceptionally well with Compose.


Part 15 — Relationships

Real databases rarely have isolated tables.

Example:

User



Orders

One user.

Many orders.

Relationship:

1



Many

One-to-many.

Other common relationships include:

  • One-to-one
  • Many-to-many

Room supports modeling these relationships with annotations and helper classes.


Part 16 — Transactions

Imagine transferring money.

Withdraw



Deposit

Suppose the app crashes between the two operations.

Money disappears.

Bad.

Transactions guarantee:

Either:

Everything Succeeds

or

Everything Rolls Back

Never a partial update.

This preserves data integrity.


Part 17 — Migrations

Suppose version 1:

User



Name

Later:

Version 2:

User



Name



Email

Existing users already have a database.

What happens?

Room needs a migration.

A migration transforms the old schema into the new one without losing data.


Ignoring migrations can result in crashes or data loss (depending on configuration).


Part 18 — Offline-First Architecture

Modern apps don’t always treat the network as the source of truth for the UI.

Instead:

UI



Room



Repository



Network

The UI reads from the local database.

The Repository synchronizes local and remote data.

Benefits:

  • Works offline
  • Faster UI
  • Consistent data source
  • Better user experience

Single Source of Truth

Imagine:

Network



Repository



Room



UI

The UI never reads directly from the network.

It observes Room.

The Repository updates Room after fetching fresh data.

This pattern avoids inconsistencies and simplifies state management.


Part 19 — Synchronization

Suppose:

Phone Offline

User creates:

New Note

Repository:

Save Locally

Later:

Internet Returns



Upload Pending Changes



Mark Synced

This is synchronization.

Many production apps maintain sync queues for pending work.


Conflict Resolution

Imagine:

Phone changes:

Name = Alice

Server changes:

Name = Alicia

Who wins?

Possible strategies:

  • Last write wins
  • Server wins
  • Client wins
  • Merge changes
  • User resolves conflict

The right strategy depends on your product requirements.


Part 20 — Caching Strategies

Common cache approaches:

Memory Cache

RAM

Very fast.

Lost when the app process dies.


Disk Cache

Room

Files

Persistent.

Slower than memory, but survives restarts.


Network Cache

Managed by HTTP cache headers and libraries like OkHttp.

Reduces unnecessary network traffic.


Complete Data Flow

User opens Products screen.

Compose Screen


ViewModel


Repository

 ┌────┴────────────┐
 ▼                 ▼
Room          Retrofit
 │                 │
 └────────┬────────┘

 Updated Database

Flow Emits

Compose Recomposes

The Repository decides when to fetch remote data and when to use local data.

The UI simply observes state.


Common Mistakes

❌ Using DataStore for relational data

DataStore is for preferences and small configuration values—not large datasets.


❌ Storing images in Room

Store image paths or URIs in the database.

Store the actual files separately.


❌ Querying Room on the Main Thread

Always use coroutine-friendly APIs.


❌ Skipping migrations

Schema evolution is inevitable in real apps.

Plan for it.


❌ Letting the UI access DAOs directly

Always go through a Repository to preserve architecture boundaries.


❌ Treating the network as the only source of truth

Offline-first apps generally use the local database as the source observed by the UI.


Mental Model

Imagine a public library.

Reader (UI)


Librarian (ViewModel)


Library Manager (Repository)

 ┌────┴───────────┐
 ▼                ▼
Local Shelves   Book Supplier
(Room)         (Network)

The reader never contacts the book supplier directly.

The librarian doesn’t manage inventory.

Each role has a clear responsibility.


Best Practices

  • Use DataStore for preferences.
  • Use Room for structured relational data.
  • Prefer Flow with Room for reactive updates.
  • Keep DAOs focused on database operations.
  • Keep synchronization logic inside Repositories.
  • Design for offline use whenever practical.
  • Test migrations before releasing schema changes.
  • Separate files from structured database records.

Interview Questions

  1. What is the difference between internal and external storage?
  2. When should you use DataStore instead of Room?
  3. Why is Room preferred over raw SQLite?
  4. What is an Entity in Room?
  5. What is a DAO?
  6. Explain primary keys and foreign keys.
  7. What is a database transaction?
  8. Why are database migrations necessary?
  9. What does “offline-first” architecture mean?
  10. What is a Single Source of Truth in Android architecture?

Module 14 Summary

You now understand how Android persists data locally:

  • Internal storage securely stores app-private files.
  • External storage is suitable for user-accessible content under modern storage rules.
  • DataStore replaces SharedPreferences for settings and preferences.
  • SQLite is Android’s embedded relational database.
  • Room provides a type-safe abstraction over SQLite.
  • Entities, DAOs, and Database classes form the core of Room.
  • Flow enables reactive database updates.
  • Transactions maintain data consistency.
  • Migrations safely evolve schemas over time.
  • Offline-first architecture improves resilience, speed, and user experience.

At this point, you’ve mastered the two primary data sources used in Android applications:

  • Remote data (Retrofit + OkHttp).
  • Local data (Room + DataStore).

The Repository sits between them, orchestrating how data flows through your app.


Next Module: Background Work (Services, WorkManager, Foreground Services & Scheduling)

In Module 15, we’ll answer another critical question:

How can an Android app continue working even when the user isn’t actively using it?

We’ll explore:

  • Android process lifecycle and background execution limits.
  • Services (Started vs Bound).
  • Foreground Services and notifications.
  • Broadcast Receivers.
  • AlarmManager.
  • JobScheduler.
  • WorkManager architecture and internals.
  • Constraints (network, charging, idle, battery).
  • One-time vs periodic work.
  • Chaining and retrying work.
  • Choosing the right background execution API.

This module is essential for building reliable applications that synchronize data, upload files, send notifications, and perform maintenance tasks without compromising battery life or user experience.