rx-preferencesをつかってSharedPreferencesの更新をSubscribeする
ブログエントリで試しているAndroidアプリにFluxアーキテクチャを導入をしたことでデータの流れがStreamになった。SharedPreferencesも rx-preferences
をつかえば更新状態をSubscribeできるので導入過程をまとめていく。
お試し中のアプリはOpenWeatherMapのAPIを使い天気情報を取得する。ZipCodeをクエリに追加すれば地域の天気情報が取得できる。アプリ内でZipCodeを登録できるようにしたいのでSharedPreferencesで管理することにする。
rx-preferencesをつかう
rx-preferences
を追加してRepository化するまで。
# dependencies.gradle rxPreferences = 'com.f2prateek.rx.preferences2:rx-preferences:2.0.0-RC3'
# SettingsRepository.kt @Singleton class SettingsRepository @Inject constructor(private val application: Application) { private val SETTING_NAME = "setting" private val ZIP_CODE_NAME = "zip_code" fun getZipCode() = getRxSharedPreferences(SETTING_NAME).getString(ZIP_CODE_NAME) fun updateZipCode(zipCode: String) = getZipCode().set(zipCode) private fun getRxSharedPreferences(name: String) = RxSharedPreferences.create( application.getSharedPreferences(BuildConfig.APPLICATION_ID + '.' + name, Context.MODE_PRIVATE)) }
Repository層の役割はデータ取得や更新など。Repositoryを使う側はその先がAPIかDBかに関心する必要させたくないのでSharedPreferencesのデータ取得や更新もRepository層で実装した。
このRepositoryをFluxアーキテクチャに乗せていく。
ActionとStoreをつくる。Dispatcherはつくらない。
# SettingsAction.kt @Singleton class SettingsAction @Inject constructor(private val settingsRepository: SettingsRepository) { fun updateZipCode(zipCode: String) = settingsRepository.updateZipCode(zipCode) }
ActionはZipCodeを更新するメソッドを追加した。
# SettingsStore.kt @Singleton class SettingsStore @Inject constructor(private val settingsRepository: SettingsRepository) { fun zipCode() = settingsRepository.getZipCode() .asObservable() .subscribeOn(Schedulers.io()) .toFlowable(BackpressureStrategy.LATEST) }
StoreにはZipCodeの状態をObserveする zipCode()
メソッドを追加した。
rx-preferencesは asObservable()
を呼び出せばStreamオブジェクトを取得できる。BackpressureStrategy.LATEST
を有効にすることで常に最新の値が流れるようにした。
Fluxアーキテクチャに乗るならばDispatcherを作るところだが、 rx-preferences
がStreamを提供してくれるのでこのエントリではDispatcherを追加していない。このアプリはまだ小さいのでDispatcherの追加はしない判断をしたが大きなアプリであればデータのアクションに応じて専用のDispatcherに乗せたほうが良い場合もあると思う。
ZipCodeの更新をSubscribeする
FragmentでZipCodeの更新をSubscribeする。
# ForecastsFragment.kt class ForecastsFragment : AutoDisposeFragmentKotlin() { // --- override fun onViewCreated(view: View, savedInstanceState: Bundle?) { forecastsStore.forecasts() .observeOn(AndroidSchedulers.mainThread()) .`as`(autoDisposable(this)) .subscribe { forecasts -> cityView.text = "%s/%s".format(forecasts.city.name, forecasts.city.country) listView.adapter = ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1, forecasts.list.map { "%s - %s %s/%s".format( DateUtils.formatDateTime(activity, it.dt * 1000L, FORMAT_NO_YEAR), it.weather.get(0).main, it.temp.min, it.temp.max) }) } savedInstanceState ?: settingsStore.zipCode() .observeOn(AndroidSchedulers.mainThread()) .`as`(AutoDispose.autoDisposable(this)) .subscribe { if (it.isNotBlank()) { forecastsAction.findByDaily(it) } else { errorAction.onError("You must to set ZipCode.") } } } // --- }
SettingActivityでZipCodeが更新されると settingsStore.zipCode()
に最新のZipCodeが流れてくるのでSubscribeをしてAPIを呼びだしている。
forecastsAction.findByDaily(it)
が呼び出されれば、 forecastsStore.forecasts()
に最新のAPI結果が流れてくるのでViewが切り替わる。
コード
rx-preferencesの導入前と後のコード比較ができるようにPRを残しています。 エントリで紹介したコードは断片的なので参考になれば嬉しいです。
https://github.com/soushin/sunshine-app/pull/4github.com
まとめ
- rx-preferencesをつかってSharedPreferencesの更新をSubsribeしてみた。1つのアクションが発火されると連鎖的に複数のStreamが流動するのを体験できた。
- まだ2つのStream(APIとSharedPreferences)だけであるが3つ,4つといったStreamが絡み合う条件などは実装が複雑になりそうである。ただRxには実装の複雑性を回避するものもあるので今後のエントリでまとめていきたい。