← Back to Tutorials Chapter 11

Storage & Data Persistence

SharedPreferences

// Save data
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().apply {
    putString("username", "Alice")
    putInt("age", 30)
    putBoolean("isLoggedIn", true)
    apply() // Async
}

// Read data
val username = prefs.getString("username", "")
val isLoggedIn = prefs.getBoolean("isLoggedIn", false)

// DataStore (modern replacement)
val dataStore = context.createDataStore(name = "settings")
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = dataStore.data.map { it[EXAMPLE_COUNTER] ?: 0 }

suspend fun incrementCounter() {
    dataStore.edit { it[EXAMPLE_COUNTER] = (it[EXAMPLE_COUNTER] ?: 0) + 1 }
}

SQLite with Room

Room is the recommended abstraction layer over SQLite.

// Entity
@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val title: String,
    val content: String,
    val timestamp: Long = System.currentTimeMillis()
)

// DAO
@Dao
interface NoteDao {
    @Query("SELECT * FROM notes ORDER BY timestamp DESC")
    fun getAllNotes(): LiveData<List<Note>>

    @Insert
    suspend fun insert(note: Note)

    @Update
    suspend fun update(note: Note)

    @Delete
    suspend fun delete(note: Note)

    @Query("SELECT * FROM notes WHERE id = :id")
    suspend fun getNoteById(id: Int): Note?
}

// Database
@Database(entities = [Note::class], version = 1)
abstract class NoteDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao

    companion object {
        @Volatile
        private var INSTANCE: NoteDatabase? = null

        fun getInstance(context: Context): NoteDatabase {
            return INSTANCE ?: synchronized(this) {
                Room.databaseBuilder(
                    context.applicationContext,
                    NoteDatabase::class.java,
                    "note_database"
                ).build().also { INSTANCE = it }
            }
        }
    }
}

// Usage
val dao = NoteDatabase.getInstance(this).noteDao()
viewModelScope.launch {
    dao.insert(Note(title = "My Note", content = "Hello Room!"))
}

File Storage

// Internal storage (private to the app)
fun writeToInternalFile(filename: String, content: String) {
    openFileOutput(filename, Context.MODE_PRIVATE).use {
        it.write(content.toByteArray())
    }
}

fun readFromInternalFile(filename: String): String {
    return openFileInput(filename).bufferedReader().readText()
}

// External storage (requires permission for public files)
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

fun saveToDownloads(content: String, filename: String) {
    val downloadsDir = Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_DOWNLOADS
    )
    val file = File(downloadsDir, filename)
    file.writeText(content)
}

// Scoped storage (Android 10+)
fun saveToAppSpecificExternal() {
    val file = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "data.txt")
    file.writeText("Hello, scoped storage!")
}

JSON & XML Parsing

// JSON with Gson/Moshi
// implementation("com.squareup.moshi:moshi-kotlin:1.15.0")

@JsonClass(generateAdapter = true)
data class User(val id: Int, val name: String, val email: String)

val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(User::class.java)

// Serialize
val user = User(1, "Alice", "alice@example.com")
val json = adapter.toJson(user)

// Deserialize
val userFromJson = adapter.fromJson(json)

// XML Pull Parser
fun parseXml(xml: String) {
    val parser = XmlPullParserFactory.newInstance().newPullParser()
    parser.setInput(StringReader(xml))
    var eventType = parser.eventType
    while (eventType != XmlPullParser.END_DOCUMENT) {
        if (eventType == XmlPullParser.START_TAG && parser.name == "item") {
            val name = parser.getAttributeValue(null, "name")
            println("Item: $name")
        }
        eventType = parser.next()
    }
}
Exercise: Create a notes app using Room database. Users can add, edit, and delete notes. Each note has a title, content, and timestamp. Display notes in a RecyclerView sorted by most recent. Show a dialog to confirm before deleting. Persist the last opened note ID using DataStore.