SharedPreferences
1. SharedPreferences là gì?
Phần tiêu đề “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:
Phần tiêu đề “Đặ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
Phần tiêu đề “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ọnval 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 appval defaultPrefs = PreferenceManager.getDefaultSharedPreferences(context)3. Lưu dữ liệu
Phần tiêu đề “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:
Phần tiêu đề “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 extensionprefs.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
Phần tiêu đề “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ề 0val 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à 0val score = prefs.getFloat("score", 0f)
// Đọc thời gian đăng nhập cuốival lastLogin = prefs.getLong("last_login", 0L)
// Đọc danh sách topics yêu thíchval 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
Phần tiêu đề “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
Phần tiêu đề “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ị)
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)
commit() - Lưu đồng bộ
Phần tiêu đề “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
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 appval userPrefs = UserPreferences(context)
// Đọcif (userPrefs.isLoggedIn) { showWelcome("Xin chào ${userPrefs.username}!")}
// GhiuserPrefs.username = "Alice"userPrefs.isLoggedIn = true
// LogoutuserPrefs.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
Phần tiêu đề “8. Sử dụng với Jetpack Compose”@Composablefun 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
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óaval masterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build()
// Tạo encrypted preferencesval 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ườngsecurePrefs.edit { putString("auth_token", "eyJhbGciOiJIUzI1NiIs...")}
val token = securePrefs.getString("auth_token", null)10. Khi nào dùng SharedPreferences?
Phần tiêu đề “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
Phần tiêu đề “📝 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