티라미수 코딩생활

[Kotlin] Kotlin Flows in practice 보면서 이해 해보기 (1) 본문

Programming/Android

[Kotlin] Kotlin Flows in practice 보면서 이해 해보기 (1)

Aosta 2022. 11. 3. 17:26

Android 와 앱 개발을 하면서 가장 많이 하게 되는 것은 Data를 UI에 보여주는 것이다.

View 가 시작될 때 데이터를 DB 혹은 RestApi 를 통해서 요청(request)하게 되고, 응답(response) 받은 데이터를 UI에 보여주는 과정이다. 이 일련의  과정(request-response)이 최근으로 올 수록  Observer패턴을 활용하는 반응형(Reactive)이 대세가 되어가는 것 같다.

 

RxJava 를 먼저 공부하고 Coroutine 을 공부하기 시작하면서 양쪽에서 강조하는 것이 같다고 느꼈다.

데이터의 변화를 감지(Observe) 하고 자동으로 View까지 전달하게 하는 파이프 라인같은 앱 내에서 인프라 구축하는 것이다. 파이프 라인은 물이 한 방향으로 흐르는 것처럼, 데이터를 한 방향으로 흐르게 하는 것이 오류 발생을 줄이고 관리하기가 쉽기 때문이라고 한다. 그리고 Kotlin에서 적극적으로 지원하는게 바로 이 Flow 다!

 

Creating Flows

Flow는 데이터 생산자와 소비자로 이루어져 있는데, Flow는 Retrofir, Room 처럼 우리가 자주 쓰는 라이브러리에서 내장되어 있어서 어지간 해서는 데이터를 직접 만들어 생산하는 일은 많지 않다. 그럼에도 만약 직접 만들어 사용하게 되는 경우가 있기 때문에 영상에서 나오는 예시를 정리해봤다.

영상에서 '수시로 앱에서 온 메세지를 확인' 하는 상황을 예로 들었다.

 

class UserMessagesDataSource (
    private val messagesApi: MessagesApi,
    private val refreshIntervalMs: Long = 5000
) {
    val latestMessages: Flow<List<Message>> = flow {
        // Producer Block
        while(true) {
            val userMessages = messagesApi.fetchLatestMessages()
            emit(userMessages) 	     // Emit the result to the flow
            delay(refreshIntervalMs) // Suspends for some time(suspend point)
        }
    } // flow
    
    // ... //
}

Flow 빌더(flow)는 suspend block 을 파라미터로 받는다. 또한, flow 는 coroutineContext 에서 실행되기 때문에 suspend 함수를 호출 할 수 있다. 이렇게 emit 으로 방출하는 flow 빌더를 Producer Block 이라고 한다.

 

코드를 보면 while 문을 돌면서 5초마다 한번씩 최신 message 를 불러온다. 흐름을 적어보면

  1. emit() 이라는 suspend 함수를 통해서 flow 에 api 결과값을 추가한다.
  2. Collector 가 값을 받을 때까지 suspend (코루틴이 잠에 듦)
  3. delay suspend point 에서 5초간 잠들었다가 다시 while 문 실행
  4. 위 동작을 Observer 가 Collect 를 멈추고 떠날 때까지 반복 동작한다. 

 

Collecting Flows

위에서 봤던 것처럼 Producer Block 에서 방출한 데이터를 했을 때, 데이터를 받는 곳은 보통 UI Layer 이다.

 

중간연산자 (Intermediate operators)

아래의 예시 코드를 보면 여러 연산자(Operator)가 있는데, Rx와 아주아주 유사하다.

중간연산자는 Producer Block 에서 생성한 flow 이고, 기능에 따라서 데이터를 방출하는 새로운 flow 를 생성한다.

그리고 중간연산자 이후에 생성되는 flow 부터 모두 DownStream Flow 라고 한다.

 

 ★ cold flow ? flow builder 는 flow.collect 되기 전까지 실행되지 않는다고 해서 cold flow 라고 불린다.

 

Map 연산자를 보면 AAC 아키텍쳐에서 Flow 에서 가지는 강점이 나타나는데, model 을 변환 해주면서, 더 좋은 앱 계층의 추상화를 제공한다. 

Catch 연산자는 예외처리를 할 수 있다. 이 연산자는 upstream flow 발생하는 에러를 잡아내 처리할 수 있다.

val userMessages: Flow<MessageUiModel> =
    userMessagesDataSource.latestMessages
        // Upstream Flow * * *
        .map {
            userMessages ->
            userMessages.toUiModel()
        }
        .filter {
            messageUiModel ->
            messageUiModel.containsSomething()
        }
        // * * * * * * * * * *
        .catch {
            e ->
            e.printStackTrace()
            if (e is IllegalArgumentException) throw e
            else emit(emptyList())
        }

 

Comments