- vừa được xem lúc

DataStore - mảnh ghép hoàn hảo cho bức tranh Kotlin Coroutines

0 0 9

Người đăng: Nguyễn Nam Anh

Theo Viblo Asia

Concept

Trước hết, chúng ta cần hiểu DataStore sinh ra với mục đích là gì.

Hiện tại, trong ứng dụng Android, chúng ta có 5 cách để lưu trữ dữ liệu, trong đó SharedPreferences là cách dùng để lưu những dữ liệu đơn giản nhất. Nó chỉ gồm keyvalue, trong đó value có thể là integer, string...

DataStore được tạo ra chính là để thay thế SharedPreferencs.

DataStore là giải pháp lưu trữ dữ liệu theo dạng cặp key-value hoặc typed objects với protocol buffers.

Tất nhiên, DataStore vẫn chỉ dành để lưu những dữ liệu có cấu trúc đơn giản. Nó sử dụng Coroutines và Flow để lưu data một cách bất đồng bộ và nhất quán.

DataStore gồm 2 loại Preferences DataStoreProto DataStore, chúng ta cùng nhìn qua bảng so sánh sau:

Preferences DataStore Proto DataStore
Lưu và truy cập data bằng key Lưu instance của một loại custom data
Không yêu cầu định nghĩa trước loại data Phải định nghĩa trước loại data bằng protocol buffers
Không có type safety Có type safety

Preferences DataStore

Create

Để sử dụng Preferences DataStore, chúng ta cần tạo một instance DataStore<Preferences> bằng property delegate với keyword preferencesDataStore.

// At the top level of your kotlin file
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

Read

Trước hết, chúng ta có 7 function tương ứng với 7 loại data:

  • intPreferencesKey()
  • longPreferencesKey()
  • doublePreferencesKey()
  • floatPreferencesKey()
  • booleanPreferencesKey()
  • stringPreferencesKey()
  • stringSetPreferencesKey()

Khi đọc data, chúng ta cần dùng function tương ứng với giá trị mà chúng ta cần lưu. Ví dụ để lưu một biến counter dạng số nguyên để đếm số lần user mở app, chúng ta có thể dùng cách sau:

val OPEN_APP_COUNTER = intPreferencesKey("open_app_counter")
val openAppCounterFlow: Flow<Int> = context.dataStore.data .map { preferences -> // No type safety. preferences[OPEN_APP_COUNTER] ?: 0 }

Điểm khác biệt với SharedPreferences chính là ở đây, data được trả về dưới dạng Flow. Giờ đây, các layer phía trên như Repository có thể observe data một cách thống nhất, không cần quan tâm nó đến từ DataStore, Room database hay Server, bởi vì tất cả đều được return dưới dạng Flow.

Write

Để ghi dữ liệu, chúng ta dùng function edit, cũng khá giống với SharedPreferences.

context.dataStore.edit { settings -> val openAppCounterValue = settings[OPEN_APP_COUNTER] ?: 0 settings[OPEN_APP_COUNTER] = openAppCounterValue + 1
}

Proto DataStore

Trước khi tìm hiểu về Proto DataStore, chúng ta cần dạo qua một vòng về protocol buffers.

Protocol buffers

Đây là một một kiểu định dạng dữ liệu mà không phụ thuộc vào ngôn ngữ lập trình hay platform. Nó giống như JSON nhưng nhỏ và nhanh hơn nhiều lần. Protocol buffers cũng được giới thiệu là định dạng dữ liệu được sử dụng phổ biến nhất tại Google.

  • Nó dùng để lưu các dữ liệu nhỏ gọn
  • Phân tích cú pháp nhanh
  • Hỗ trợ nhiều ngôn ngữ lập trình như C++, C#, Dart, Go, Java, Kotlin, Python
  • Tối ưu hoá chức năng thông qua các class được generate tự động

Ví dụ một message về thông tin user gồm tên, id và email:

message UserProfile { optional string name = 1; optional int32 id = 2; optional string email = 3;
}

Để so sánh về hiệu năng so của Protocol buffers so với JSON, chúng ta thử gọi 500 GET requests từ một app Spring Boot này tới app Spring Boot khác với 2 môi trường có nén và không nén data. Và đây là kết quả:

Chúng ta có thể thấy Protocol buffer nhanh hơn từ 5 đến 6 lần so với JSON.

Create

Để sử dụng Proto DataStore, chúng ta phải định nghĩa loại data bằng một file proto settings.pb trong folder app/src/main/proto/ như sau:

syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings { int32 open_app_counter = 1;
}

Sau đó, tiếp tục khai báo một object implement class Serializer<T> với T là kiểu dữ liệu đã được định nghĩa trong proto file.

object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings.getDefaultInstance() override suspend fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo( t: Settings, output: OutputStream ) = t.writeTo(output)
}

