Bỏ qua để đến nội dung

SharedPreferences

Khi bạn xây dựng ứng dụng, thường có nhu cầu lưu trữ một số thông tin đơn giản như:

  • User đã đăng nhập chưa?
  • Theme sáng hay tối?
  • Ngôn ngữ ưa thích?
  • Lần cuối user mở app là khi nào?

SharedPreferences giải quyết nhu cầu này bằng cách lưu trữ dữ liệu dạng key-value (chìa khóa - giá trị) vào một file XML trong bộ nhớ của app.

  • Persistent: Dữ liệu vẫn còn sau khi tắt app
  • Đơn giản: Chỉ lưu được các kiểu dữ liệu cơ bản
  • Private: Chỉ app của bạn có thể đọc được

Lưu ý: Google khuyến nghị dùng DataStore thay cho SharedPreferences trong projects mới vì nó an toàn hơn với async operations.


Trước khi đọc/ghi dữ liệu, bạn cần lấy instance của SharedPreferences:

// Cách 1: Tạo file preferences riêng với tên tùy chọn
val prefs = getSharedPreferences("my_app_settings", Context.MODE_PRIVATE)

Giải thích:

  • "my_app_settings": Tên file để lưu preferences (bạn đặt tùy ý)
  • Context.MODE_PRIVATE: Chỉ app này có thể đọc file này
// Cách 2: Dùng default preferences của app
val defaultPrefs = PreferenceManager.getDefaultSharedPreferences(context)

SharedPreferences hỗ trợ các kiểu dữ liệu sau:

  • String - Chuỗi văn bản
  • Int - Số nguyên
  • Long - Số nguyên lớn
  • Float - Số thập phân
  • Boolean - True/False
  • Set<String> - Tập hợp các chuỗi
val prefs = getSharedPreferences("user_data", Context.MODE_PRIVATE)
// Cách viết ngắn gọn với Kotlin extension
prefs.edit {
putString("username", "alice") // Lưu tên user
putInt("age", 25) // Lưu tuổi
putBoolean("is_logged_in", true) // Lưu trạng thái đăng nhập
putFloat("score", 98.5f) // Lưu điểm số
putLong("last_login", System.currentTimeMillis()) // Lưu thời gian
putStringSet("favorite_topics", setOf("kotlin", "android", "compose"))
}

Giải thích từng dòng:

  1. prefs.edit { } - Mở “phiên chỉnh sửa” preferences
  2. putString("username", "alice") - Lưu giá trị “alice” với key là “username”
  3. Khi block code kết thúc, dữ liệu tự động được save

Khi đọc, bạn cần cung cấp giá trị mặc định phòng trường hợp key chưa tồn tại:

val prefs = getSharedPreferences("user_data", Context.MODE_PRIVATE)
// Đọc tên user, nếu chưa có thì trả về chuỗi rỗng ""
val username = prefs.getString("username", "") ?: ""
// Đọc tuổi, nếu chưa có thì trả về 0
val age = prefs.getInt("age", 0)
// Đọc trạng thái đăng nhập, mặc định là false (chưa đăng nhập)
val isLoggedIn = prefs.getBoolean("is_logged_in", false)
// Đọc điểm, mặc định là 0
val score = prefs.getFloat("score", 0f)
// Đọc thời gian đăng nhập cuối
val lastLogin = prefs.getLong("last_login", 0L)
// Đọc danh sách topics yêu thích
val topics = prefs.getStringSet("favorite_topics", emptySet()) ?: emptySet()

Tại sao cần giá trị mặc định?

  • Lần đầu mở app, chưa có dữ liệu nào được lưu
  • Nếu không có giá trị mặc định, app sẽ crash

prefs.edit {
remove("username") // Xóa một key cụ thể
}
prefs.edit {
clear() // Xóa TẤT CẢ dữ liệu trong file preferences này
}

Khi nào cần xóa?

  • User đăng xuất → xóa thông tin đăng nhập
  • Reset settings → xóa tất cả preferences

Có 2 cách để lưu thay đổi:

apply() - Lưu bất đồng bộ (Khuyến nghị)

Phần tiêu đề “apply() - Lưu bất đồng bộ (Khuyến nghị)”
prefs.edit {
putString("key", "value")
} // apply() được gọi tự động
  • Không block UI thread
  • Nhanh hơn
  • Không biết kết quả (thành công hay thất bại)
val success = prefs.edit {
putString("key", "value")
}.commit()
if (success) {
Log.d("Prefs", "Lưu thành công!")
} else {
Log.e("Prefs", "Lưu thất bại!")
}
  • Block thread cho đến khi lưu xong
  • Trả về true nếu thành công
  • Dùng khi cần chắc chắn dữ liệu đã được lưu

Best Practice: Hầu hết các trường hợp, dùng apply() là đủ.


7. Wrapper Class - Cách tổ chức code tốt hơn

Phần tiêu đề “7. Wrapper Class - Cách tổ chức code tốt hơn”

Thay vì gọi prefs.getString(...) ở nhiều nơi, hãy tạo một class wrapper:

class UserPreferences(context: Context) {
// Tạo preferences một lần
private val prefs = context.getSharedPreferences(
"user_prefs",
Context.MODE_PRIVATE
)
// Property cho username - đọc/ghi dễ dàng
var username: String
get() = prefs.getString("username", "") ?: ""
set(value) = prefs.edit { putString("username", value) }
// Property cho trạng thái đăng nhập
var isLoggedIn: Boolean
get() = prefs.getBoolean("is_logged_in", false)
set(value) = prefs.edit { putBoolean("is_logged_in", value) }
// Property cho dark mode
var isDarkMode: Boolean
get() = prefs.getBoolean("dark_mode", false)
set(value) = prefs.edit { putBoolean("dark_mode", value) }
// Function để xóa tất cả khi logout
fun clearAll() = prefs.edit { clear() }
}

Sử dụng wrapper class:

// Ở đâu đó trong app
val userPrefs = UserPreferences(context)
// Đọc
if (userPrefs.isLoggedIn) {
showWelcome("Xin chào ${userPrefs.username}!")
}
// Ghi
userPrefs.username = "Alice"
userPrefs.isLoggedIn = true
// Logout
userPrefs.clearAll()

Lợi ích của wrapper class:

  • Code gọn gàng hơn
  • Key names được tập trung một chỗ
  • Intellisense hỗ trợ khi code
  • Dễ test

@Composable
fun SettingsScreen() {
val context = LocalContext.current
// Tạo preferences instance (chỉ tạo một lần với remember)
val prefs = remember {
context.getSharedPreferences("settings", Context.MODE_PRIVATE)
}
// State cho dark mode, khởi tạo từ preferences
var darkMode by remember {
mutableStateOf(prefs.getBoolean("dark_mode", false))
}
// UI
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Chế độ tối")
Switch(
checked = darkMode,
onCheckedChange = { isChecked ->
// Cập nhật state
darkMode = isChecked
// Lưu vào preferences
prefs.edit { putBoolean("dark_mode", isChecked) }
}
)
}
}

Giải thích flow:

  1. Khi mở màn hình → đọc giá trị từ prefs → hiển thị Switch
  2. User toggle Switch → cập nhật state → lưu vào prefs
  3. Lần sau mở app → đọc lại từ prefs → giữ nguyên trạng thái

9. Encrypted SharedPreferences - Lưu dữ liệu nhạy cảm

Phần tiêu đề “9. Encrypted SharedPreferences - Lưu dữ liệu nhạy cảm”

Với dữ liệu nhạy cảm như token, password… cần mã hóa:

// Thêm dependency
// implementation "androidx.security:security-crypto:1.1.0-alpha06"
// Tạo master key để mã hóa
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
// Tạo encrypted preferences
val securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs", // Tên file
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Sử dụng giống hệt SharedPreferences thông thường
securePrefs.edit {
putString("auth_token", "eyJhbGciOiJIUzI1NiIs...")
}
val token = securePrefs.getString("auth_token", null)

Use Case Phù hợp Giải thích
Settings (theme, language) ✅ Có Dữ liệu đơn giản, ít thay đổi
User preferences ✅ Có On/off features
Auth tokens ✅ Có Dùng EncryptedSharedPreferences
Trạng thái nhỏ ✅ Có isFirstLaunch, lastSyncTime
Danh sách items ❌ Không Dùng Room Database
JSON objects ❌ Không Dùng DataStore hoặc Room
Data thay đổi thường xuyên ❌ Không Dùng DataStore (có Flow support)

  1. SharedPreferences lưu dữ liệu dạng key-value đơn giản
  2. Lưu: prefs.edit { putString("key", "value") }
  3. Đọc: prefs.getString("key", defaultValue)
  4. Luôn cung cấp giá trị mặc định khi đọc
  5. Dùng apply() (khuyến nghị) hoặc commit()
  6. Tạo Wrapper class để code gọn gàng hơn
  7. Dùng EncryptedSharedPreferences cho dữ liệu nhạy cảm
  8. Cân nhắc dùng DataStore cho projects mới