도서/코틀린 코루틴

플로우 생명주기 함수

안스 인민군 2024. 3. 24. 14:33

플로우에는 여러가지 생명주기 함수가 있다. (중간 함수)

하나씩 알아보자.

onEach

onEach 함수는 값을 하나씩 받는다. 따라서 onEach 함수에 delay를 넣으면 값을 받아올 때마다 delay 함수가 실행이 된다.

suspend fun main() {
    flowOf(1,2,3).onEach { delay(1000) }.collect(::println)
} 
// 
1초 후
1
1초 후
2
1초 후
3

onStart

onStart 함수는 이름 그대로 collect 최종 연산이 실행될 때 호출되는 리스너 이다.

첫 번째 원소를 요청했을 때 호출되는 것을 확인할 수 있다.

suspend fun main() {
    flowOf(1, 2, 3).onStart { println("start") }.collect(::println)
}
//
start
1
2
3

onStart 에서도 값을 방출할 수 있다. 이 때 원소들은 onStart 아래로 호출하게 된다.

suspend fun main() {
    flowOf(1, 2, 3).onStart { emit(0) }.collect(::println)
}
//
0
1
2
3

onCompletion

onCompletion함수는 이름 그대로 플로우가 완료되었을 때 호출되는 리스너 이다.

suspend fun main() {
    flowOf(1, 2, 3).onCompletion { println("finish") }.collect(::println)
}
1
2
3
finish

실제 Android 개발에서 사용할 때 progress 를 그리거나 가리기 위해 onStart 와 onCompletion 함수를 사용할 수 있다.

onEmpty

onEmpty 함수는 플로우에 예기치 못한 상황으로 값을 방출하기전에 플로우가 완료될 때 호출되는 리스너 이다.

suspend fun main() {
    emptyFlow<Int>().onEmpty { println("empty value") }.collect(::println)
}
//
empty value

catch

플로우에서 예외를 처리하기 위해 사용하는 중간함수 이다.

suspend fun main() {
    flowOf(1, 2, 3).onCompletion {
        throw Error("error 발생")
    }.catch {
        println(it.message)
    }.collect(::println)
}
//
1
2
3
error 발생

catch 메서드는 예외를 잡아 전파하는 걸 멈춘다. 이전 단계는 멈춘 상태지만 catch 에는 값을 여전히 방출할 수 있다.

또한 catch 함수를 사용할 때 주의해야하는 점이, 윗 부분에서 발생 된 예외만 잡을 수 있다.

suspend fun main() {
    flowOf(1, 2, 3).catch {
        println(it.message)
    }.onStart {
        throw Error("error 발생")
    }.onCompletion {
        println("finish")
    }.collect(::println)
}
//
finish
Exception in thread "main" java.lang.Error: error 발생

따라서 실제로 사용할 때 catch 함수를 중간 함수중 맨 상위에서 호출해야 한다.

잡히지 않는 예외

플로우에서 잡히지 않은 예외는 플로우를 즉시 취소하고 collect는 예외를 다시 던진다.

중간함수(coroutineScope) 가 처리하는 방식과 동일하다.

Flow 바깥에서는 try - catch 로 예외를 잡을 수 있다.

suspend fun main() {
    val flow = flow {
        emit("start flow block")
        throw Error("error 발생")
    }
    try {
        flow.collect(::println)
    } catch (e: Exception) {
        println(e.message)
    }
}
//
start flow block
Exception in thread "main" java.lang.Error: error 발생

플로우 내부에서 발생된 예외는 try - catch 를 사용으로 예외를 잡을 수가 없다.

collect 에서 예외가 발생하면 예외를 잡지못하므로, collect 연산을 옮기며 해결할 수 있다.

suspend fun main() {
    val flow = flow {
        emit(1)
        emit(2)

    }
    flow.onStart {
        println("start")
    }.onEach {
        println(it)
    }.onEach {
        throw Error("error 발생")
    }.catch {
        println(it.message)
    }.collect()
}
//
start
1
error 발생

기존 collect 에서 수행될 코드를 onEach 로 옮겨 예외를 잡을 수 있다.

flowOn

flowOn 함수를 사용해 CoroutineContext 를 변경할 수 있다.

withContext 와 동일한 역할을 수행한다.

suspend fun main() {
    val flow = flow {
        emit(1)
    }

    withContext(CoroutineName("test1")) {
        flow.onEach {
            println(currentCoroutineContext()[CoroutineName])
        }.flowOn(
            CoroutineName("test2")
        ).collect {
            println(currentCoroutineContext()[CoroutineName])
        }
    }
}
//
CoroutineName(test2)
CoroutineName(test1)

launchIn

launchIn 함수로 collect 를 매핑하면 다른 코루틴에서 해당 플로우를 처리할 수 있다.

public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
    collect() // tail-call
}

suspend fun main() {
    coroutineScope {
        flowOf(1, 2).onStart {
            println(0)
        }.onEach {
            println(it)
        }.launchIn(this)
    }
}
//
0
1
2