Androidアーキテクチャコンポーネント(AAC)は「堅牢でテストとメンテナンスが簡単なアプリの設計を支援する」とドキュメントで説明されています。
その中で紹介されているコンポーネントの1つが「LiveData」です。
LiveDataについて、まとめました。
目次
LiveDataとは
LiveDataはデータを格納する箱です。これだけでは変数と変わりません。
しかし、この箱には「データの更新を通知する監視機能」が付いています。

Observerはvalueを監視していて、valueが更新されるとobserveによって登録されたonChange関数を実行(関数オブジェクトへ通知)します。
LiveDataは、ただ単にvalueが更新されたらonChangeを実行するだけで、onChangeが所属するインスタンス(クラス)について関知しません。
LiveDataが効果的な場面
LiveDataが効果的な場面は、UI(User Interface)関連のデータの更新をきっかけにView(TextViewなどのWidget)の表示を更新する時です。
以下のサンプルは、カウンターの値を4桁の数字で表示するアプリです。

private const val KEY_COUNT = "KeyCount"
class MainActivity : AppCompatActivity() {
lateinit var counter: Counter
private val fourFigure: MutableLiveData<String> = // LiveDataの宣言
MutableLiveData<String>("0000") // (コンストラクタで初期化)
override fun onCreate(savedInstanceState: Bundle?) {
...
// Viewの表示を更新(onChange関数の定義,Observerの登録)
fourFigure.observeForever({ fourFigure ->
_txtCounter.text = fourFigure
})
// Activityの起動で初期値を定義する
counter = Counter(savedInstanceState?.getInt(KEY_COUNT, 0)?:0)
fourFigure.value = "%04d".format(counter.value) // valueの更新
_btnCount.setOnClickListener {
// CountUpボタンのクリックでカウンタを+1にする
counter.countUp()
fourFigure.value = "%04d".format(counter.value) // valueの更新
}
_btnClear.setOnClickListener {
// Clearボタンのクリックでカウンタを0にする
counter.clear()
fourFigure.value = "%04d".format(counter.value) // valueの更新
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_COUNT, counter.value)
}
}
class Counter(value: Int = 0) {
var value: Int = value
private set(value) { field = value }
fun countUp() { value = if(value == 9999) 0 else ++value }
fun clear() { value = 0 }
}
LiveData(fourFigure)に格納されたvalueの値を更新するだけで、onChaned関数に定義された「Viewの表示を更新」が実行されます。
LiveDataで特徴的なのは、「データを更新する」というロジック的な部分と「表示を更新する」というUI的な部分が分離できる所です。
―――――――――
次に説明する「LiveDataとViewModelの連携」では、その特徴が一層強まります。
LiveDataとViewModelの連携
LiveDataがもっと効果的な場面は、ViewModelと連携させた時です。
LiveDataをViewModel側へ移動
ViewModelは「 ライフサイクルを超えたデータの引き継ぎ」を行うコンポーネントです。
※詳細は「ViewModelでライフサイクルを超えたデータの引き継ぎ」を参照
LiveDataで表現したUI関連のデータをViewModelインスタンスへ移動させます。これにより、onSave|onRestoreInstanceStateでデータの保存と復元を行う必要はありません。
UI関連のデータをViewModelインスタンスへ移動させたことで、ロジック的な部分とUI的な部分の分離がより一層強まります。
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by lazy {
ViewModelProvider(
this, ViewModelProvider.NewInstanceFactory()
).get(MainViewModel::class.java)
}
private lateinit var fourFigureObserver: Observer<String>
override fun onCreate(savedInstanceState: Bundle?) {
...
_btnCount.setOnClickListener { viewModel.onClick(it.id) }
_btnClear.setOnClickListener { viewModel.onClick(it.id) }
fourFigureObserver = Observer<String> { fourFigure ->
_txtCounter.text = fourFigure
}
viewModel.fourFigure.observeForever(fourFigureObserver) // Observerの登録
}
override fun onDestroy() {
super.onDestroy()
viewModel.fourFigure.removeObserver(fourFigureObserver) // Observerの削除
}
}
class MainViewModel : ViewModel() {
private val counter = Counter()
private val _fourFigure: MutableLiveData<String> =
MutableLiveData<String>("0000")
val fourFigure: LiveData<String> = _fourFigure // LiveData型で外部へ公開
fun onClick(resID: Int) {
when(resID) {
R.id.btnCount -> { counter.countUp() }
R.id.btnClear -> { counter.clear() }
else -> {}
}
_fourFigure.value = "%04d".format(counter.value)
}
}
このプログラムは注意点が2つあります。
注意点1:インスタンスはLiveData型で公開
LiveDataのインスタンスをMutableLiveData型で公開すると、valueがViewModel以外の場所で変更できてしまいます。
意図的に変更しなければ良いのですが、間違って変更してしまう場合もまります。
よって、外部で変更できないように、LiveData型で公開することが推奨されています。
注意点2:メモリーリークの回避
オブジェクトの寿命は「ViewModel>Activity」になります。よって、ViewModelからActivityを参照する行為はメモリーリークの原因になります。
ViewModel内のLiveDataはonChange関数(関数オブジェクト)でTextViewを参照しています。TextViewは内部にActivity(Context)の参照を持っているので、上記のメモリーリークの条件に適合します。
メモリーリークを回避する方法は、LiveDataに登録されたObserverインスタンス(onChange関数)を削除することです。削除はLiveData#removeObserver( )で行うことが出来ます。
よって、Activityが閉じられる適切なタイミングでremoveObserver( )の実行が必要になります。
―――――――――
次に説明する「LiveDataとLifecycleの連携」では、別の方法でメモリーリークの回避を行います。
LiveDataとLifecycleの連携
LiveDataがもっともっと効果的な場面は、ViewModelならびにLifecycleと連携させた時です。
メモリーリークの回避を自動化
Observer(onChange関数)をLiveDataに登録するobserveには2種類あります。
(1) observeForever(Observer<? super T> observer)
(2) observe(LifecycleOwner owner, Observer<? super T> observer)
(2)はObserverに加えてLifecycleOwnerを引数に持っています。
(2)を使うとObserverはActivityのライフサイクルの状態(Lifecycle.State.XXX)に従って次のような動作になります。
※詳細は「Lifecycleでライフサイクル対応コンポーネント作成」を参照
- INITIALIZED、STARTEDまたはRESUMEDの期間はObserverを登録可能
- STARTEDまたはRESUMEDの期間はvalueの更新を監視
- DESTROYでObserverを自動的に削除
つまり、メモリーリークの回避策である「LiveDataに登録されたObserverインスタンス(onChange関数)を削除する」をActivityの生存範囲に合わせて自動的に行ってくれます。
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by lazy {
ViewModelProvider(
this, ViewModelProvider.NewInstanceFactory()
).get(MainViewModel::class.java)
}
private lateinit var fourFigureObserver: Observer<String>
override fun onCreate(savedInstanceState: Bundle?) {
...
_btnCount.setOnClickListener { viewModel.onClick(it.id) }
_btnClear.setOnClickListener { viewModel.onClick(it.id) }
viewModel.fourFigure.observe(this, { fourFigure -> // Observerの登録
_txtCounter.text = fourFigure
})
}
}
LiveData#observe( )を使えばメモリーリークの心配は無くなります。
補足1:LiveDataへ複数のObserverを登録
LiveDataへ複数のObserver(onChange関数)を登録できます。
実行は登録した順番です。
fourFigure.observe(this, { fourFigure ->
Log.i(TAG, "(1)onChangeが実行されました! fourFigure = ${fourFigure}")
})
fourFigure.observe(this, { fourFigure ->
Log.i(TAG, "(2)onChangeが実行されました! fourFigure = ${fourFigure}")
})
fourFigure.observe(this, { fourFigure ->
Log.i(TAG, "(3)onChangeが実行されました! fourFigure = ${fourFigure}")
}
:
I/MainActivity: (1)onChangeが実行されました! fourFigure = 0003
I/MainActivity: (2)onChangeが実行されました! fourFigure = 0003
I/MainActivity: (3)onChangeが実行されました! fourFigure = 0003
:
補足2:SAM変換の例
LiveData#observeForeverならびに#observeの引数ObserverはSAM形式です。
※SAM:Single Absorute Method、抽象メソッドが一つの抽象クラス
public interface Observer<T> {
void onChanged(T t);
}
よって、SAM変換されることを狙って、引数をObserverのインスタンスからラムダ式(関数オブジェクト)へ置き換えることが出来ます。
※詳細は「Kotlin:SAM変換」を参照
ラムダ式は省略の方法が多彩なので例を示しておきます。
// ★関数オブジェクトの参照値を受け渡す
// 省略できる箇所なし
fun onChange(fourFigure: String) {
_txtCounter.text = fourFigure
}
fourFigure.observe(this, ::onChange)
// ★無名関数を受け渡す
// 省略できる箇所なし
fourFigure.observe(this,
fun(fourFigure: String){ _txtCounter.text = fourFigure })
// ★ラムダ式(無名関数)を受け渡す
// 原形
fourFigure.observe(this,
{ fourFigure: String -> _txtCounter.text = fourFigure })
// 引数リストの最後がラムダ式ならカッコの外に出せる
fourFigure.observe(this) {
fourFigure: String -> _txtCounter.text = fourFigure }
// ラムダ式の引数の型が明確な場合(ここではString)は削除可能
fourFigure.observe(this) {
fourFigure -> _txtCounter.text = fourFigure }
// ラムダ式の引数が一つの場合は削除可能、引数はitで参照
fourFigure.observe(this) { _txtCounter.text = it }
関連記事:
