IT272 Mobile Application Development

Mobile Application DevelopmentUnit 615 min read

Data Storage in Mobile Apps: Preferences, Files & SQLite

Unit 6 of Mobile Application Development explores three essential data storage methods in Android—SharedPreferences for simple key-value pairs, Files for structured data, and SQLite for relational databases—with step-by-step implementation, comparisons, and real-world use cases like eSewa’s transaction logs and Daraz’s

TAKEAWAYS:

  • SharedPreferences stores primitive data (strings, booleans) as key-value pairs using XML files, ideal for app settings but limited to small, non-relational data.
  • File storage (internal/external) handles larger, structured data (JSON, CSV) but requires manual serialization and lacks built-in querying.
  • SQLite is a lightweight relational database embedded in Android, perfect for complex queries (e.g., user profiles, transaction histories) with ACID compliance.
  • Internal storage is private to the app; external storage (SD card) requires permissions and user consent, risking data loss if unmanaged.
  • ContentProviders enable secure data sharing between apps (e.g., Contacts app) via URIs, adhering to Android’s security model.
  • Best practices: Use SharedPreferences for configs, SQLite for structured data, and encrypt sensitive files (e.g., bank apps like Ncell’s transaction records).

1. SharedPreferences: Simple Key-Value Storage

SharedPreferences is Android’s built-in mechanism for storing primitive data types (strings, integers, booleans, floats) in XML format. It’s lightweight and ideal for app settings, login states, or user preferences.

0isLoggedInusername1—2—3—
SharedPreferences as a hash table (keys: strings, values: primitives)

How It Works

  • Data is stored as key-value pairs in a private XML file (e.g., app_preferences.xml).
  • No need for a database schema; keys are strings, values are primitives.
  • Methods:
    • getSharedPreferences(): Retrieves a SharedPreferences object.
    • edit(): Opens an editor to modify values.
    • putString(), putInt(), etc.: Add/update values.
    • commit() or apply(): Saves changes (commit is synchronous; apply is asynchronous).

Example: Storing User Login State

