본문 바로가기

Kotlin_study/CodeLab

[Codelab] Coroutine에 대해 이해하기 2(with LiveData, Flow)

목차
1. 공부 이유

2. Coroutine이란?

3. LiveData와 코루틴 사용

4. Flow와 코루틴 사용

5. 후기, 이후 계획

6. 참고문헌

 

1. 공부 이유

이전 게시글에 이어서 안드로이드에서 코루틴을 활용하는 방법에 대해 더 깊게 이해하고자 한다.


2. Coroutine 이란?

메인 스레드를 blocking 하면 안되기 때문에 백그라운드 스레드에서 네트워크, DB 작업을 진행해야 하는 것으로 알고 있었다. 그러다보니 처음 안드로이드를 배울 때 했던 FLO 클론코딩에서는 코루틴을 사용하지 않고 했는데, 그렇다면 문제가 생겼어야 했던 것이 아닌가? 라는 생각이 들었다. Room은 dao에서 suspend function을 사용하고 Retrofit은 자동으로 suspend function을 만듦으로써 메인 스레드가 blocking이 되지 않는게 보장되기 때문에 IO가 아니라 Main 스레드를 사용하는 것이다. 그래서 FLO 클론코딩에서 Room을 이용해서 데이터를 읽고 쓰고, Retrofit을 사용해 네트워크 작업을 했기 때문에 문제가 발생하지 않았던 것이다. Codelab에서 이를 다음과 같이 설명한다. 'Since both Room and Retrofit provide main-safe suspending functions, it's safe to orchestrate this async work from Dispatchers.Main.' 


3. LiveData와 코루틴 사용

Flow와 코루틴을 사용해보기 전에 LiveData와 코루틴을 어떻게 함께 사용하는지 알아보고자 한다. LiveData를 사용할 때 값을 비동기적으로 계산할 필요가 있을 때(ex. 로컬에서 사용자의 값을 불러온다던가, 네트워크 요청을 통해 현재 token이 유효한지 판단한다던가) liveData의 빌더 함수를 사용하여, suspend function을 호출하고 이 결과를 LiveData 객체로 제공할 수 있다고 한다. emit(), emitSource()를 사용하면 된다. emitSource()를 사용하면 LiveData에서 여러 값을 내보낼 수 있다. 

val user : LiveData<User> = liveData {
	val data = database.loadUser() // DB에서 유저 값을 불러오는 suspend function
    	emit(data) // 불러온 값(data)를 내보냄
}

4. Flow와 코루틴 사용

Flow는 터미널 연산자가 적용될 때마다, 포함하는 코루틴이 취소될 때까지, 마지막 값이 완전히 처리되고 다른 값이 요청되었을 때 실행된다고 한다. Flow에서 터미널 연산자는 collect가 있고, kotlinx-coroutines에서 toList, first, single과 같은 연산자를 터미널 연산자를 제공한다고 한다.

 

Flow로 return type이 지정되면 Room은 변경사항을 자동으로 관찰해서 새 값을 내보낸다고 한다. 이를 활용하면 Room의 데이터에 변경 사항이 생겼을 때(추가, 삭제, 변경) UI가 업데이트 될 것이다. 

//getPlantsFlow() 를 통해 Room에서 Flow 형태로 Plant 리스트를 가져옴
val plants : Flow<List<Plant>>
	get() = plantDao.getPlantsFlow() 
    	//combine을 통해 plants와 customSortFlow 두개의 Flow를 결합
            .combine(customSortFlow) { plants, sortOrder ->
            	plants.applySort(sortOrder)
            }
            .flowOn(defaultDispatcher)
            .conflate()

위의 코드는 두개의 Flow를 결합하는 코드이다. 두 개의 Flow 모두에서 결과를 받았을 때 sortOrder를 적용해 plants가 정렬된다. (flowOn은 flow가 실행되는 스레드를 제어하고, conflate는 마지막 결과만 저장하도록 flowOn의 버퍼를 덮어쓰는 역할을 한다고 하는데 이에 대한 이해는 아직 부족해 추후에 보충하고자 한다.)

 StateFlow는 Flow 값 홀더인데, 마지막 값만 보유하는데 초기 설정 값으로 생성되고 collect를 하지 않아도 상태가 유지되며 collect 될 때(터미널 연산자가 적용될 때)만 실행되는 Flow와는 차이가 있다. 


5. 후기, 이후 계획

지금까지 학습한 내용을 바탕으로 정리해보자면 Flow를 사용하면 로컬 데이터베이스에 저장된 내용에 변경이 생겼을 때 이를 UI에 별도의 작업 없이 반영될 수 있다는 점, 요청할 때마다 값이 생성되고 Flow의 소비자가 취소되면 전체 Flow가 취소되기 때문에 유출이 생기지 않는다는 점을 확인할 수 있었는데, 솔직히 첫번째 점 외에는 제대로 이해하지 못한 것 같다.뭔가 좋아보이기는 하는데 이해하지 못하고 다른 사람들이 쓴 코드를 비슷하게 바꿔서 작성하기 시작하면 내 코드를 내가 이해하지 못하는 상황이 생길 것 같아서 Flow 사용은 이에 대한 이해를 마치기 전까지는 Room에서 불러올 때 말고는 사용하지 못할 것 같다.

 

Flow에 대해 이해하기 전에 뭘 먼저 학습하면 이해도를 높일 수 있는지 파악한 후 이에 대한 학습을 하고나서 더 살펴보려고 한다. 

실습한 내용은 Github 링크를 통해 확인하실 수 있습니다.

참고 문헌

Learn advanced coroutines with Kotlin Flow and LiveData (android.com)

Use Kotlin Coroutines in your Android App