Và cuối cùng là sử dụng property delegate với keyword dataStore để tạo một instance của DataStore<T>.

val Context.settingsDataStore: DataStore<Settings> by dataStore( fileName = "settings.pb", serializer = SettingsSerializer
)

Read

Tương tự như Preferences DataStore, chúng ta cũng dùng DataStore.data để trả về một Flow.

val openAppCounterFlow: Flow<Int> = context.settingDataStore.data .map { settings -> // The openAppCounter is generated from the proto schema. settings.openAppCounter }

Write

Để ghi data vào Proto DataStore, chúng ta có function updateData().

context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build()
}

So sánh với SharedPreferences

Migrate from SharedPreferences to Preferences DataStore

Để migrate, chúng ta truyền SharedPreferencesMigration vào param produceMigrations. DataStore sẽ tự động migrate cho chúng ta.

val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = DATA_STORE_NAME produceMigrations = { context -> listOf(SharedPreferencesMigration( context, SHARED_PREFERENCES_NAME )) }
)

Migrate from SharedPreferences to Proto DataStore

Trước tiên, chúng ta cần khai báo UserProfileUserProfileSerializer tương tự như các bước ở trên. Sau đó viết một mapping function để migrate từ cặp key-value trong SharedPreferences sang loại dữ liệu trong Proto DataStore.

val Context.dataStore: DataStore<UserProfile> by dataStore( fileName = "settings.pb", serializer = UserProfileSerializer, produceMigrations = { context -> listOf( SharedPreferencesMigration( context, "settings_pref" ) { prefs: SharedPreferencesView, user: UserProfile -> user.toBuilder() .setName(prefs.getString(NAME_KEY)) .setId(prefs.getInt(ID_KEY)) .setEmail(prefs.getString(EMAIL_KEY)) .build() } ) }
)

References

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 1: Làm quen cô nàng Flutter

Lời mở đầu. Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động vậy.

0 0 254

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 3: Lột trần cô nàng Flutter, BuildContext là gì?

Lời mở đầu. Màn làm quen cô nàng FLutter ở Phần 1 đã gieo rắc vào đầu chúng ta quá nhiều điều bí ẩn về nàng Flutter.

0 0 189

- vừa được xem lúc

[Android] Hiển thị Activity trên màn hình khóa - Show Activity over lock screen

Xin chào các bạn, Hôm nay là 30 tết rồi, ngồi ngắm trời chờ đón giao thừa, trong lúc rảnh rỗi mình quyết định ngồi viết bài sau 1 thời gian vắng bóng. .

0 0 93

- vừa được xem lúc

Tìm hiểu Proguard trong Android

1. Proguard là gì . Cụ thể nó giúp ứng dụng của chúng ta:. .

0 0 83

- vừa được xem lúc

Làm ứng dụng học toán đơn giản với React Native - Phần 6

Chào các bạn một năm mới an khang thịnh vượng, dồi dào sức khỏe. Lại là mình đây Đây là link app mà các bạn đang theo dõi :3 https://play.google.com/store/apps/details?id=com.

0 0 51

- vừa được xem lúc

20 Plugin hữu ích cho Android Studio

1. CodeGlance. Plugin này sẽ nhúng một minimap vào editor cùng với thanh cuộn cũng khá là lớn. Nó sẽ giúp chúng ta xem trước bộ khung của code và cho phép điều hướng đến đoạn code mà ta mong muốn một cách nhanh chóng.

0 0 301