Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발자이야기

android AAC LiveData - switchMap, map 본문

android

android AAC LiveData - switchMap, map

개발자가되고싶어 2021. 5. 28. 00:24
AAC(Android Architecture Components)

 

우선 글을 시작하기에 앞서 AAC ViewModel은 mvvm의 vm을 말하는것은 아닙니다. AAC ViewModel 없이도 충분히 mvvm 디자인패턴 설계를 할 수 있습니다.

AAC의 ViewModel은 프래그먼트 간의 데이터 공유, 수명주기 관리를 편하게 해주는 라이브러리 입니다.

자세한 내용은 https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko

해당 글은 LiveData에 대한 설명을 할 것이지만 ViewModel도 같이 사용하였기에 위의 글 남깁니다.

 

 

AAC 에는 아래와 같은 구성요소가 있다.

LiveData, ViewModel, LifeCycles, Room, Paging

 

해당 글에서는 LiveData 의 사용법을 다뤄 볼까 한다.

 

ViewModel

 

아래의 예제는 Repository 디자인 패턴을 간단히 넣은 예제이며 Repository 의 목적은 webservice, database 의 분리 입니다.

해당 예제는 Repository 디자인 패턴을 제대로 사용했다고 볼 수 없습니다. webService (retrofit), database (room) 등을 사용하지 않았기 때문입니다. 단순 설명을 위해 넣은는 점 양해 부탁드립니다.

class MainViewModel(private val repository: MainRepository) : ViewModel() {
    private val _count: MutableLiveData<Int> = MutableLiveData()
    private val _mathDouble: LiveData<Int> = Transformations.switchMap(_count) {
        repository.mathDouble(it)
    }
    private val _mathTriple: LiveData<Int> = Transformations.map(_count) {
        it*3
    }

    val count: LiveData<Int>
        get() = _count

    val mathDouble: LiveData<Int>
        get() = _mathDouble

    val mathTriple: LiveData<Int>
        get() = _mathTriple

    fun countUp() {
        _count.value = repository.countUp(_count.value ?: 0)
    }

    fun countDown() {
        _count.value = repository.countDown(_count.value ?: 0)
    }
}

우선 화면과 함께 viewModel 을 살펴보자.

 

UP 버튼과 DOWN 버튼을 누름 으로써 _count LiveData에 값을 set 해준다.

set 해주어 발행된 값을 switchMap 과 map 으로 _count 의 이벤트롤 받아 2배, 3배 증가시켜 출력해 주는 간단한 예제이다.

 

Transformations

switchMap, map

우선 위의 메소드를 간단히 살펴보려고 한다.

 

switchMap
public static <X, Y> LiveData<Y> switchMap(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, LiveData<Y>> switchMapFunction)

먼저 switchMap의 두개의 파라미터를 확인해보자

첫번째로 받고 있는 Livedata<x> source 를 보자 

아래의 소스에서 알수 있듯이 _count 를 첫번째 인자로 넘겨주고 있다. (_count  viewModel 에 있는 LiveData이다

이로 인해 _count 를 관찰할수 있게 되었다.

Transformations.switchMap(_count) {
    repository.mathDouble(it)
}

 

두번째로 받고있는 Function<X, LiveData<Y>> switchMapFunction 을 살펴보자. 해당 파라미터는 함수를 파라미터로 받고 있는 고차 함수이다.

위의 소스에서 보게 되면 람다식으로 repository.mathDouble(it) repository 함수를 호출 하도록 구현해 놓은것을 알 수 있다. repository.mathDouble(it) 함수는 최종적으로 LiveData를 return 해주는 함수이다.

 

public static <X, Y> LiveData<Y> switchMap(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, LiveData<Y>> switchMapFunction) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = switchMapFunction.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;
}	

switchMap 메소드의 전체 코드이다.

중간 과정의 코드는 건너뛰고 마지막 return 값을 보면 MediatorLivedata 를 return 하는것을 알 수 있다. (이로 인해 LiveData 변경이 필요 할 경우 유용하다.)

중간 과정의 코드를 살펴보면 

LiveData<Y> newLiveData = switchMapFunction.apply(x);

switchMapFunction.apply(x) 를 호출 해 줌으로써 

Transformations.switchMap(_count) {
    repository.mathDouble(it)
}