// Save login state
SharedPreferences prefs = getSharedPreferences("UserPrefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("isLoggedIn", true);
editor.putString("username", "user123");
editor.apply();

// Retrieve login state
boolean loggedIn = prefs.getBoolean("isLoggedIn", false);
String username = prefs.getString("username", "guest");

Visual: SharedPreferences File Structure

Advantages and Limitations

Advantages Limitations
Simple API, no setup required Limited to primitive data types
Persists across app restarts No querying or complex operations
Lightweight, fast access Not suitable for large datasets

2. File Storage: Internal vs. External

Android supports two types of file storage:

  1. Internal Storage: Private to the app, no permissions needed.
  2. External Storage: Public (e.g., SD card), requires READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE permissions.
PrivateRequires PermissionUser AccessibleInternal StorageExternal Storage (SD Card)App DataPublic Directory
File storage paths in Android (internal vs. external)

How It Works

  • Internal Storage:
    • Files are stored in /data/data/<package_name>/files/.
    • Use openFileOutput() to write and openFileInput() to read.
  • External Storage:
    • Files are stored in /storage/emulated/0/Android/data/<package_name>/files/ or public directories like Environment.getExternalStoragePublicDirectory().
    • Requires runtime permissions (Android 6.0+).

Example: Saving a JSON File Internally

// Save JSON to internal storage
String json = "{\"name\":\"John\", \"age\":30}";
FileOutputStream fos = openFileOutput("user_data.json", Context.MODE_PRIVATE);
fos.write(json.getBytes());
fos.close();

// Read JSON
FileInputStream fis = openFileInput("user_data.json");
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
String fileContent = new String(buffer);
fis.close();

Visual: File Storage Paths

Real-World Example: Daraz Order History

Daraz stores order details (JSON/XML) in internal storage for quick access during the user’s session. For backup, it uses external storage (with user consent) to sync orders to the cloud.


3. SQLite: Relational Database for Mobile Apps

SQLite is a serverless, file-based database included in Android. It supports SQL queries, transactions, and is ideal for structured data like user profiles, transaction logs, or app content.

503070204080
BST after inserting [50, 30, 70, 20, 40] (step-by-step growth shown in note)
erDiagram
  users ||--o{ orders : places
  users {
    int id PK
    string name
    string email
  }
  orders {
    int id PK
    int user_id FK
    string order_details
    date timestamp
  }
SQLite ER diagram for a user-order system (like Daraz)

How It Works

  1. Database Helper Class: Extend SQLiteOpenHelper to create/upgrade the database.
  2. SQL Queries: Use sqlite3 commands (CREATE TABLE, INSERT, SELECT) via SQLiteDatabase.
  3. CRUD Operations: Perform Create, Read, Update, Delete operations.

Example: User Profile Database

// Database Helper
public class UserDBHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME = "UserDatabase.db";
    public static final String TABLE_NAME = "users";
    public static final String COL_ID = "id";
    public static final String COL_NAME = "name";
    public static final String COL_EMAIL = "email";

    public UserDBHelper(Context context) {
        super(context, DATABASE_NAME, null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE " + TABLE_NAME +
                  " (" + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                  COL_NAME + " TEXT, " + COL_EMAIL + " TEXT)");
    }
}

// Insert a user
UserDBHelper helper = new UserDBHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(UserDBHelper.COL_NAME, "Alice");
values.put(UserDBHelper.COL_EMAIL, "alice@example.com");
db.insert(UserDBHelper.TABLE_NAME, null, values);

// Query users
Cursor cursor = db.query(UserDBHelper.TABLE_NAME,
                        null, null, null, null, null, null);
while (cursor.moveToNext()) {
    String name = cursor.getString(cursor.getColumnIndex(UserDBHelper.COL_NAME));
    Log.d("User", name);
}

Visual: SQLite Table Creation and Query

Visual: SQLite Query Execution Steps

flowchart TD
    A["Start"] --> B["Open Database"]
    B --> C["Prepare Query: 'SELECT * FROM users'"]
    C --> D["Execute Query"]
    D --> E["Cursor: Move to First Row"]
    E --> F{"Has Next Row?"}
    F -->|"Yes"| G["Read Data"]
    G --> E
    F -->|"No"| H["Close Cursor"]
    H --> I["End"]

Advantages and Limitations

Advantages Limitations
Full SQL support No built-in encryption (use SQLCipher)
ACID transactions Slower than SharedPreferences for small data
Scalable for complex queries Requires more boilerplate code

4. ContentProviders: Secure Data Sharing

ContentProviders enable apps to share data securely via URIs. They are essential for inter-app communication (e.g., Contacts app).

How It Works

  • Define a ContentProvider class extending ContentProvider.
  • Implement methods like query(), insert(), update(), delete().
  • Use ContentResolver to interact with the provider.

Example: Sharing Contacts

// Define a ContentProvider for contacts
public class ContactProvider extends ContentProvider {
    private static final String CONTENT_URI = "content://com.example.contacts/contacts";
    private SQLiteDatabase db;

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        db = new UserDBHelper(getContext()).getReadableDatabase();
        return db.query("contacts", projection, selection, selectionArgs,
                         null, null, sortOrder);
    }
    // Implement other required methods...
}

// Query contacts from another app
Cursor cursor = getContentResolver().query(
    Uri.parse("content://com.example.contacts/contacts"),
    null, null, null, null);

Visual: ContentProvider Architecture


In the Real World

  1. eSewa (Nepal):

    • Uses SQLite to store transaction logs (user ID, amount, timestamp) for offline processing. When online, it syncs with the server via ContentProvider.
    • Why SQLite? Needs fast local queries for receipt generation and fraud detection.
  2. Khalti (Nepal):

    • Stores user payment preferences (e.g., default bank account) in SharedPreferences for quick access during checkout.
    • Why SharedPreferences? Small, frequently accessed data with no complex relationships.
  3. Daraz (Nepal):

    • Uses internal file storage (JSON) to cache product listings for offline browsing. Large images are stored in external storage (with user permission).
    • Why Files? Simpler than SQLite for unstructured data like product catalogs.
  4. Ncell (Nepal):

    • Employs SQLite with encryption (SQLCipher) to secure customer call logs and usage data. Uses ContentProvider to share data with billing apps.
    • Why Encryption? Protects sensitive telecom data under GDPR-like regulations.
  5. NEPSE (Nepal Stock Exchange):

    • Mobile apps (e.g., NEPSE’s official app) use SQLite to cache stock prices and portfolios. Offline charts are rendered from local JSON files.
    • Why Hybrid Storage? Balances query speed (SQLite) and large dataset handling (files).

