1. Trước khi bắt đầu
Giới thiệu
Trong phần này, bạn đã học cách sử dụng SQL và Room để lưu dữ liệu trên thiết bị. SQL và Room đều là những công cụ mạnh mẽ. Tuy nhiên, trong trường hợp bạn không cần lưu trữ dữ liệu quan hệ, DataStore có thể cung cấp một giải pháp đơn giản. Thành phần DataStore Jetpack là một cách tuyệt vời để lưu trữ các tập dữ liệu nhỏ và đơn giản với chi phí thấp. Có hai phương thức triển khai DataStore là Preferences DataStore và Proto DataStore.
Preferences DataStorelưu trữ các cặp khoá-giá trị. Giá trị có thể là các loại dữ liệu cơ bản của Kotlin, chẳng hạn nhưString,BooleanvàInteger. Phương thức này không thể lưu trữ các tập dữ liệu phức tạp. Phương thức này cũng không yêu cầu giản đồ xác định trước. Thường thìPreferences Datastoredùng để lưu trữ lựa chọn ưu tiên của người dùng trên thiết bị của họ.Proto DataStorelưu trữ các loại dữ liệu tuỳ chỉnh. Phương thức này yêu cầu một giản đồ được xác định trước để ánh xạ các định nghĩa proto với cấu trúc đối tượng.
Lớp học lập trình này chỉ đề cập tới Preferences DataStore, nhưng bạn có thể đọc thêm về Proto DataStore trong tài liệu về DataStore.
Preferences DataStore là một cách tuyệt vời để lưu trữ các chế độ cài đặt do người dùng kiểm soát. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách triển khai DataStore để thực hiện chính xác điều đó!
Điều kiện tiên quyết:
- Hoàn thành bài tập Khái niệm cơ bản về Android với Compose thông qua lớp học lập trình Đọc và cập nhật dữ liệu bằng Room.
Bạn cần có
- Máy tính có kết nối Internet và Android Studio.
- Một thiết bị hoặc trình mô phỏng
- Mã khởi đầu cho ứng dụng Dessert Release
Sản phẩm bạn sẽ tạo ra
Ứng dụng Dessert Release cho thấy một danh sách bản phát hành Android. Biểu tượng trên thanh ứng dụng dùng để chuyển đổi bố cục giữa chế độ xem lưới và chế độ xem danh sách.

Ứng dụng sẽ không lưu bố cục được chọn lúc này. Khi bạn đóng ứng dụng, bố cục bạn chọn sẽ không được lưu và chế độ cài đặt dành cho bố cục sẽ quay về lựa chọn mặc định. Trong lớp học lập trình này, bạn sẽ thêm DataStore vào ứng dụng Dessert Release và dùng giải pháp lưu trữ dữ liệu này để lưu trữ các lựa chọn ưu tiên cho bố cục.
2. Tải mã khởi đầu xuống
Nhấp vào đường liên kết sau đây để tải toàn bộ mã cho lớp học lập trình này:
Hoặc nếu muốn, bạn có thể sao chép mã Dessert Release trên GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-release.git $ cd basic-android-kotlin-compose-training-dessert-release $ git checkout starter
- Trong Android Studio, hãy mở thư mục
basic-android-kotlin-compose-training-dessert-release. - Mở mã ứng dụng Dessert Release trong Android Studio.
3. Thiết lập phần phụ thuộc
Thêm các dòng sau vào dependencies trong tệp app/build.gradle.kts:
implementation("androidx.datastore:datastore-preferences:1.0.0")
4. Triển khai kho lưu trữ các lựa chọn ưu tiên của người dùng
- Trong gói
data, hãy tạo một lớp mới có tên làUserPreferencesRepository.

