Transformations.map(), switchMap(), distinctUntilChanged()

최희창·2022년 7월 14일
0

Android AAC

목록 보기
9/13

map(), switchMap()

  • LiveData를 위한 라이브러리
  • LiveData<'X'> 타입 데이터를 LiveData<'Y'> 타입 데이터로 변환한다는 공통점이 있지만, 차이점이 있습니다.

리턴 타입

  • LiveData<'Y'> map (LiveData<'X'> source, Function<'X','Y'> func) :
    -> LiveData<'X'> 타입 데이터를 LiveData<'Y'> 타입 데이터로 변환합니다. func에서는 Y 타입의 객체를 리턴한다.
  • LiveData<'Y'> switchMap (LiveData<'X'> trigger, Function<'X', LiveData<'Y'>> func)
    -> LiveData<'X'> 타입 데이터를 LiveData<'Y'> 타입 데이터로 변환합니다. func에서는 LiveData<'Y'> 타입의 객체를 리턴합니다.
  • map()
val userLiveData : LiveData<User> = MutableLiveData(User("Jone", "Doe"))
val userName : LiveData<String> = Transformations.map(userLiveData) { user ->
    user.firstName + user.lastName        
}

-> 데이터 변환을 처리하는 함수에서 String 타입의 객체를 리턴하면 됩니다. 그러면 map() 내부에서 LiveData<'String'> 타입의 객체를 리턴합니다.

  • switchMap()
val userLiveData : LiveData<User> = MutableLiveData(User("Jone", "Doe"))
val userName : LiveData<String> = Transformations.switchMap(userLiveData) { user ->
    MutableLiveData(user.firstName + user.lastName)
}

-> 데이터 변환을 처리하는 함수에서 LiveData<'String'> 타입의 객체를 리턴해야 합니다.

정적 변환 vs 동적 변환

  • map() : 정적 변환
class MainViewModel {
  val viewModelResult = Transformations.map(repository.getDataForUser() { data ->
     convertDataToMainUIModel(data)
  }
}

-> 데이터 변화가 있을 때 즉각적으로 변환만 하면 되는 경우 map()을 사용할 수 있습니다. LiveData인 source의 변경이 발생하면 데이터가 변환되어 Observer에게 이벤트가 전달됩니다.

  • switchMap() : 동적 변환
class MainViewModel {
  val repositoryResult = Transformations.switchMap(userManager.user) { user ->
     repository.getDataForUser(user)
  }
}

-> LiveData 객체를 리턴하기 때문에 repository.getDataForUser(user)에서 미래에 사용할 LiveData를 리턴하고, 준비가 끝났을 때 repository.getDataForUser(user) 내부에서 변환된 데이터를 이전에 리턴한 LiveData에 업데이트 할 수 있습니다.

단순한 작업 vs 오래 걸리는 작업

  • 시간이 오래 안걸리는 작업에서는 map으로 구현하는 것이 적절하고, 시간이 오래 걸리는 작업에서는 switchMap으로 구현하는 것이 좋다고 합니다.

라이브러리 코드

public static <X, Y> LiveData<Y> map(
        @NonNull LiveData<X> source,
        @NonNull final Function<X, Y> mapFunction) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(mapFunction.apply(x));
        }
    });
    return result;
}
  • source의 업데이트가 발생하면 mapFunction 안에서 변환된 데이터를 MediatorLiveData에 업데이트 합니다.
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
                                           @NonNull final Function<X, LiveData<Y>> func) {

    final MediatorLiveData<Y> result = new MediatorLiveData<>();

    result.addSource(trigger, new Observer<X>() {
        LiveData<Y> mSource;

        @Override
        public void onChanged(@Nullable X x) {
            LiveData<Y> newLiveData = func.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            if (mSource != null) {
                result.removeSource(mSource);
            }
            mSource = newLiveData;
            if (mSource != null) {
                result.addSource(mSource, new Observer<Y>() {
                    @Override
                    public void onChanged(@Nullable Y y) {
                        result.setValue(y);
                    }
                });
            }
        }
    });
    return result;
}
  • Source의 변경이 있을 때, func에서 리턴되는 LiveData를 새로운 Source로 MediatorLiveData에 설정하고, MediatorLiveData를 업데이트 합니다.

distinctUntilchanged()

  • 일반적으로 LiveData에 어떤 데이터가 설정되면 이 데이터를 관찰하고 있는 Observer들에게 업데이트 이벤트를 전달합니다. 이때 이전에 설정된 데이터 값과 상관없이 동일한 값이 들어와도 Observer들에게 매번 이벤트를 전달합니다.
  • 이런 부분을 해결하기 위해 도입된 라이브러리가 distinctUntilChanged()입니다.
  • 중복은 무시하고, 실제 데이터의 변경이 있을 때만 업데이트 이벤트를 전달하는 LiveData를 생성합니다.
  • 중복된 값이 설정되도 업데이트 이벤트를 발생하지 않아 Observer가 업데이트 이벤트를 수신하지 않게 됩니다.
profile
heec.choi

0개의 댓글