Tìm hiểu và sử dụng DataStore trong gói Android Jetpack (Phần 2)
Bài đăng này đã không được cập nhật trong 4 năm
Giới thiệu
Ở phần 1 chúng ta đã cùng tìm hiểu và thực hiện một ví dụ về sử dụng Preferences DataStore để đọc và ghi một chuỗi UUID. Tiếp theo phần này chúng ta sẽ tìm hiểu về cách sử dụng Proto DataStore để đọc và ghi một chuỗi Token. Việc ghi dữ liệu từ Proto DataStore cần nhiều bước hơn và yêu cầu phải tự định nghĩa trước một file schema có định dạng .proto theo ngôn ngữ protocol buffer. Nếu ai chưa quen thuộc về ngôn ngữ này có thể tìm hiểu qua link tại đây https://developers.google.com/protocol-buffers/docs/overview
Sử dụng Proto DataStore để lưu trữ dữ liệu
Định nghĩa file Schema sử dụng cho việc lưu trữ thông tin
Như đã giới thiệu ở phần trước Proto DataStore là cách lưu trữ đảm bảo thông tin của loại dữ liệu luôn đúng khi có thao tác đọc và ghi. Việc đảm bảo này được thực hiện qua việc định nghĩa trước một file schema và chương trình khi compile sẽ tiến hành đọc thông tin từ file schema đã định nghĩa trước đó và tạo ra các hàm đọc và ghi theo loại dữ liệu đã quy định nhờ đó nó giúp dữ liệu được thao tác chính xác.
File proto này được quy định lưu trữ 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 như trên. Tiếp theo ta tiến hành tạo một file theo cấu trúc của ngôn ngữ protobuf. Ở đây ví dụ đặt tên là một file MyProtoDataStore.proto có thông tin như sau:
syntax = "proto3";
option java_package = "com.example.datastore";
option java_multiple_files = true;
message TestModel {
string token = 1;
}
Ý nghĩa của file này là để tạo ra một class có tên là TestModel với package định nghĩa là "com.example.datastore". Trong class này có phần tử đầu tiên là một biến có tên là token với kiểu dữ liệu là string, số 1 tượng trưng cho vị trí đầu tiên chứ không phải là giá trị default của biến token. Ta có thể nhìn thấy file compile khi chạy chương trình sẽ khởi tạo ra có dạng như bên dưới.
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: MyProtoDataStore.proto
package com.example.datastore;
/**
* Protobuf type {@code TestModel}
*/
public final class TestModel extends
com.google.protobuf.GeneratedMessageLite<
TestModel, TestModel.Builder> implements
// @@protoc_insertion_point(message_implements:TestModel)
TestModelOrBuilder {
private TestModel() {
token_ = "";
}
public static final int TOKEN_FIELD_NUMBER = 1;
private java.lang.String token_;
/**
* <code>string token = 1;</code>
* @return The token.
*/
@java.lang.Override
public java.lang.String getToken() {
return token_;
}
/**
* <code>string token = 1;</code>
* @param value The token to set.
*/
private void setToken(
java.lang.String value) {
value.getClass();
token_ = value;
}
/**
* <code>string token = 1;</code>
*/
private void clearToken() {
token_ = getDefaultInstance().getToken();
}
...
}
Cài đặt thư viện
Để sử dụng Proto DataStore chúng ta cần thêm cách thư viện sau vào file Gradle của dự án:
- Đầu tiên là plugins của thư viện protobuf
plugins {
...
id "com.google.protobuf" version "0.8.12"
}
- Kế đến là các thư viện để sử dụng proto datastore trong dự án
dependencies {
...
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0-alpha01"
implementation "com.google.protobuf:protobuf-javalite:3.11.0"
// coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.7"
}
- Cuối cùng là thông tin đặc tả để giúp chuyển đổi file schema đã được nghĩa trước đó trong app/src/main/proto thành file java tương ứng để có thể sử dụng và gọi từ trong mã nguồn của chương trình.
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.11.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
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. Nội dung có dạng như class MyProtoDataStoreSerializer bên dưới:
object MyProtoDataStoreSerializer : Serializer<TestModel> {
override fun readFrom(input: InputStream): TestModel {
try {
return TestModel.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override fun writeTo(t: TestModel, output: OutputStream) = t.writeTo(output)
}
- Kế tiếp, sử dụng phương thức Context.createDataStore() để tạo một đối tượng DataStore<T>, nơi mà T là loại dữ liệu đã định nghĩa từ proto file. Hàm này sẽ có 2 tham số: fileName và serializer. Với fileName là tên file được sử dụng cho việc lưu trữ data ở đây là MyProtoDataStore.proto, còn serializer là tham số báo cho DataStore biết tên của lớp serializer đã được định nghĩa trước đó, ở đó là lớp MyProtoDataStoreSerializer. Code cụ thể như bên dưới:
//create proto data store
lateinit var protoDataStore: DataStore<TestModel>
protoDataStore = this.createDataStore(
fileName = "MyProtoDataStore.proto",
serializer = MyProtoDataStoreSerializer
)
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. Trong ví dụ này ta muốn lưu một chuỗi token, nến việc ghi dữ liệu sẽ được thực hiện như sau:
suspend fun saveToken(token: String) {
protoDataStore.updateData {
it.toBuilder().setToken(token).build()
}
}
Lưu ý cũng như Preferences DataStore, Proto DataStore cũng thực hiện lưu trữ kiểu 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.
Đọc dữ liệu đã ghi từ Proto DataStore
Sử dụng 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. Ví dụ ở đây là chuỗi token thì dữ liệu phát ra sẽ có dạng Flow<String>. Hàm đọc dữ liệu sẽ có dạng như sau:
lateinit var token: Flow<String>
fun readToken() {
token = protoDataStore.data.map {
it.token
}
}
Tương tự như phần 1, ta cũng phải sử dụng phương thức collect để lắng nghe các dữ liệu phát ra từ Flow<String> này.
CoroutineScope(Dispatchers.Main).launch {
token.collect { value ->
if (value.isEmpty()) {
val newToken = "Your token ABCDEF"
showToast("Not have token saved. Create new token ${newToken} and save to Proto DataStore")
saveToken(newToken)
} else {
showToast("Read the token from proto data store ${value}")
}
}
}
Kết quả khi chạy chương trình:
Đến đây ta đã hoàn thành việc đọc và ghi một giá trị sử dụng phương thức lưu trữ Proto DataStore.
Mã nguồn của dự án có thể tham khảo tại đây: https://github.com/hungan1409/ExampleDataStore.git
Tham khảo
https://developer.android.com/topic/libraries/architecture/datastore
All rights reserved