- Trong hàm khởi tạo
UserPreferencesRepository, hãy xác định một thuộc tính mang giá trị riêng tư để biểu thị một thực thể đối tượngDataStorecó loạiPreferences.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
}
DataStore lưu trữ các cặp khoá-giá trị. Để truy cập vào một giá trị, bạn phải xác định khoá.
- Tạo
companion objectbên trong lớpUserPreferencesRepository. - Dùng hàm
booleanPreferencesKey()để xác định một khoá và đặt tên cho khoá đó làis_linear_layout. Tương tự như tên bảng SQL, khoá cần sử dụng định dạng dấu gạch dưới. Khoá này dùng để truy cập vào một giá trị boolean cho biết có nên hiển thị bố cục tuyến tính hay không.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
}
...
}
Ghi vào DataStore
Bạn tạo và sửa đổi các giá trị trong DataStore bằng cách truyền một hàm lambda vào phương thức edit(). Hàm lambda được truyền một thực thể của MutablePreferences. Bạn có thể sử dụng thực thể này để cập nhật các giá trị trong DataStore. Tất cả nội dung cập nhật bên trong hàm lambda này được thực thi dưới dạng một giao tác duy nhất. Nói cách khác, bản cập nhật này có tính không thể phân chia (atomic) — toàn bộ diễn ra cùng lúc. Loại cập nhật này ngăn chặn trường hợp một số giá trị cập nhật nhưng một số khác thì không.
- Tạo một hàm tạm ngưng và gọi hàm này là
saveLayoutPreference(). - Trong hàm
saveLayoutPreference(), hãy gọi phương thứcedit()trên đối tượngdataStore.
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit {
}
}
- Để mã của bạn dễ đọc hơn, hãy định nghĩa tên cho
MutablePreferencesđược cung cấp trong phần thân hàm lambda. Sử dụng thuộc tính đó để đặt giá trị bằng khoá đã xác định và truyền giá trị boolean vào hàmsaveLayoutPreference().
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit { preferences ->
preferences[IS_LINEAR_LAYOUT] = isLinearLayout
}
}
Lấy dữ liệu từ DataStore
Giờ đây khi bạn đã tạo được cách ghi isLinearLayout vào dataStore, hãy làm theo các bước sau để đọc dữ liệu:
- Tạo một thuộc tính trong
UserPreferencesRepositorythuộc kiểuFlow<Boolean>tên làisLinearLayout.
val isLinearLayout: Flow<Boolean> =
- Bạn có thể dùng thuộc tính
DataStore.datađể hiển thị các giá trịDataStore. ĐặtisLinearLayoutthành thuộc tínhdatacủa đối tượngDataStore.
val isLinearLayout: Flow<Boolean> = dataStore.data
Thuộc tính data là Flow của đối tượng Preferences. Đối tượng Preferences chứa tất cả các cặp khoá-giá trị trong DataStore. Mỗi lần cập nhật dữ liệu trong DataStore, một đối tượng Preferences mới sẽ được phát vào Flow.
- Dùng hàm ánh xạ để chuyển đổi
Flow<Preferences>thànhFlow<Boolean>.
Hàm này chấp nhận tham số lambda với đối tượng Preferences hiện tại làm tham số. Bạn có thể chỉ định khoá mà bạn đã xác định trước đó để có được lựa chọn ưu tiên về bố cục. Xin lưu ý rằng giá trị này có thể không tồn tại nếu saveLayoutPreference chưa được gọi, vì vậy, bạn nên cung cấp một giá trị mặc định.
- Chỉ định
trueđể đặt mặc định là thành phần hiển thị bố cục tuyến tính.
val isLinearLayout: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
Xử lý ngoại lệ
Bất cứ khi nào bạn tương tác với hệ thống tệp trên một thiết bị đều có khả năng xảy ra lỗi. Ví dụ: một tệp có thể không tồn tại hoặc ổ đĩa có thể bị đầy hoặc ngắt kết nối. Khi DataStore đọc và ghi dữ liệu từ các tệp, IOExceptions có thể xảy ra khi truy cập vào DataStore. Hãy sử dụng toán tử catch{} để phát hiện các ngoại lệ và xử lý những lỗi này.
- Trong đối tượng đồng hành, hãy triển khai một thuộc tính chuỗi
TAGkhông thể thay đổi để dùng để ghi nhật ký.
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
const val TAG = "UserPreferencesRepo"
}
Preferences DataStoregửi mộtIOExceptionnếu xảy ra lỗi trong khi đọc dữ liệu. Trong khối khởi độngisLinearLayout, trướcmap(), hãy dùng toán tửcatch{}để phát hiệnIOException.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
- Trong khối catch, nếu có
IOexception, hãy ghi lỗi và phátemptyPreferences(). Nếu một loại ngoại lệ khác được gửi, hãy ưu tiên loại bỏ ngoại lệ đó. Bằng cách phátemptyPreferences()nếu có lỗi, hàm ánh xạ vẫn có thể ánh xạ tới giá trị mặc định.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {
if(it is IOException) {
Log.e(TAG, "Error reading preferences.", it)
emit(emptyPreferences())
} else {
throw it
}
}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
5. Khởi chạy DataStore
Trong lớp học lập trình này, bạn phải xử lý quá trình chèn phần phụ thuộc theo cách thủ công. Do đó, bạn cần cung cấp Preferences DataStore cho lớp UserPreferencesRepository. Làm theo các bước sau để chèn DataStore vào UserPreferencesRepository.
- Tìm gói
dessertrelease. - Trong thư mục này, hãy tạo một lớp mới có tên là
DessertReleaseApplicationvà triển khai lớpApplication. Đây là vùng chứa cho DataStore của bạn.
class DessertReleaseApplication: Application() {
}
- Hãy khai báo
private const valcó tên làLAYOUT_PREFERENCE_NAMEvào bên trong tệpDessertReleaseApplication.kt, nhưng ở bên ngoài lớpDessertReleaseApplication. - Chỉ định giá trị chuỗi
layout_preferencescho biếnLAYOUT_PREFERENCE_NAME. Sau đó, bạn có thể dùng giá trị này làm tên củaPreferences Datastoremà bạn tạo thực thể ở bước tiếp theo.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
- Hãy tạo một thuộc tính mang giá trị riêng tư thuộc kiểu
DataStore<Preferences>có tên làContext.dataStoreở bên ngoài phần thân lớpDessertReleaseApplication, nhưng nằm trong tệpDessertReleaseApplication.ktbằng cách sử dụng lớp uỷ quyềnpreferencesDataStore. TruyềnLAYOUT_PREFERENCE_NAMEcho tham sốnamecủa uỷ quyềnpreferencesDataStore.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
- Bên trong thân lớp
DessertReleaseApplication, tạo một thực thểlateinit varchoUserPreferencesRepository.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
}
- Ghi đè phương thức
onCreate().
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
override fun onCreate() {
super.onCreate()
}
}
- Bên trong phương thức
onCreate(), hãy khởi chạyuserPreferencesRepositorybằng cách tạoUserPreferencesRepositorycódataStorelàm tham số.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
override fun onCreate() {
super.onCreate()
userPreferencesRepository = UserPreferencesRepository(dataStore)
}
}
- Thêm dòng sau vào bên trong thẻ
<application>trong tệpAndroidManifest.xml.
<application
android:name=".DessertReleaseApplication"
...
</application>
Phương pháp này xác định lớp DessertReleaseApplication làm điểm truy cập của ứng dụng. Mục đích của mã này là khởi tạo các phần phụ thuộc được xác định trong lớp DessertReleaseApplication trước khi chạy MainActivity.
6. Sử dụng UserPreferencesRepository
Cung cấp kho lưu trữ cho ViewModel
UserPreferencesRepository hiện có sẵn thông qua tính năng chèn phần phụ thuộc, bạn có thể sử dụng tính năng này trong DessertReleaseViewModel.
- Trong
DessertReleaseViewModel, hãy tạo một thuộc tínhUserPreferencesRepositorylàm tham số hàm khởi tạo.
class DessertReleaseViewModel(
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
...
}
- Trong đối tượng đồng hành của
ViewModel, thuộc khốiviewModelFactory initializer, lấy một thực thể củaDessertReleaseApplicationbằng mã sau.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
...
}
}
}
}
- Tạo một thực thể của
DessertReleaseViewModelvà truyềnuserPreferencesRepository.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
DessertReleaseViewModel(application.userPreferencesRepository)
}
}
}
}
ViewModel nay có thể truy cập UserPreferencesRepository. Bước tiếp theo là sử dụng khả năng đọc và ghi của UserPreferencesRepository mà bạn đã triển khai trước đó.
Lưu trữ tuỳ chọn bố cục
- Chỉnh sửa hàm
selectLayout()trongDessertReleaseViewModelđể truy cập vào kho lưu trữ tuỳ chọn và cập nhật tuỳ chọn bố cục. - Hãy nhớ rằng việc ghi vào
DataStoređược thực hiện không đồng bộ bằng hàmsuspend. Bắt đầu một Coroutine mới để gọi hàmsaveLayoutPreference()của kho lưu trữ tuỳ chọn.
fun selectLayout(isLinearLayout: Boolean) {
viewModelScope.launch {
userPreferencesRepository.saveLayoutPreference(isLinearLayout)
}
}
Đọc tuỳ chọn bố cục
Ở phần này, bạn sẽ tái cấu trúc uiState: StateFlow hiện có trong ViewModel để phản ánh isLinearLayout: Flow từ kho lưu trữ.
- Xoá mã khởi tạo thuộc tính
uiStatethànhMutableStateFlow(DessertReleaseUiState).
val uiState: StateFlow<DessertReleaseUiState> =
Lựa chọn bố cục tuyến tính ưu tiên từ kho lưu trữ có thể có hai giá trị, đúng hoặc sai, dưới dạng Flow<Boolean>. Giá trị này phải ánh xạ đến một trạng thái giao diện người dùng.
- Đặt
StateFlowthành kết quả của phép biến đổi tập hợpmap()được gọi trênisLinearLayout Flow.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
}
- Trả về một phiên bản của lớp dữ liệu
DessertReleaseUiState, truyềnisLinearLayout Boolean. Màn hình sử dụng trạng thái giao diện người dùng này để xác định các chuỗi và biểu tượng phù hợp để hiển thị.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
UserPreferencesRepository.isLinearLayout là một Flow lạnh. Tuy nhiên, để cung cấp trạng thái cho giao diện người dùng, bạn nên sử dụng quy trình nóng, chẳng hạn như StateFlow để trạng thái luôn có sẵn cho giao diện người dùng.
- Dùng hàm
stateIn()để chuyển đổiFlowthànhStateFlow. - Hàm
stateIn()chấp nhận 3 tham số:scope,startedvàinitialValue. Lần lượt truyềnviewModelScope,SharingStarted.WhileSubscribed(5_000)vàDessertReleaseUiState()cho các tham số này.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = DessertReleaseUiState()
)
- Khởi chạy ứng dụng. Lưu ý rằng bạn có thể nhấp vào biểu tượng bật/tắt để chuyển đổi giữa bố cục lưới và bố cục tuyến tính.

Xin chúc mừng! Bạn đã thêm thành công Preferences DataStore vào ứng dụng của mình để lưu lựa chọn ưu tiên của người dùng về bố cục.
7. Lấy mã giải pháp
Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh git sau:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-release.git $ cd basic-android-kotlin-compose-training-dessert-release $ git checkout main
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.
Nếu bạn muốn xem mã giải pháp, hãy xem mã đó trên GitHub.