repository.mathDouble(it) 를 담고있는 switchMapFunction 구현부가 호출 되어 LiveData를 return 해 줌으로써 해당 코드 위의 newLiveData 객체에 할당 된다. 

이렇게 새로운 LiveData를 받아와 내부적으로 셋팅되었으니 이제 LiveData에 setValue를 해주면 된다.

(설명이 길어 난잡해지는것 같다..)

 

map
public static <X, Y> LiveData<Y> map(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, Y> mapFunction)

 

map 에서는 switchMap과 같이 첫번째 파라미터로 받고 있는 LiveData의 이벤트를 감지한다.

 

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;
}

map 의 전체 코드이다. 첫번째 파라미터는 switchMap과 같은 역할을 하므로 생략한다.

두번째 파라미터를 살펴보자. 두번째 파라미터도 switchMap과 같은 고차함수의 파라미터이다.

final Function<X, LiveData<Y>> switchMapFunction
final Function<X, Y> mapFunction

switchMap 의 파라미터와 나란히 비교해보자. 보면 알수있듯이 switchMap은 Livedata<Y> 타입과 Map은 Y 타입으로 서로 다르다. 

Transformations.map(_count) {
    it*3
}

이로 인해 map 에서는 이벤트를 수신한 value 를 mapFunction 고차함수의 구현 부로 넘겨줌 으로써 it*3 이 된 값이 return 되어 setValue 된다. 이벤트로 전달 된 값을 가공 해 사용할때 유용하다. (이 설명이 좋은 설명인지 모르겠다..)

 

Repository

 

처음 설명 드린 대로 repository 내에 어떤 web 통신이나 database에 관련된 로직이 없습니다. repository 패턴을 적용할 필요가 없었으나 단순 예시 입니다.

class MainRepository {

    private val mathDouble: MutableLiveData<Int> = MutableLiveData()

    fun countUp(count: Int): Int {
        return count + 1
    }

    fun countDown(count: Int): Int {
        return count - 1
    }

    fun mathDouble(count: Int): LiveData<Int> {
        mathDouble.value = count*2
        return mathDouble
    }
}

repository 를 살펴보면 mathDouble MutableLiveData가 있다.

UP 또는 DOWN 버튼을 누르기 전까진 해당 LiveData는 어떤 이벤트도 발행하지 않는 lazy한 LiveData 이다. 

 

 

switchMap 의 필요성

간단히 아래의 코드를 한번 보자.

이게 올바른 예는 아닐것이다. 심플한 하나의 예이니 이렇구나 라고만 생각하시면 될것 같습니다.

private var _mathDouble: LiveData<Int> = MutableLiveData()

viewModel 에서 위와같이 LiveData를 만들어놓았다고 가정해 보자

view 에서는 viewModel 에 있는 위의 _mathDouble LiveData를 관찰하고 있는 상태이다.

여기서 UP 버튼을 눌렀을때를 생각해보자.

UP 버튼의 이벤트로 인해 아래의 함수가 호출되었다.

fun mathDouble(count: Int) {
    _mathDouble = repository.mathDouble(count)
}

그리고 repository.mathDobule 함수가 호출 될 것이다.

class MainRepository {

    private val mathDouble: MutableLiveData<Int> = MutableLiveData()

    fun mathDouble(count: Int): LiveData<Int> {
      mathDouble.value = count*2
      return mathDouble
    }
}

 

위와같이 repository 에서 mathDobule() 함수를 통해 mathDouble LiveData를 return 해 주어도 viewModel 에서의 _mathDouble과 repository 에서의 mathDouble LiveData는 주소값 부터 다른 LiveData이기 때문에 view 입장에서는 감지를 할 수 없게된다. 이럴때 switchMap 을 사용하면 되는 것이다.

 

소스는 여기에 https://github.com/boidmy/AACMvvm

 

해당 글은 공부하며 작성한 글입니다. 개선 할 점이 많으며 설명도 부족합니다. 피드백은 환영하며 읽어주셔서 감사합니다.

'android' 카테고리의 다른 글

android Dagger  (0) 2021.06.09
mvvm + dagger  (0) 2021.06.07
dagger @binds 어노테이션  (1) 2021.05.23
의존성주입(DI) android dagger  (0) 2021.05.22
android recyclerview diffutil  (0) 2021.05.14