Giới thiệu về DataStore trong gói Android Jetpack
Mục lục
I. Giới thiệu
Trong bài viết này, ta sẽ đi tìm hiểu về DataStore, một trong những giải pháp lưu trữ dữ liệu mới trong những giải pháp lưu trữ dữ liệu trong gói Android Jetpack và sẽ dần thay thế kiểu dữ liệu SharePreferences đang có.
DataStore là một giải pháp lưu trữ dữ liệu cho phép lưu trữ các cặp khóa-giá trị hoặc đối tượng đã nhập có vùng đệm giao thức. DataStore sử dụng coroutine và Flow để lưu dữ liệu một cách không đồng bộ, nhất quán và có thể chia sẻ.
Trong DataStore có các phương thức triển khai sau:
- Preference DataStore lưu trữ và truy cập vào dữ liệu bằng các khoá. Phương thức triển khai này không yêu cầu có giản đồ được xác định trước và không đảm bảo an toàn về kiểu.
- Proto DataStore lưu trữ dữ liệu theo một kiểu dữ liệu tuỳ chỉnh. Phương thức triển khai này yêu cầu bạn xác định một giản đồ bằng cách sử dụng vùng đệm giao thức, nhưng có đảm bảo an toàn về kiểu.
Để sử dụng được Preference DataStore và Proto DataStore thì cần đảm bảo ứng dụng chạy từ api 29 trở lên.
* Lưu ý: DataStore là giải pháp lý tưởng cho các tập dữ liệu nhỏ và đơn giản, không hỗ trợ việc cập nhật một phần hoặc tính toàn vẹn tham chiếu.
II.So sánh SharePreferences và DataStore
III. Cách sử dụng DataStore?
1. Kho dữ liệu tùy chọn
Cài đặt thư viện
Để sử dụng Preferences DataStore chúng ta thêm thư viện sau vào file Build.Gradle của dự án:
Triển khai “androidx.datastore:datastore-preferences:1.0.0”
Tạo đối tượng Preferences DataStore
Để tạo một đối tượng Preferences DataStore thì ta hãy sử dụng tính năng ủy quyền thuộc tính do PreferencesDataStore để tạo Datastore<Preferences>. Gọi tệp này ở cấp cao nhất để có thể truy cập được tệp này trong suốt phần còn lại của ứng dụng:
Ghi dữ liệu vào Preferences DataStore
DataStore Preference cung cấp một hàm edit() có thể chia sẻ cập nhật dữ liệu trong một DataStore. Cũng lưu ý DataStore sử dụng cơ chế lưu trữ bất đồng bộ kết hợp với Coroutines nên khi khai báo một hàm để lưu trữ dữ liệu sẽ có thêm suspend ở đầu hàm:
Trong đó key_value là khóa key đang dùng với kiểu dữ liệu String, bạn có thể thay đổi kiểu dữ liệu khác như Int, Boolean…
Đọc dữ liệu trong Preferences DataStore
Preferences DataStore cung cấp phương thức DataStore.data để lấy dữ liệu ra và dữ liệu được lưu trữ dưới dạng một Flow:
Đến đây myPreference mới là kiểu Flow<String>, để có thể sử dụng dữ liệu này ta cần phải sử dụng phương thức collect để lắng nghe các dữ liệu phát ra từ Flow<String>:
Vì myPreference.collect là một hàm suspend nên cần được chạy từ một Coroutine Scope và lắng nghe dữ liệu thay đổi, mỗi khi dữ liệu có sự thay đổi nó sẽ tự động lắng nghe và cập nhật giá trị như kiểu LiveData.
2. Proto DataStore
Tạo tệp Proto
Tạo một file proto được quy định lưu tại thư mục app/src/main/proto, nếu dự án của bạn chưa có thì hãy tạo theo đúng đường dẫn trên. Ta sẽ tiến hành tạo một file với cấu trúc của ngôn ngữ protobuf:
Cài đặt thư viện
Để sử dụng Proto DataStore chúng ta thêm thư viện sau vào file Build.Gradle của dự án:
Tạo đối tượng Proto DataStore
Có 2 việc cần thực hiện để tạo 1 đối tượng Proto Datastore dùng cho việc lưu trữ loại đối tượng mà ta đã định nghĩa từ file .proto trước đó:
- Định nghĩa 1 class cái mà sẽ implement Serializer<T>, nơi mà T là loại dữ liệu chúng ta đã định nghĩa. Lớp Serializer này sẽ báo cho DataStore cách thức để đọc và ghi dữ liệu của chúng ta đã định nghĩa.
- Sử dụng tính năng uỷ quyền thuộc tính do dataStore tạo để tạo một bản sao của DataStore<T>, trong đó T là loại được xác định trong tệp proto:
Ghi dữ liệu vào Proto DataStore
Proto DataStore cung cấp phương thức updateData() để thực hiện một hoạt động cập nhật đối tượng đến DataStore:
Đọc dữ liệu trong Proto DataStore
Cũng giống như Preferences Datastore sử dụng DataStore.data để lấy dữ liệu ra và dữ liệu được lưu trữ dưới dạng một Flow.
Sử dụng DataStore trong mã đồng bộ
Để giúp thu hẹp khoảng cách giữa mã đồng bộ và không đồng bộ. Bạn có thể sử dụng runBlocking() để đọc dữ liệu trong DataStore một cách đồng bộ. Mã sau đây chặn luồng gọi cho đến khi DataStore trả về dữ liệu:
val users = runBlocking { usersDataStore.data.first() }
Việc thực hiện các thao tác I/O đồng bộ trên luồng giao diện người dùng có thể khiến ANR hoặc giao diện người dùng bị giật. Bạn có thể giảm thiểu những vấn đề này bằng cách tải trước không đồng bộ dữ liệu trong DataStore:
lifecycleScope.launch {
val user = usersDataStore.data.first()
}
Bằng cách này, DataStore đọc không đồng bộ dữ liệu và lưu dữ liệu vào bộ nhớ đệm trong bộ nhớ. Các lượt đọc đồng bộ sau này sử dụng runBlocking() có thể nhanh hơn hoặc có thể tránh được toàn bộ thao tác của ổ đĩa I/O nếu lần đọc ban đầu hoàn tất.