Exam Tip

  1. SharedPreferences:

    • Remember to use apply() for async saves and commit() for sync (though apply() is preferred).
    • Common Exam Question: "How would you store a user’s last login time?" → Use putLong() with System.currentTimeMillis().
  2. File Storage:

    • Permissions: Always check Environment.getExternalStorageState() before writing to external storage.
    • Exam Pitfall: Forgetting to handle FileNotFoundException or IOException in file operations.
  3. SQLite:

    • Schema Design: Know how to define PRIMARY KEY, FOREIGN KEY, and constraints.
    • Querying: Practice JOIN operations (e.g., linking users to orders).
    • Exam Question: "Write SQL to fetch all orders > Rs. 5000 for a user." →
      SELECT * FROM orders WHERE user_id = ? AND amount > 5000;
      
  4. ContentProviders:

    • Understand the URI format: content://<authority>/<path>.
    • Exam Tip: Always use ContentResolver to interact with providers, not direct database access.
  5. Comparisons:

    • Exam Question: "When would you use SharedPreferences vs. SQLite?"
      Use Case SharedPreferences SQLite
      App settings ✅ Yes ❌ No
      User profiles ❌ No ✅ Yes
      Large datasets ❌ No ✅ Yes
      Simple key-value pairs ✅ Yes ❌ Overkill

Worked Example: Bank Loan Calculator App

Scenario: A bank app (like NMB Bank) needs to store loan details (principal, interest rate, tenure) and calculate monthly payments. Which storage method to use?

Solution

  1. SharedPreferences: Store user preferences (e.g., showInterestBreakdown = true).
  2. SQLite: Store loan history (principal, rate, tenure, EMI) for querying.
  3. Internal Files: Cache EMI calculation results (JSON) for quick access.

SQLite Table Design

CREATE TABLE loans (
    loan_id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER,
    principal REAL,
    rate REAL,  -- Annual interest rate (e.g., 8.5)
    tenure INTEGER,  -- In months
    emi REAL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

EMI Calculation Query

// Calculate EMI using SQLite
String query = "SELECT principal, rate, tenure FROM loans WHERE loan_id = ?";
Cursor cursor = db.rawQuery(query, new String[]{String.valueOf(loanId)});
if (cursor.moveToFirst()) {
    double principal = cursor.getDouble(0);
    double rate = cursor.getDouble(1) / 12 / 100;  // Monthly rate
    int tenure = cursor.getInt(2);
    double emi = (principal * rate * Math.pow(1 + rate, tenure)) /
                 (Math.pow(1 + rate, tenure) - 1);
    // Update EMI in database
    ContentValues values = new ContentValues();
    values.put("emi", emi);
    db.update("loans", values, "loan_id = ?", new String[]{String.valueOf(loanId)});
}

Visual: EMI Calculation Flow

flowchart TD
    A["Start"] --> B["Fetch Loan Data from SQLite"]
    B --> C["Calculate EMI: P * r * (1 + r)^n / ((1 + r)^n - 1)"]
    C --> D["Update EMI in SQLite"]
    D --> E["Display EMI to User"]
    E --> F["End"]

Summary Table: Storage Methods Comparison

Method Data Type Use Case Performance Security
SharedPreferences Primitives (String, int, etc.) App settings, flags ⚡ Very Fast Low (XML file)
Internal Files Any (JSON, CSV, binary) Large structured data ⚡ Fast Medium (encrypt manually)
External Files Any Backups, media 🐢 Slow (I/O) Low (permissions required)
SQLite Structured (tables) User data, transactions 🐢 Moderate Medium (encrypt with SQLCipher)
ContentProvider Any (via URI) Inter-app data sharing 🐢 Moderate High (Android security)

Final Checklist for Exams

  1. SharedPreferences:

    • Know the methods: getSharedPreferences(), edit(), putXxx(), apply().
    • Remember: No complex queries; only key-value pairs.
  2. File Storage:

    • Internal vs. external paths.
    • Permissions: WRITE_EXTERNAL_STORAGE (deprecated in Android 11+; use MANAGE_EXTERNAL_STORAGE carefully).
  3. SQLite:

    • DatabaseHelper lifecycle (onCreate(), onUpgrade()).
    • CRUD operations with ContentValues and Cursor.
    • Joins and aggregations (e.g., GROUP BY, HAVING).
  4. ContentProviders:

    • URI format and authority.
    • ContentResolver usage.
  5. Real-World Mapping:

    • eSewa → SQLite for transactions.
    • Daraz → Files for product cache.
    • Ncell → Encrypted SQLite for call logs.

In the real world

  • Ncell’s Transaction Records: Uses SQLite to store structured transaction logs (user_id, amount, timestamp) for quick querying and reporting. The database ensures ACID compliance for critical financial data.
  • Daraz Order History: Stores order details (JSON/XML) in internal storage for offline access, while syncing to external storage (cloud) for backup with user consent.
  • Khalti Payment App: Uses SharedPreferences to cache user login tokens (e.g., isLoggedIn, userToken) for seamless session management, reducing server calls.

Based on the TU BIM syllabus for Mobile Application Development (IT272), unit 6.

Discussion

Loading…