LiveData
는 data holder 클래스로 data가 가진 값들을 확인할 수 있으며, 다른 observerable과 다르게 app component의 lfe cycle에 따라 observing 여부를 지정할 수 있습니다.Location 정보를 받는 예제
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);
}
}
- onActive() 함수는 LiveData가 active observer를 하나라도 가질때 호출. 따라서 예제처럼 location update를 시작해야 한다.
- onInactive() 함수는 LiveData가 active observer를 하나도 가지지 않았을 때 호출. 따라서 변경사항이 없기 대문에 LocationManager를 떼내도록 한다.
- setValue() 함수가 호출되면 LiveData의 값을 업데이트 함. 또한 active observer에가 notify 한다.
public class MyFragment extends LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LiveData<Location> myLocationListener = ...;
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.observe(this, location -> {
// update UI
});
}
});
}
}
observe()는 LifecycleOwner와 같이 등록되어, life cycle에 따라 observer의 동작이 결정됩니다.
- 등록된 LifeCycle함수가 active state일때 (STARTED or RESUMED) 데이터가 변경되면 noti를 발생함. (그외 상태에서는 noti가 호출되지 않음)
- LifeCycle이 DESTROYED 되면 자동으로 observer가 remove
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);
}
}
Singleton으로 만들었으니, fragment에서는 아래와 같이 호출할 수 있습니다.
public class MyFragment extends LifecycleFragment {
public void onActivityCreated (Bundle savedInstanceState) {
Util.checkUserStatus(result -> {
if (result) {
LocationLiveData.get(getActivity()).observe(this, location -> {
// update UI
});
}
});
}
}
특징
- Memory leak 방지: Observer는 Lifecycle과 연결되기 때문에 Lifecycle과 함께 cleanup 된다.
- activity stop에 따른 crash 방지: inActive 상태에서는 이벤트를 받지 않기 때문에 stop 상태에서 수행하면서 발생하는 crash를 방지한다
- Always up to date data: LifeCycle이 재 시작되면 가장 최신 데이터를 받는다
- Proper configuration change: activity나 fragment가 재생성된느 경우 최신 데이터를 즉각 받을 수 있다
- Sharing Resources: singleton으로 만들어서 system service(resource)에 한번만 연결하고, 관련 observer들을 전부 커버할 수 있다
- No more manual lifecycle handling: lifecycle에 대한 처리를 직접할 필요가 없다
Transformation
Api |
Definition |
Description |
LiveData를 return하며, source에 이벤트가 생길때마다 main thread에서 function이 수행된다. |
||
LiveData<Y> switchMap (LiveData<X> trigger, Function<X, LiveData<Y>> func) |
trigger LiveData가 변경에 따라 이벤트를 발생 시키면 function을 적용하여 결과를 새로만든 새로운 LiveData에 set한다. 또한 이때 새로운 LiveData에 등록된 observer들에게 재전송 된다. |
// 예제1
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
// 예제2
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
Transformation은 lifeCycle에 따라 LiveData가 발생될때 수행되므로 laze calculation을 구현할 수 있습니다.
또한 이는 ViewModel 내부에서 LifeCycle을 필요로 하는경우 유용하게 쓰일 수 있겠죠~
우편번호를 얻는 예제를 보면 아래와 같습니다.
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
위 코드는 우편번호를 얻는 코드(getPostalCode(String address))를 수행하면 새로운 LiveData를 반환합니다.
따라서 UI는 기존 LiveData를 unregiter 하고 새로운 LiveData object에 register 해야합니다.
또한 UI가 재생성 되는 경우에도 repository.getPostCode(address)를 호출하여야 합니다.
이런경우 transformation을 이용하여 아래와 같이 수정할 수 있습니다.
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
}
private void setInput(String address) {
addressInput.setValue(address);
}
}
postalCode는 final로 지정되어 변경되지 않습니다. addressInput이 변경될때 등록되어있는 active한 observer가 있다면 function이 수행되고, 그렇지 않으면 수행되지 않습니다.
'개발이야기 > Android' 카테고리의 다른 글
Android Architecture Components #5 - ViewModel (0) | 2017.10.17 |
---|---|
Android Architecture Components #4 - LiveData API (0) | 2017.10.16 |
Android Architecture Components #2 - Handling Lifecycles (0) | 2017.10.14 |
Android Architecture Components #1 Guide to App Architecture (2) | 2017.10.13 |
Android Service간 통신 #3 (1) | 2017.10.12 |