Edited at

【Android Architecture Components】LiveData 和訳

More than 1 year has passed since last update.


注意

LiveDataの和訳になります。

わかりづらい表現を意訳したり、回りくどいところを端折ったりしています。

和訳に自信がないところもあるため、間違いを見つけた場合は指摘してください。

・・・・・

LiveDataはライフサイクルを意識した監視可能なデータホルダークラスです。

通常の観測とは異なり、LiveDataLifecycleによって、観測するアプリケーションコンポーネントのライフサイクルを考慮します。


注:LiveDataをプロジェクトにインポートする方法についてはAdding Components to your Project

を参照してください。


ObserverLifecycleSTARTEDRESUMEDの状態の場合、LiveDataObserverをアクティブ状態とします。

public class LocationLiveData extends LiveData<Location> {

private LocationManager locationManager;

private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};

public LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
}

@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}

@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}

Locationリスナーの実装で3つの重要な箇所があります。


onActive()

このメソッドはLiveDataにアクティブなObserverがある場合に呼び出されます。

これは、デバイスからのロケーション更新を観測する必要があることを意味します。


onInactive()

このメソッドはLiveDataにアクティブなObserverが無い場合に呼び出されます。

アクティブなObserverが無いので、LocationManagerに接続しておく必要はありません。無駄にバッテリーのを消費するだけです。


setValue()

このメソッドを呼び出すと、LiveDataインスタンスの値が更新され、アクティブなObserverに変更を通知します。

この新しいLocationLiveDataをこのように使用できます。

public class MyFragment extends LifecycleFragment {

public void onActivityCreated (Bundle savedInstanceState) {
LiveData<Location> myLocationListener = ...;
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.addObserver(this, location -> {
// update UI
});
}
});
}
}

addObserver() の第一引数にLifecycleOwnerを渡しています。

これは、オブザーバがLifecycleに紐づけられることを意味しています。

これによって、

Lifecycleがアクティブな状態(STARTEDまたはRESUMED)でない場合、オブザーバは値の更新があったとしても呼び出されません。

Lifecycleが破棄された場合、自動的にオブザーバも解除されます。

LiveDataがライフサイクルに対応していることで、複数のActivity、Fragmentなどの間でデータの共有が可能になります。

サンプルをシンプルにするため、シングルトンにしています。

public class LocationLiveData extends LiveData<Location> {

private static LocationLiveData sInstance;
private LocationManager locationManager;

@MainThread
public static LocationLiveData get(Context context) {
if (sInstance == null) {
sInstance = new LocationLiveData(context.getApplicationContext());
}
return sInstance;
}

private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};

private LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
}

@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}

@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}

Fragmentで以下のように使用します。

public class MyFragment extends LifecycleFragment {

public void onActivityCreated (Bundle savedInstanceState) {
Util.checkUserStatus(result -> {
if (result) {
LocationLiveData.get(getActivity()).observe(this, location -> {
// update UI
});
}
});
}
}

MyLocationListener インスタンスを複数のActivityやFragmentから観測している場合もあり、

LiveDataはそれらがアクティブ状態の時だけ接続するようにうまく管理します。

LiveDataには以下の利点があります。

メモリリークをしないObserverLifecycleに紐づくことによって、Lifecycleが破棄される時にObserverも解除されます。

Activityが非活性になったことによるクラッシュがなくなるObserverLifecycleが非活性(Activityがバックスタックにいるなど)である場合、変更イベントを受け付けません。

常に最新のデータ:ライフサイクルが再び開始された場合(Activityがバックスタックから復帰した場合など)、最新のロケーションデータが受信されます。

構成の変更に適切に対処できる:画面回転などによって構成が変更され、ActivityやFragmentが再生成された場合でも、すぐに最後に有効だったLocationを使用できます。

リソースの共有MyLocationListenerの単一のインスタンスで、システムサービスに1度だけ接続して、アプリ内のすべてのオブザーバを適切にサポートします。

ライフサイクルのハンドリングから解放されるMyFragmentは必要な時にデータを観察するだけで、MyFragmentが停止状態であることを気にする必要もなく、停止後に観察を開始することもありません。

MyFragment自身のライフサイクルを紐づけることによって、LiveDataはこれを全て自動化しています。


LiveDataのトランスフォーメーション

オブザーバに通知する前にLiveDataの値を変更したり、値を元に別のLiveDataインスタンスに変換して返す必要があるかもしれません。

android.arch.lifecycleパッケージはこれらの操作のヘルパーメソッドを含むTransformationsクラスを提供します。


Transformations.map()

LiveDataに関数を適用し、結果を下流に伝播します。

LiveData<User> userLiveData = ...;

LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});


Transformations.switchMap()

map()と似ていて、値に関数を適用して別のLiveDataを生成して下流に結果を伝播します。

private LiveData<User> getUser(String id) {

...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

トランスフォーメーションはオブザーバが無効な場合は無駄な処理をしないように、Lifecycleを引き継ぐので、明示的な呼び出しや依存関係を追加することなく、ライフサイクルに関連した動作を暗黙的に行うことができます。

例えば、ユーザが住所を入力して、その住所の郵便番号を受け取るUIがあるとします。

このUIの純粋なViewModelはこのようになります。

class MyViewModel extends ViewModel {

private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}

public LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}


注:原文ではgetPostalCode()privateですが、publicにしています。


この実装の場合、getPostalCode()を呼び出すたびに、前のLiveDataから登録を解除して、新しいLiveDataインスタンスで再登録を行う必要があります。

さらに、UIが再生成された場合、前とは別のrepository.getPostCode()を呼び出します。

上記の実装の代わりに、住所を入力して郵便番号に変換する実装にすることができます。

class MyViewModel extends ViewModel {

private final PostalCodeRepository repository;
private final MutableLiveData<String> addressInput = new MutableLiveData();
public final LiveData<String> postalCode =
Transformations.switchMap(addressInput, (address) -> {
return repository.getPostCode(address);
});

public MyViewModel(PostalCodeRepository repository) {
this.repository = repository
}

public void setInput(String address) {
addressInput.setValue(address);
}
}


注:原文ではsetInput()privateですが、publicにしています。


postalCodeのインスタンスは変更されないので、public finalで定義されていることに注目してください。

postalCodeaddressInputのトランスフォーメーションのために定義され、addressInputに変更があった時にアクティブなオブザーバが存在する場合、repository.getPostCode()が呼び出されます。もし、アクティブなオブザーバが存在しない場合は、オブザーバが追加されるまで処理は実行されません。


独自のトランスフォーメーションを作成する

独自のトランスフォーメーションを作成するためにMediatorLiveDataクラスを使用します。

このクラスは、他のLiveDataインスタンスを適切にリッスンして、通知されたイベントを処理するために作られました。

MediatorLiveDataLiveDataのアクティブ/非アクティブ状態を考慮してに通知します。

詳しくはTransformationsを参照してください。