SharedPreferences trong Android
1. SharedPreferences là gì?
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.
Đặc điểm quan trọng:
- 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.
2. Lấy SharedPreferences
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)3. Lưu dữ liệu
SharedPreferences hỗ trợ các kiểu dữ liệu sau:
String- Chuỗi văn bảnInt- Số nguyênLong- Số nguyên lớnFloat- Số thập phânBoolean- True/FalseSet<String>- Tập hợp các chuỗi
Ví dụ lưu thông tin user:
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:
prefs.edit { }- Mở “phiên chỉnh sửa” preferencesputString("username", "alice")- Lưu giá trị “alice” với key là “username”- Khi block code kết thúc, dữ liệu tự động được save
4. Đọc dữ liệu
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
5. Xóa dữ liệu
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
6. apply() vs commit() - Cách lưu dữ liệu
Có 2 cách để lưu thay đổi:
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)
commit() - Lưu đồng bộ
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ề
truenế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
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
8. Sử dụng với Jetpack Compose
@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:
- Khi mở màn hình → đọc giá trị từ prefs → hiển thị Switch
- User toggle Switch → cập nhật state → lưu vào prefs
- 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
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)10. Khi nào dùng SharedPreferences?
| 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) |
📝 Tóm tắt cho người mới
- SharedPreferences lưu dữ liệu dạng key-value đơn giản
- Lưu:
prefs.edit { putString("key", "value") } - Đọc:
prefs.getString("key", defaultValue) - Luôn cung cấp giá trị mặc định khi đọc
- Dùng apply() (khuyến nghị) hoặc commit()
- Tạo Wrapper class để code gọn gàng hơn
- Dùng EncryptedSharedPreferences cho dữ liệu nhạy cảm
- Cân nhắc dùng DataStore cho projects mới