この記事について
HiltでProvideしていた「SharedPreferences」をJetpackの「Preferences DataStore」に置き換えようという内容です。
「Proto DataStore」には触れていません。
モジュール構成
今までの構成
Repository(自作)
-> SharedPref(自作)
-> SharedPreferences
-> デバイスのファイル「/data/data/{package-name}/shared_prefs/forecast_pref.xml」
これを
Repository(自作)
-> SharedPref(自作)
-> DataStore<Preferences>
-> デバイスのファイル「/data/data/{package-name}/files/forecast_pref_datastore.xml」
に変えていきます。
dependencies
依存関係に、以下を足します。
dependencies {
implementation "androidx.datastore:datastore-preferences:1.0.0"
}
HiltのModule定義なし(@Provide定義しない場合)
SharedPrefを以下の様に改造しました。
元々は、ModuleクラスでSharedPrefをProvideしていましたが、これをやめて自動でProvideされる様にしています。
+ @Singleton
class SharedPref @Inject constructor(
- private val sharedPreferences: SharedPreferences,
+ @ApplicationContext private val context: Context,
) {
+ // ① DataStoreのインスタンスを取得する
+ private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "forecast_pref_datastore")
+ // ② DataStoreのキーを生成する
+ private val keyAreaDataUpdatedAt = longPreferencesKey(SharedPrefKey.AreaDataUpdatedAt.name)
- var areaDataUpdatedAt: Long
- get() = sharedPreferences.getLong(SharedPrefKey.AreaDataUpdatedAt.name, 0L)
- set(value) = sharedPreferences.edit {
- putLong(SharedPrefKey.AreaDataUpdatedAt.name, value)
- }
+ // ③ 値を取得する
+ val areaDataUpdatedAt: Flow<Long> = context.dataStore.data.map { preferences ->
+ preferences[keyAreaDataUpdatedAt] ?: 0L
+ }
+ // ④ 値を更新する
+ suspend fun editAreaDataUpdatedAt(value: Long) {
+ context.dataStore.edit { preferences ->
+ preferences[keyAreaDataUpdatedAt] = value
+ }
+ }
}
使う側はこんな感じです。
// 値を取得する
@OptIn(ExperimentalCoroutinesApi::class)
override fun getArea(): Flow<Future<Area>> {
- return flow {
- val areaDataUpdatedAt = sharedPref.areaDataUpdatedAt
- emit(areaDataUpdatedAt.isPassedTime(minutes = 10))
+ return sharedPref.areaDataUpdatedAt
+ .map { areaDataUpdatedAt ->
+ areaDataUpdatedAt.isPassedTime(minutes = 10)
}.flatMapConcat { needServerData ->
if (needServerData) {
getAreaFromServer()
} else {
getAreaFromLocal()
}
}.onStart {
emit(Future.Proceeding)
}.catch { cause ->
emit(Future.Error(cause))
}.flowOn(dispatchers)
}
// 値を設定する
private fun saveArea(area: Area): Flow<Future<Area>> {
return flow<Future<Area>> {
/* いろいろ処理 */
- sharedPref.areaDataUpdatedAt = System.currentTimeMillis()
+ sharedPref.editAreaDataUpdatedAt(System.currentTimeMillis())
emit(Future.Success(area))
}.catch { cause ->
emit(Future.Error(cause))
}
}
HiltのModule定義あり(@Provideする場合)
テストやなんかで、DataStore自体をSharedPrefとかの自作クラスで直接インスタンス化したくない場合は、Module定義で以下の様に書くことができます。
preferencesDataStoreは、Module定義なしの時は「by」で入れていましたが、関数の時はgetValueを読んでいます。
getValueの第2引数は使用されませんので、dummyPropを定義してそれを渡すようにしています。
@Module
@InstallIn(SingletonComponent::class)
object InfrastructureModule {
private val dummyProp: Nothing? = null
@Provides
fun providePrefDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> =
preferencesDataStore(name = "forecast_pref_datasource")
.getValue(context, InfrastructureModule::dummyProp)
}
完成
ソースはこちらです。
Module定義なし
Module定義あり