新型コロナの影響で自宅待機になってしまい、その間勉強するものとしてSunflowerリポジトリを勧めてもらいました。
JetPackのライブラリのうち、今回はLiveData編です
DataBinding編からの続きです。
尚、引用しているソースは明記しているところ以外は、基本的には全てSunflowerのリポジトリのものです。
環境
- 確認時は
Android Studioのバージョンは3.6.2を使用しました -
JetPackはAndroidXライブラリを利用するのでCompile SDKを28以上にする必要があります
そもそもLiveDataってなに?
公式の説明によると
-
LiveDataは監視可能なデータホルダー クラス - 通常の監視と異なるのはライフサイクルに応じた監視が可能
- ライフサイクルがアクティブなオブザーバーのみを更新する
- ライフサイクルの状態が
STARTEDまたはRESUMEDの場合 = アクティブ -
LiveDataは更新に関する情報をアクティブなオブザーバーにのみ通知 -
非アクティブなオブザーバーには、変更に関する通知は行われない
- ライフサイクルの状態が
-
オブザーバーはLifecycleOwnerインターフェースを実装するオブジェクトとペアで登録できる- ペアリング → 対応する
Lifecycleオブジェクトの状態がDESTROYED→オブザーバーを削除可能
- ペアリング → 対応する
-
アクティビティとフラグメントで利用すると、LiveDataオブジェクトを安全に監視可能- ライフサイクルが破棄されるとすぐに登録が解除される
だそうで、ざっくりいうと
LifecycleOwnerが破棄されれば登録が解除される通知機能を持つクラス
という感じでしょうか??(^^;
メリット
公式にはこんなに書いてありました!
- UI をデータの状態と一致させることができる
- メモリリークが発生しない
- 停止されたアクティビティに起因するクラッシュが発生しない
- 手動によるライフサイクル処理が行われない
- データが常に最新
- 適切な設定の変更
- リソースの共有
たくさんあります!!
購読処理の実施(LiveDataとの連携)
DataBinding編で見ていた処理を確認してみましょう
使用箇所
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentGardenBinding.inflate(inflater, container, false)
:
:
subscribeUi(adapter, binding)
return binding.root
}
private fun subscribeUi(adapter: GardenPlantingAdapter, binding: FragmentGardenBinding) {
viewModel.plantAndGardenPlantings.observe(viewLifecycleOwner) { result ->
binding.hasPlantings = !result.isNullOrEmpty()
adapter.submitList(result)
}
- 名前の通り
subscribeUi()メソッドで購読処理を実施しており、plantAndGardenPlantingsをobserveしています。
こうすることで、plantAndGardenPlantingsの値が変更された時にresultが更新されて、そこから算出された
hasPlantingsでRecyclerViewを更新します。
購読を実施するタイミングについて
この購読処理はGardenFragment#onCreateViewで実施されています。
その理由は下記によるものです。
- 殆どの場合画面が
作られた時にのみ実施されるのが適しているためです。- 例えば、
ActivityのonResume()などの場合は、繰り返し呼ばれる場合があるので不適切
- 例えば、
- アクティビティやフラグメントが
アクティブになり次第、そのデータを表示できるため(その前に準備しておく必要がある!)
LiveDataクラスを見てみる
次にplantAndGardenPlantingsがどうなっているかをみてみましょう!
LiveDataクラスの定義
class GardenPlantingListViewModel internal constructor(
gardenPlantingRepository: GardenPlantingRepository
) : ViewModel() {
val plantAndGardenPlantings: LiveData<List<PlantAndGardenPlantings>> =
gardenPlantingRepository.getPlantedGardens()
}
なにやらカッコが多くて見にくいですが
plantAndGardenPlantingsの型は LiveData<List<PlantAndGardenPlantings>> となっています
これはLiveDataクラスで、PlantAndGardenPlantingsクラスのリストの型をしているよ! という定義です。
class GardenPlantingRepository private constructor(
private val gardenPlantingDao: GardenPlantingDao
) {
:
:
fun getPlantedGardens() = gardenPlantingDao.getPlantedGardens()
:
:
}
@Dao
interface GardenPlantingDao {
:
:
@Transaction
@Query("SELECT * FROM plants WHERE id IN (SELECT DISTINCT(plant_id) FROM garden_plantings)")
fun getPlantedGardens(): LiveData<List<PlantAndGardenPlantings>>
:
:
}
GardenPlantingRepository#getPlantedGardens()→ GardenPlantingDao#getPlantedGardens() と順に呼び出していき、
最終的にはDBからデータを取得していますね。
Observeの定義とonChanged()
@MainThread inline fun <T> LiveData<T>.observe(
owner: LifecycleOwner,
crossinline onChanged: (T) -> Unit
): Observer<T> {
val wrappedObserver = Observer<T> { t -> onChanged.invoke(t) }
observe(owner, wrappedObserver)
return wrappedObserver
}
そして、LiveDataの定義をみてみます。。。
とても複雑ですが、ポイントは
-
observe()はメインスレッドで実施する - 第二引数のラムダ式
onChange()も、メインスレッドで実施する
改めて最初のobserveメソッドを実施している部分をみてみると・・・
private fun subscribeUi(adapter: GardenPlantingAdapter, binding: FragmentGardenBinding) {
viewModel.plantAndGardenPlantings.observe(viewLifecycleOwner) { result ->
binding.hasPlantings = !result.isNullOrEmpty()
adapter.submitList(result)
}
-
observe()メソッドのラムダ式で書いてある第二引数のonChange()はメインスレッドで実行する。 - resultに値がセットされた場合、通知がくる
- result には
list<PlantAndGardenPlantings>が返ってくる
注意点
- 実際に動作確認を実施したところ、
値が変更した時のみに動作するのかと思っていたのですが、
そうではなくLiveDataに値がセットされる度に、onChanged()が実行されていました。
(onChanged()じゃない気がしますが。。。)
まとめ
-
LiveDataとは、LifecycleOwnerが破棄されれば登録が解除される通知機能を持つクラスです
*LiveData<データ型>で定義します - LiveDataの購読処理はonCreate() (Fragmentの場合はonCreateView)で実施するのが殆どの場合適している。
- 購読された
LiveDataのデータが更新されると、その度に通知が来る。- ただし、更新とは言っても
変更する度ではなく、値がセットされる度に通知がくる。
- ただし、更新とは言っても
以上です!
参考サイト
- Sunflowerリポジトリ
- LiveData: 公式ドキュメント
- DataBinding: 公式ドキュメント