Table of Contents
Introduction to Data Management in Android
1.1 Why Data Management Matters
1.2 Overview of Data Storage Options
SharedPreferences for Simple Data Storage
2.1 What is SharedPreferences?
2.2 Real-Life Use Cases
2.3 Step-by-Step Implementation
2.4 Best Practices and Exception Handling
2.5 Pros, Cons, and Alternatives
Introduction to Room Database for Persistent Storage
3.1 What is Room Database?
3.2 Real-Life Use Cases
3.3 Setting Up Room in Android Studio
3.4 Basic CRUD Operations with Room
3.5 Best Practices and Exception Handling
3.6 Pros, Cons, and Alternatives
File Storage: Internal and External Storage
4.1 Understanding File Storage in Android
4.2 Internal Storage: Implementation and Examples
4.3 External Storage: Implementation and Examples
4.4 Best Practices and Exception Handling
4.5 Pros, Cons, and Alternatives
Handling JSON Data with Gson and Moshi
5.1 Introduction to JSON Parsing
5.2 Using Gson for JSON Parsing
5.3 Using Moshi for JSON Parsing
5.4 Real-Life Example: Fetching and Displaying Weather Data
5.5 Best Practices and Exception Handling
5.6 Pros, Cons, and Alternatives
Practical Exercise: Building a Note-Taking App
6.1 Project Overview
6.2 Step-by-Step Implementation
6.3 Testing and Debugging
6.4 Enhancing the App with Advanced Features
Conclusion and Next Steps
7.1 Recap of Learning Outcomes
7.2 Further Learning Resources
1. Introduction to Data Management in Android
1.1 Why Data Management Matters
Data is the backbone of any mobile application. Whether it's saving user preferences, storing a list of tasks, or caching API responses, effective data management ensures a seamless user experience. In Android, developers have multiple options to store and manage data, each suited for specific use cases. This module explores SharedPreferences, Room database, file storage, and JSON parsing, guiding you from beginner to advanced concepts with real-life examples.
1.2 Overview of Data Storage Options
Android provides several data storage mechanisms:
SharedPreferences: For lightweight key-value pair storage.
Room Database: For structured, persistent data storage.
File Storage: For saving files in internal or external storage.
JSON Parsing: For handling structured data from APIs or local files.
Each method has unique advantages, and choosing the right one depends on your app’s requirements.
2. SharedPreferences for Simple Data Storage
2.1 What is SharedPreferences?
SharedPreferences is a lightweight storage mechanism in Android for saving key-value pairs. It’s ideal for storing small amounts of data, such as user settings (e.g., theme preference) or login status.
2.2 Real-Life Use Cases
App Settings: Save user preferences like dark mode or notification settings.
User Sessions: Store login state or user ID.
Simple Counters: Track the number of app launches.
2.3 Step-by-Step Implementation
Let’s create a simple app that saves and retrieves a user’s preferred theme (light or dark) using SharedPreferences.
Step 1: Add Permissions (Optional)
No permissions are required for SharedPreferences as it uses internal storage.
Step 2: Create the UI
Create a layout with a toggle button to switch themes and a text view to display the current theme.
<!-- res/layout/activity_main.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/themeStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Current Theme: Light"/>
<Button
android:id="@+id/toggleThemeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toggle Theme"/>
</LinearLayout>
Step 3: Implement SharedPreferences Logic
In your MainActivity, use SharedPreferences to save and retrieve the theme preference.
// app/src/main/java/com/example/myapp/MainActivity.java
package com.example.myapp;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String PREFS_NAME = "MyPrefs";
private static final String KEY_THEME = "theme";
private TextView themeStatus;
private boolean isDarkTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
themeStatus = findViewById(R.id.themeStatus);
Button toggleThemeButton = findViewById(R.id.toggleThemeButton);
// Initialize SharedPreferences
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
isDarkTheme = prefs.getBoolean(KEY_THEME, false); // Default is light theme
updateThemeUI();
toggleThemeButton.setOnClickListener(v -> {
isDarkTheme = !isDarkTheme;
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(KEY_THEME, isDarkTheme);
editor.apply(); // Asynchronous save
updateThemeUI();
});
}
private void updateThemeUI() {
themeStatus.setText("Current Theme: " + (isDarkTheme ? "Dark" : "Light"));
// Apply theme (simplified for example)
getWindow().getDecorView().setBackgroundColor(isDarkTheme ? 0xFF333333 : 0xFFFFFFFF);
}
}
Step 4: Test the App
Run the app, toggle the theme, and close/reopen it to verify that the theme preference persists.
2.4 Best Practices and Exception Handling
Use apply() for Asynchronous Saves: apply() is preferred over commit() for better performance.
Handle Default Values: Always provide default values when retrieving data to avoid null issues.
Clear Preferences When Needed:
SharedPreferences.Editor editor = prefs.edit();
editor.clear().apply(); // Clears all preferences
Exception Handling:
try {
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
isDarkTheme = prefs.getBoolean(KEY_THEME, false);
} catch (ClassCastException e) {
// Handle case where key exists but type is incorrect
isDarkTheme = false;
}
2.5 Pros, Cons, and Alternatives
Pros:
Simple and lightweight.
No setup overhead.
Ideal for small data.
Cons:
Not suitable for complex or large datasets.
No querying capabilities.
Alternatives:
Room Database: For structured data.
File Storage: For larger, unstructured data.
3. Introduction to Room Database for Persistent Storage
3.1 What is Room Database?
Room is a persistence library provided by Google as part of Android Jetpack. It’s an abstraction layer over SQLite, making it easier to store and manage structured data.
3.2 Real-Life Use Cases
Note-Taking Apps: Store notes with titles, content, and timestamps.
E-Commerce Apps: Save product lists or cart items locally.
Fitness Trackers: Log workout data.
3.3 Setting Up Room in Android Studio
Step 1: Add Dependencies
Add Room dependencies to your build.gradle (app-level).
// app/build.gradle
dependencies {
implementation "androidx.room:room-runtime:2.6.1"
annotationProcessor "androidx.room:room-compiler:2.6.1"
// For Kotlin (if used)
kapt "androidx.room:room-compiler:2.6.1"
implementation "androidx.room:room-ktx:2.6.1" // Optional for coroutines
}
Sync the project.
Step 2: Define the Entity
Create a Note entity to represent a note in the database.
// app/src/main/java/com/example/myapp/Note.java
package com.example.myapp;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "notes")
public class Note {
@PrimaryKey(autoGenerate = true)
private int id;
private String title;
private String content;
private long timestamp;
// Constructor
public Note(String title, String content, long timestamp) {
this.title = title;
this.content = content;
this.timestamp = timestamp;
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}
Step 3: Create the DAO
Define a Data Access Object (DAO) to handle database operations.
// app/src/main/java/com/example/myapp/NoteDao.java
package com.example.myapp;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface NoteDao {
@Insert
void insert(Note note);
@Update
void update(Note note);
@Delete
void delete(Note note);
@Query("SELECT * FROM notes ORDER BY timestamp DESC")
List<Note> getAllNotes();
}
Step 4: Set Up the Database
Create the Room database class.
// app/src/main/java/com/example/myapp/AppDatabase.java
package com.example.myapp;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {Note.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract NoteDao noteDao();
}
Step 5: Initialize the Database
Initialize Room in your activity or application class.
// In Application class or Activity
AppDatabase db = Room.databaseBuilder(
getApplicationContext(),
AppDatabase.class,
"note_database"
).build();
3.4 Basic CRUD Operations with Room
Create
Note note = new Note("Meeting", "Discuss project timeline", System.currentTimeMillis());
new Thread(() -> db.noteDao().insert(note)).start();
Read
List<Note> notes = db.noteDao().getAllNotes(); // Run on background thread
Update
note.setContent("Updated content");
new Thread(() -> db.noteDao().update(note)).start();
Delete
new Thread(() -> db.noteDao().delete(note)).start();
3.5 Best Practices and Exception Handling
Run Database Operations on Background Threads: Use AsyncTask, Executors, or coroutines to avoid blocking the UI thread.
Handle Database Migrations:
@Database(entities = {Note.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
// Migration from version 1 to 2
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE notes ADD COLUMN priority INTEGER");
}
};
}
Exception Handling:
try {
db.noteDao().insert(note);
} catch (SQLiteConstraintException e) {
// Handle constraint violations (e.g., duplicate primary key)
Log.e("DatabaseError", "Insert failed: " + e.getMessage());
}
3.6 Pros, Cons, and Alternatives
Pros:
Structured data storage.
Built-in support for queries and relationships.
Type-safe with compile-time checks.
Cons:
Steeper learning curve than SharedPreferences.
Overhead for small datasets.
Alternatives:
SQLiteOpenHelper: Direct SQLite access (more manual).
Firebase Realtime Database: For cloud-based storage.
4. File Storage: Internal and External Storage
4.1 Understanding File Storage in Android
Android supports two types of file storage:
Internal Storage: Private to the app, not accessible by other apps.
External Storage: Shared storage (e.g., SD card or public directories).
4.2 Internal Storage: Implementation and Examples
Example: Save a Text File
// Save to internal storage
String filename = "myfile.txt";
String fileContents = "Hello, Android!";
try (FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE)) {
fos.write(fileContents.getBytes());
} catch (IOException e) {
Log.e("FileError", "Write failed: " + e.getMessage());
}
Read from Internal Storage
StringBuilder content = new StringBuilder();
try (FileInputStream fis = openFileInput(filename)) {
int character;
while ((character = fis.read()) != -1) {
content.append((char) character);
}
} catch (IOException e) {
Log.e("FileError", "Read failed: " + e.getMessage());
}
4.3 External Storage: Implementation and Examples
Step 1: Add Permissions
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Step 2: Check Permissions
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
Step 3: Save to External Storage
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS), "myfile.txt");
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write("Hello, Android!".getBytes());
} catch (IOException e) {
Log.e("FileError", "Write failed: " + e.getMessage());
}
4.4 Best Practices and Exception Handling
Check Storage Availability:
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)) {
// Handle storage not available
}
Use Scoped Storage (Android 10+): Prefer Storage Access Framework (SAF) for better security.
Close Streams Properly: Use try-with-resources to avoid resource leaks.
4.5 Pros, Cons, and Alternatives
Pros:
Suitable for large files (e.g., images, videos).
Flexible for custom file formats.
Cons:
Requires permission handling for external storage.
No built-in querying like databases.
Alternatives:
Room Database: For structured data.
Cloud Storage: Firebase Storage for remote file storage.
5. Handling JSON Data with Gson and Moshi
5.1 Introduction to JSON Parsing
JSON (JavaScript Object Notation) is widely used for API responses and local data storage. Libraries like Gson and Moshi simplify parsing JSON into Java objects.
5.2 Using Gson for JSON Parsing
Step 1: Add Gson Dependency
implementation "com.google.code.gson:gson:2.10.1"
Step 2: Define a Data Model
// app/src/main/java/com/example/myapp/Weather.java
package com.example.myapp;
public class Weather {
private String city;
private double temperature;
private String description;
// Getters and Setters
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public double getTemperature() { return temperature; }
public void setTemperature(double temperature) { this.temperature = temperature; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}
Step 3: Parse JSON
String json = "{\"city\":\"New York\",\"temperature\":25.5,\"description\":\"Sunny\"}";
Gson gson = new Gson();
Weather weather = gson.fromJson(json, Weather.class);
5.3 Using Moshi for JSON Parsing
Step 1: Add Moshi Dependency
implementation "com.squareup.moshi:moshi:1.15.0"
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.15.0"
Step 2: Parse JSON with Moshi
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Weather> jsonAdapter = moshi.adapter(Weather.class);
Weather weather = jsonAdapter.fromJson(json);
5.4 Real-Life Example: Fetching and Displaying Weather Data
Assume you’re fetching weather data from an API (simulated here with a local JSON string).
// Display weather data
TextView weatherView = findViewById(R.id.weatherView);
weatherView.setText(String.format("City: %s\nTemp: %.1f°C\nDesc: %s",
weather.getCity(), weather.getTemperature(), weather.getDescription()));
5.5 Best Practices and Exception Handling
Handle JSON Errors:
try {
Weather weather = gson.fromJson(json, Weather.class);
} catch (JsonSyntaxException e) {
Log.e("JsonError", "Parsing failed: " + e.getMessage());
}
Use Type Adapters for Complex JSON: For nested objects, define custom adapters in Gson or Moshi.
Validate JSON: Ensure the JSON structure matches the data model.
5.6 Pros, Cons, and Alternatives
Pros:
Gson: Simple and widely used.
Moshi: Lightweight and supports Kotlin natively.
Cons:
Gson: Slower for large datasets.
Moshi: Requires additional setup for Kotlin.
Alternatives:
Jackson: Another JSON parsing library.
Manual Parsing: Using JSONObject and JSONArray (not recommended).
6. Practical Exercise: Building a Note-Taking App
6.1 Project Overview
Let’s build a note-taking app that uses Room for persistent storage, SharedPreferences for settings, and JSON for exporting notes. The app allows users to create, read, update, and delete notes, with a setting to toggle the theme.
6.2 Step-by-Step Implementation
Step 1: Set Up the Project
Create a new Android Studio project with an Empty Activity. Add Room and Gson dependencies as shown earlier.
Step 2: Create the Database
Use the Note entity, NoteDao, and AppDatabase classes from Section 3.
Step 3: Create the UI
Main Layout (activity_main.xml):
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notesRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
android:id="@+id/addNoteButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Note"/>
<Button
android:id="@+id/toggleThemeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toggle Theme"/>
Note Item Layout (item_note.xml):
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/noteTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"/>
<TextView
android:id="@+id/noteContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Step 4: Implement the RecyclerView Adapter
// app/src/main/java/com/example/myapp/NoteAdapter.java
package com.example.myapp;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteViewHolder> {
private List<Note> notes;
public NoteAdapter(List<Note> notes) {
this.notes = notes;
}
@Override
public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_note, parent, false);
return new NoteViewHolder(view);
}
@Override
public void onBindViewHolder(NoteViewHolder holder, int position) {
Note note = notes.get(position);
holder.title.setText(note.getTitle());
holder.content.setText(note.getContent());
}
@Override
public int getItemCount() {
return notes.size();
}
public void updateNotes(List<Note> newNotes) {
this.notes = newNotes;
notifyDataSetChanged();
}
static class NoteViewHolder extends RecyclerView.ViewHolder {
TextView title, content;
NoteViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.noteTitle);
content = itemView.findViewById(R.id.noteContent);
}
}
}
Step 5: Implement the Main Activity
// app/src/main/java/com/example/myapp/MainActivity.java
package com.example.myapp;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.Room;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private AppDatabase db;
private NoteAdapter adapter;
private boolean isDarkTheme;
private static final String PREFS_NAME = "MyPrefs";
private static final String KEY_THEME = "theme";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize SharedPreferences
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
isDarkTheme = prefs.getBoolean(KEY_THEME, false);
updateTheme();
// Initialize Room
db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "note_database").build();
// Set up RecyclerView
RecyclerView recyclerView = findViewById(R.id.notesRecyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new NoteAdapter(new ArrayList<>());
recyclerView.setAdapter(adapter);
// Load notes
loadNotes();
// Add note button
Button addNoteButton = findViewById(R.id.addNoteButton);
addNoteButton.setOnClickListener(v -> {
Note note = new Note("New Note", "Content", System.currentTimeMillis());
new Thread(() -> {
db.noteDao().insert(note);
runOnUiThread(this::loadNotes);
}).start();
});
// Toggle theme button
Button toggleThemeButton = findViewById(R.id.toggleThemeButton);
toggleThemeButton.setOnClickListener(v -> {
isDarkTheme = !isDarkTheme;
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(KEY_THEME, isDarkTheme);
editor.apply();
updateTheme();
});
}
private void loadNotes() {
new Thread(() -> {
List<Note> notes = db.noteDao().getAllNotes();
runOnUiThread(() -> adapter.updateNotes(notes));
}).start();
}
private void updateTheme() {
getWindow().getDecorView().setBackgroundColor(isDarkTheme ? 0xFF333333 : 0xFFFFFFFF);
}
}
Step 6: Export Notes to JSON
Add a button to export notes as a JSON file using Gson.
Button exportButton = findViewById(R.id.exportButton);
exportButton.setOnClickListener(v -> {
new Thread(() -> {
List<Note> notes = db.noteDao().getAllNotes();
Gson gson = new Gson();
String json = gson.toJson(notes);
try (FileOutputStream fos = openFileOutput("notes.json", Context.MODE_PRIVATE)) {
fos.write(json.getBytes());
} catch (IOException e) {
Log.e("FileError", "Export failed: " + e.getMessage());
}
}).start();
});
6.3 Testing and Debugging
Test CRUD Operations: Add, update, and delete notes to ensure Room works correctly.
Verify Theme Persistence: Toggle the theme and restart the app.
Check JSON Export: Verify the exported notes.json file contains the correct data.
6.4 Enhancing the App with Advanced Features
Add Note Editing: Create a new activity to edit note details.
Search Functionality: Add a search bar to filter notes.
Cloud Sync: Integrate Firebase for cloud backup.
7. Conclusion and Next Steps
7.1 Recap of Learning Outcomes
You’ve learned how to:
Store and retrieve data using SharedPreferences and Room.
Parse and display JSON data with Gson.
Implement file storage for exporting data.
Build a note-taking app with CRUD operations.
No comments:
Post a Comment
Thanks for your valuable comment...........
Md. Mominul Islam