English | 中文
Jetpack DataStore is a data storage solution that uses Kotlin Coroutines or RxJava to store data in an asynchronous manner with consistent transaction methods. Its usage is different from other storage solutions such as SharedPreferences and MMKV, making it more special. Therefore, there are not many good DataStore encapsulations available online. I have done a lot of exploration and attempts and finally developed a very satisfactory set of usage methods, which I hope can help everyone.
- No need to create DataStore, RxDataStore, or Preferences.Key objects;
- Supports both Kotlin Coroutines and RxJava usage;
- Uses property names as key names, eliminating the need to declare a large number of key name constants;
- Ensures type safety and avoids exceptions caused by inconsistent types or key name.
Which one to choose between DataStore and MMKV? It is recommended to read this article. If you find the article too long, you can directly read the summary. As for the view of DataStore, it needs to be modified a little bit, because DataStore now supports not only Kotlin Coroutines, but also RxJava usage. Therefore, DataStore is also suitable for Java projects.
If you plan to use MMKV, you can use my other library MMKV-KTX. If you choose to use DataStore, then this library is your best choice.
Add the following to the root build.gradle
file:
allprojects {
repositories {
//...
maven { url 'https://www.jitpack.io' }
}
}
Add the following dependencies to the module's build.gradle
file:
dependencies {
implementation 'com.github.DylanCaiCoding.DataStoreKTX:datastore-ktx:1.0.0'
// Optional
implementation 'com.github.DylanCaiCoding.DataStoreKTX:datastore-rxjava2:1.0.0'
implementation 'com.github.DylanCaiCoding.DataStoreKTX:datastore-rxjava3:1.0.0'
}
Inherit the DataStoreOwner
class in a class, and then use the by xxxxPreference()
function to delegate the property to DataStore
. For example:
object SettingsRepository : DataStoreOwner(name = "settings") {
val counter by intPreference()
val language by stringPreference(default = "zh")
}
If you already have a superclass that cannot be inherited, implement IDataStoreOwner by DataStoreOwner(name)
instead, for example:
object SettingsRepository : BaseRepository(), IDataStoreOwner by DataStoreOwner(name = "settings") {
//...
}
Make sure that the name
used is not duplicated so that type safety can be 100% guaranteed!!!
The following delegation functions that use the property name as the retrieval key value are supported:
- intPreference()
- longPreference()
- booleanPreference()
- floatPreference()
- doublePreference()
- stringPreference()
- stringSetPreference()
The get()
function that calls this property reads the data by executing dataStore.data.map {...}
, for example:
// Call in coroutine
val language = SettingsRepository.language.get()
// val language = SettingsRepository.language.getOrDefault()
The set()
function that calls this property saves the data by executing dataStore.edit {...}
, for example:
// Call in coroutine
SettingsRepository.counter.set(100)
SettingsRepository.counter.set { (this ?: 0) + 1 }
This property can also be used as Flow
or LiveData
. In this way, a notification callback is made each time the data changes, which can be used to update UI or to write streaming code. For example:
SettingsRepository.counter.asLiveData()
.observe(this) {
tvCount.text = (it ?: 0).toString()
}
SettingsRepository.counter.asFlow()
.map { ... }
By default, only Coroutine is supported. You can perform some simple adaptations to extend it to support RxJava. First, add the dependency datastore-rxjava2
or datastore-rxjava3
to build.gradle
.
dependencies {
// Optional
implementation 'com.github.DylanCaiCoding.DataStoreKTX:datastore-rxjava2:1.0.0'
implementation 'com.github.DylanCaiCoding.DataStoreKTX:datastore-rxjava3:1.0.0'
}
Then change the DataStoreOwner
class to the RxDataStoreOwner
class, and it will be adapted. It's recommended to add the @JvmStatic
annotation to the property, which will make Java code calling this property more concise.
object SettingsRepository : RxDataStoreOwner(name = "settings") {
@JvmStatic
val counter by intPreference()
}
The new getAsync()
function call of this property reads the data by executing rxDataStore.updateDataAsync(prefsIn -> ...)
, and the return value is Single<T>
, for example:
SettingsRepository.getCounter().getAsync()
.subscribe(counter -> {
//...
});
The new setAsync()
function call of this property writes the data by executing rxDataStore.data().map(prefs -> ...)
, for example:
SettingsRepository.getCounter().setAsync(100);
SettingsRepository.getCounter().setAsync((counter, prefsIn) -> counter + 1);
It can also be used as a Flowable
. In this way, a notification callback is made each time the data changes, which can be used to update UI or to write streaming code. For example:
SettingsRepository.getCounter().asFlowable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(counter -> tvCounter.setText(String.valueOf(counter)));
Coroutine and RxJava usage can be used together, as long as it is the same property, and the read/write functions operate on the same data source.
Library | Introduction |
---|---|
Longan | Possibly the easiest-to-use Kotlin utility library |
LoadingStateView | Decoupling the title bar or view of loading, loading failure, no data, etc., supporting two-line code integration into the base class |
ViewBindingKTX | The most comprehensive ViewBinding tool |
MMKV-KTX | The most flexible and easy-to-use MMKV tool |
Tracker | A lightweight tracking framework based on Buzzvideo's view tree tracking idea |
Copyright (C) 2023. Dylan Cai
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.