Chapter 4: Data Storage and Persistence
Introduction: Why Data Persistence Matters
Mobile applications are not just about interfaces; they are tools for productivity,
communication, and entertainment. A crucial aspect of their functionality is the
ability to remember information. Data persistence is the mechanism that allows an
application to save data to a device's non-volatile memory. This ensures that data
survives the application process being killed or the device being restarted.
Imagine a messaging app that forgets your chat history or a game that resets your
high score every time you close it. Data persistence prevents this, providing a
seamless and personalized user experience. This chapter explores the various tools
and techniques Android provides for storing data, ranging from simple key-value
pairs to complex relational databases and cloud synchronization.
4.1 Shared Preferences
Definition
Shared Preferences is an Android framework API for storing and retrieving small
amounts of primitive data as key-value pairs. It's the go-to solution for simple,
lightweight persistence.
Primary Use Cases
User Settings: Storing preferences like notification on/off, units (metric/imperial),
or display density.
Login State: Remembering if a user has already logged in to skip the login screen
on subsequent app launches.
App Configuration: Saving the last selected tab, the app's theme (light/dark), or a
high score.
Language Selection: Storing the user's preferred language for the app.
Internally, Android manages this data in an XML file within the app's private
storage, making it inaccessible to other applications for security reasons.
Characteristics
Key-Value Pair Structure: Data is accessed via a unique key (a String).
Simplicity: Extremely easy to implement with minimal code.
Data Type Support: Supports String, Set<String>, int, float, long, and boolean.
Persistence: Data remains until the app is uninstalled or the data is manually
cleared.
Privacy: The XML file is private to the application by default
(MODE_PRIVATE).
How Shared Preferences Work
The process involves three main steps:
1. Get the Shared Preferences Object: You can obtain it in two ways:
o Get Shared Preferences(String name, int mode): Use this if you need multiple
preference files, identified by the name.
o [Link](Context context): Use this to get
a single, default preference file for your app.
2. Edit with an Editor: To write data, you must call edit() on
the SharedPreferences object to get a [Link].
3. Apply or Commit Changes: After putting your key-value pairs, you must
call apply() (asynchronous) or commit() (synchronous) to save them.
Code Example: Saving and Retrieving Data
java
// Saving data
public void saveUserPreferences(Context context, String username, boolean isLog
gedIn) {
SharedPreferences prefs = [Link]("MyAppPrefs", Contex
t.MODE_PRIVATE);
[Link] editor = [Link]();
[Link]("username", username);
[Link]("isLoggedIn", isLoggedIn);
[Link]("launchCount", 5);
[Link](); // or [Link]();
}
// Retrieving data
public void loadUserPreferences(Context context) {
SharedPreferences prefs = [Link]("MyAppPrefs", Contex
t.MODE_PRIVATE);
// The second parameter is a default value if the key is not found
String username = [Link]("username", "Guest");
boolean isLoggedIn = [Link]("isLoggedIn", false);
int launchCount = [Link]("launchCount", 0);
// Use the retrieved data...
}
Advantages
Fast and Efficient: Ideal for quick read/write operations on small data sets.
Easy to Learn and Implement: Requires no SQL knowledge or complex setup.
Android Native: Built directly into the Android framework.
Limitations
Not for Complex or Structured Data: You cannot store a list of objects or a
relational data structure.
Not for Large Datasets: Performance degrades significantly with large amounts
of data.
No Query Capabilities: You cannot run queries like "find all users with a specific
name".
4.2 SQLite Database
Definition
SQLite is a lightweight, disk-based, relational database management system
(RDBMS) embedded directly into the Android operating system. It is ACID-
compliant and implements most of the SQL standard. For applications that need to
manage structured, repeating data, SQLite is a powerful and reliable solution.
Primary Use Cases
Complex Data Storage: Storing thousands of records, such as a music library, a
product catalog, or a list of contacts.
Data Analysis: Performing complex queries, aggregations, and sorting on the data.
Relationships: Modeling real-world relationships between different types of data
(e.g., a user has many posts, a post belongs to a user).
Core Concepts
Table: Data is organized into tables, similar to a spreadsheet.
Row: A single, horizontal record in a table.
Column: A vertical field representing a specific attribute of the record.
Primary Key: A unique identifier for each row in a table.
SQL (Structured Query Language): The language used to interact with the
database.
Basic SQL Operations (CRUD)
CREATE: CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT,
email TEXT UNIQUE);
INSERT: INSERT INTO users (name, email) VALUES ('Alice',
'alice@[Link]');
SELECT (READ): SELECT * FROM users WHERE name = 'Alice';
UPDATE: UPDATE users SET email = '[Link]@[Link]' WHERE id = 1;
DELETE: DELETE FROM users WHERE id = 1;
Working with SQLite in Android (The Traditional Way)
Developers typically create a helper class that extends SQLiteOpenHelper. This
class manages database creation and version management. You then
use getWritableDatabase() or getReadableDatabase() to obtain
a SQLiteDatabase object, on which you can execute raw SQL queries or use helper
methods like insert(), query(), update(), and delete().
Advantages
Full Relational Database: Supports complex data structures and relationships.
Performance with Large Data: Highly optimized for structured data and complex
queries.
Standard SQL: Leverages existing SQL knowledge.
Limitations (in the context of modern Android development)
Boilerplate Code: Requires a significant amount of code to define the database
schema and write helper methods.
No Compile-Time Safety: Raw SQL queries are written as Strings. A simple typo
in a query will only crash the app at runtime.
Manual Object Mapping: You must manually write code to convert
database Cursor objects into your application's data model objects
(e.g., User objects).
4.3 Room Persistence Library (The Modern Standard)
Definition
Room is a persistence library that is part of Android Jetpack. It acts as an
abstraction layer over SQLite, aiming to make database access more robust and
less boilerplate-heavy while still leveraging the full power of SQLite. It is now the
recommended way to work with SQLite databases in Android.
Why Room is the Preferred Choice
Room addresses the key limitations of raw SQLite:
1. Compile-Time Verification of SQL Queries: Room checks your SQL queries at
compile time. If a query has a syntax error or references a non-existent column,
your build will fail, preventing a runtime crash.
2. Reduced Boilerplate Code: By using annotations, Room automatically generates
the code to create tables and perform object mapping.
3. Seamless Integration with Architecture Components: Room works flawlessly
with other Jetpack components like LiveData, ViewModel, and Kotlin Flow,
making it easy to build reactive and lifecycle-aware applications.
Main Components of Room
Room is built around three primary components:
1. Entity: Represents a table within the database. You create a regular data class and
annotate it with @Entity. The class's fields correspond to the table's columns.
java
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "full_name")
public String name;
public String email;
}
2. DAO (Data Access Object): An interface annotated with @Dao that defines the
methods for accessing the database. Room uses annotations
like @Insert, @Update, @Delete, and @Query to generate the necessary code.
java
@Dao
public interface UserDao {
@Insert
void insertUser(User user);
@Query("SELECT * FROM users WHERE id = :userId")
User getUserById(int userId);
@Query("SELECT * FROM users")
List<User> getAllUsers();
}
3. Database: An abstract class that extends RoomDatabase. It is annotated
with @Database, lists the entities, and provides abstract methods to get the DAO
instances. This class serves as the main access point to the underlying SQLite
database.
java
@Database(entities = {[Link]}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
To use the database, you get an instance via [Link](...).
Advantages of Room
Compile-time Safety: Catches SQL errors during development.
Less Code: Annotations handle the heavy lifting.
Excellent Architecture Fit: Designed to work with ViewModels, LiveData, and
coroutines, promoting a clean and testable architecture.
Built-in Migration Support: Provides a robust system for handling database
schema changes as your app evolves.
4.4 Content Providers
Definition
A Content Provider is one of the fundamental Android components (alongside
Activities, Services, and Broadcast Receivers). Its primary purpose is to manage
access to a structured set of data. It encapsulates the data and provides mechanisms
for defining data security. They are primarily used to share data between different
applications.
Why Content Providers Exist
Android's security model sandboxes applications, preventing them from directly
accessing each other's files or databases. A Content Provider acts as a bridge,
offering a standardized interface to share data securely. The requesting app does
not need to know the details of how the data is stored, only that it can be accessed
via a specific URI.
Common Examples
Contacts Provider: Used to access the user's contact list from any app.
MediaStore Provider: Used to access images, videos, and audio files on the
device.
Calendar Provider: Used to read and write calendar events.
Key Concepts
Content URI: A unique URI that identifies the provider and the specific data to
access. It follows the format: content://<authority>/<path>/<id>.
e.g., content://[Link]/contacts/1.
Content Resolver: The client-side object (obtained via getContentResolver()) that
applications use to communicate with any Content Provider. It sends method calls
to the corresponding provider.
MIME Type: Describes the type of data being returned by the provider
(e.g., [Link]/[Link] for a directory of people,
or [Link]/[Link] for a single person).
Operations Supported (CRUD)
Content Providers expose a public interface that mirrors database operations:
insert(): Adds a new row.
query(): Retrieves data.
update(): Modifies existing rows.
delete(): Removes rows.
When to Create Your Own Content Provider
You generally only need to create your own Content Provider if you want to share
your app's private data with other apps in a controlled manner. If all data is for
internal use only, Room is the superior choice.
4.5 Data Synchronization Concepts
Definition
Data Synchronization (Data Sync) is the process of establishing consistency
between data on a mobile device and a remote server (the "source of truth"). It
involves continuously reconciling changes made on both ends to ensure both
copies are up-to-date.
Why It's Crucial
Modern apps are rarely standalone. They interact with cloud services. Consider a
to-do list app: you can add tasks on your phone and later view and complete them
on your tablet. Data sync makes this possible.
Types of Synchronization
One-Way Sync: Data flows exclusively in one direction.
o Server to Device: A news app pushes the latest articles to the device. Changes on
the device are not sent back.
o Device to Server: A sensor data collection app sends readings to a server. The
server does not send data back.
Two-Way (or Bi-directional) Sync: Data flows in both directions, and changes
made on either end are merged. This is the most common and complex type.
o Example: A note-taking app like Google Keep. You can add a note on your phone,
and edit another note on the web. The sync process ensures both devices end up
with the same, merged set of notes.
Challenges and Strategies in Data Synchronization
1. Network Interruption: Syncing must be resilient to dropped connections.
o Strategy: Implement retry mechanisms with exponential backoff.
2. Data Conflicts: The most challenging problem. What happens when you edit a
shopping list item's name on your phone to "Milk" and simultaneously on your
tablet to "2% Milk"?
o Strategies:
Last Write Wins (LWW): The most recent change (based on a timestamp)
overwrites the other.
Merge: Attempt to intelligently merge the changes. For a text field, this is
difficult. For a list, it might involve keeping both items.
Manual Resolution: Ask the user to decide which version to keep.
3. Duplicate Records: If a sync is interrupted and retried, it might send the same data
twice.
o Strategy: Use unique identifiers (UUIDs) generated on the client to allow the
server to de-duplicate records.
4. Efficiency: Syncing an entire database every time is slow and consumes a lot of
data.
o Strategies:
Incremental Updates: Only sync the changes that occurred since the last
successful sync.
Timestamp Comparison: Track a last_modified timestamp for each record and
only sync records newer than the last sync time.
4.6 Offline-First Mobile Data Design
Definition
Offline-First is a design philosophy and architecture where an application's core
functionality is built around a local data store. Instead of treating the network as
always available, the app assumes it is often unreliable or absent. The local
database is the single source of truth for the UI, and synchronization with the cloud
happens seamlessly in the background.
Core Principles
Local is Primary: The user can perform all read and write operations on the local
data store (e.g., Room database) at any time, regardless of network connectivity.
Sync is a Background Process: Data synchronization with the remote server is a
continuous, background task. The user should never have to wait for a network
request to complete to use the app.
Optimistic UI: When a user performs an action (e.g., "like" a post), the UI updates
immediately, assuming the operation will succeed. The actual network request
happens in the background. If it fails, the UI is later updated to reflect the error.
How an Offline-First App Works
1. Data Read: When the UI needs data, it always queries the local database (e.g.,
Room).
2. Data Write: When the user creates or modifies data, it is first saved to the local
database.
3. Synchronization: A background service (e.g., WorkManager) monitors network
connectivity and the local database for unsynced changes.
4. Conflict Resolution: When the network is available, the background service
pushes local changes to the server and pulls down remote changes. It applies a pre-
defined conflict resolution strategy to merge the data.
5. UI Update: Once the local database is updated with the synced data, the UI
automatically refreshes (e.g., using LiveData or Flow), showing the most current
information.
Technologies Used in Offline-First Architecture
Local Database: Room (for structured data), DataStore (for preferences).
Background Processing: WorkManager for scheduling reliable, deferrable
background work like syncing.
Network Layer: Retrofit for making API calls.
Repository Pattern: A repository class acts as a single source of truth for the UI.
It decides whether to fetch data from the local database or the network and
orchestrates the data flow.
Background Synchronization: Libraries and services that manage the sync logic.
Benefits of an Offline-First Approach
Superior User Experience: The app feels instantaneous and works everywhere,
even in areas with poor connectivity (subways, remote locations).
Improved Performance: Reading from a local database is always faster than
making a network call.
Reduced Network Usage: By only syncing changes, you minimize unnecessary
data transfer, saving the user's data plan and battery.
Increased Robustness: The app is not crippled by server outages or network
problems.
Real-World Examples
Google Docs/Sheets: Allows full editing offline. Changes are synced when a
connection is re-established.
Spotify: Downloads playlists for offline listening.
WhatsApp: Messages are stored locally and are viewable even without internet.
Outgoing messages are queued locally and sent when the network returns.
SQLite Database in Mobile Application Development
SQLite is a lightweight, embedded relational database used in mobile
applications to store structured data locally on the device. It is widely used in
mobile platforms such as Android and iOS.
Unlike large database systems (like MySQL or PostgreSQL), SQLite does not
require a separate server. The entire database is stored in a single file inside the
mobile device, making it fast and efficient for mobile apps.
1. Definition
SQLite database is a self-contained, serverless, and lightweight database
engine that allows mobile applications to store, retrieve, update, and delete data
using SQL (Structured Query Language).
It is commonly used when an app needs to save structured data permanently on
the device.
2. Key Characteristics of SQLite
Some important features include:
1. Serverless Database
SQLite runs inside the application. It does not require a database server.
2. Lightweight
It uses very little memory, which is suitable for mobile devices.
3. File-Based Storage
The database is stored as a single file on the device storage.
4. Relational Database
Data is organized in tables with rows and columns, similar to traditional
databases.
5. Supports SQL
You can use SQL commands like:
o CREATE
o INSERT
o SELECT
o UPDATE
o DELETE
3. Why SQLite is used in Mobile Applications
SQLite is used because mobile apps often need offline data storage.
Common uses include:
Saving user information
Storing application settings
Saving messages or chat history
Storing offline data
Managing large structured datasets
Example apps using SQLite include:
Contacts storage
Offline note applications
Local caching for internet data
4. Basic SQLite Operations
1. Create Database and Table
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT,
department TEXT
);
2. Insert Data
INSERT INTO students (name, department)
VALUES ('John', 'Computer Science');
3. Retrieve Data
SELECT * FROM students;
4. Update Data
UPDATE students
SET department = 'Software Engineering'
WHERE id = 1;
5. Delete Data
DELETE FROM students WHERE id = 1;
5. Example Use in Android
In Android applications, developers usually interact with SQLite using classes such
as:
SQLiteDatabase
SQLiteOpenHelper
Example structure:
public class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, "SchoolDB", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
[Link]("CREATE TABLE students(id INTEGER PRIMARY KEY,
name TEXT)");
}
}
6. Advantages of SQLite
Fast data access
Works offline
No server configuration needed
Reliable and stable
Open source
7. Limitations
Not suitable for very large databases
Limited multi-user concurrency
Less powerful than enterprise databases