코틀린 스터디

플로우 생명 주기 함수

막이86 2024. 2. 2. 11:20
728x90

코틀린 코루틴을 요약한 내용입니다.

  • 플로우는 요청이 한쪽 방향으로 흐르고 요청에 의해 생성된 값이 다른 방향으로 흐르는 파이프라 생각할 수 있음
  • 플로우가 완료되거나 예외가 발생했을 때, 정보가 전달되어 중간 단계가 종료
  • 모든 정보가 플로우로 전달되므로 값, 예외 및 다른 특정 이벤트를 감지할 수 있음

onEach

  • onEach 람다식은 중단 함수
  • 원소는 순서대로 처리
  • delay를 넣으면 각각의 값이 흐를 때마다 지연
  • suspend fun main() { flowOf(1, 2, 3, 4) .onEach { print(it) } .collect() }

onStart

  • 최종 연산이 호출될 때 플로우가 시작되는 경우에 호출
  • 첫 번째 원소를 요청했을 때 호출
  • suspend fun main() { flowOf(1, 2) .onEach { delay(1000) } .onStart { println("Befor") } .collect { println(it) } }
  • onStart에서도 원소를 내보낼 수 있음
  • suspend fun main() { flowOf(1, 2) .onEach { delay(1000) } .onStart { emit(0) } .collect { println(it) } }

onCompletion

  • 플로우 빌더가 끝났을때 onCompletion 메서드를 호출
  • suspend fun main() = coroutinScope { val job = launch { flowOf(1, 2) .onEach { delay(1000) } .onCompletion { println("Completed") } .collect { println(it) } } delay(1100) job.cancel() }

onEmpty

  • 플로우는 예기치 않은 이벤트가 발생하면 값을 내보내기 전에 완료될 수 있음
  • onEmpty 함수는 원소를 내보내기 전에 플로루가 완료되면 실행
  • onEmpty는 기본값을 내보내기 위한 목적으로 사용될 수 있음
  • suspend fun main() = coroutineScope { flow<List<Int>> { delay(1000) } .onEmpty { emit(emptyList()) } .collect { println(it) } }

catch

  • 플로우를 만들거나 처리하는 도중에 예외가 발생할 수 있음
  • catch 메서드는 예외를 인자로 받고 정리를 위한 연산을 수행
  • class MyError: Throwable("My error") val flow = flow { emit(1) emit(2) throw MyError() } suspend fun main(): Unit { flow.onEach { println("Got $it") } .catch { println("Caught $it") } .collect { println("Collected $it") } }
  • catch 메서드는 예외를 잡아 전파되는 걸 멈춤
    • catch는 새로운 값을 내보낼 수 있어 남은 플로우를 지속 할 수 있음
    val flow = flow {
    	emit("Message1")
    	throw MyError()
    }
    
    suspend fun main(): Unit {
    	flow.catch { println("Caught $it") }
    		.collect { println("Collected $it") }
    }
    

잡히지 않는 예외

  • 플로우에서 잡히지 않는 예외는 플로우를 즉시 취소
    • collect는 예외를 다시 던짐
  • 플로우 바깥에서 전통적인 try-catch 블록을 사용해서 예외를 잡을 수 있음
  • val flow = flow { emit("Message1") throw MyError() } suspend fun main(): Unit { try { flow.collect { println("Collected $it") } } catch (e: MyError) { println("Caught") } }
  • catch를 사용하는 건 최종 연산에서 발생한 예외를 처리하는데 전혀 도움이 되지 않음
    • collect에서 예외가 발생하면 예외를 잡지 못하여 블록 밖으로 전달됨
    val flow = flow {
    	emit("Message1")
    	emit("Message2")
    }
    
    suspend fun main(): Unit {
    	flow
    		.onStart { println("Before") }
    		.catch { println("Cauth $it") }
    		.collect { throw MyError() }
    }
    
  • collect 연산을 onEach로 옴기고 catch 이전에 두는 방법이 자주 사용됨
    • collect가 예외를 발생시킬 여지가 있다면 onEach로 이동
    val flow = flow {
    	emit("Message1")
    	emit("Message2")
    }
    
    suspend fun main(): Unit {
    	flow
    		.onStart { println("Before") }
    		.onEach { throw MyError() }
    		.catch { println("Cauth $it") }
    		.collect()
    }
    

flowOn

  • 플로우 빌더의 인자로 사용되는 람다식은 모두 중단 함수
  • 중단 함수는 컨텍스트가 필요하며 부모와 관계를 유지
  • 플로우의 함수들은 collect가 호출된 곳의 컨텍스트를 얻어 옴
  • fun usersFlow(): Flow<String> = flow { repeat(2) { val ctx = currentCoroutineContext() val name = ctx[CoroutineName]?.name emit("User$is in $name") } } suspend fun main() { val users = usersFlow() withContext(CoroutineName("Name1")) { users.collect { println(it) } } withContext(CoroutinName("Name2")) { users.collect { println(it) } } }
  • flowOn 함수로 컨텍스트를 변경 가능
  • suspend fun present(place: String, message: String) { val ctx = currentCoroutineContext() val name = ctx[CoroutineName]?.name emit("[$name] $message on $place") } fun messageFlow(): Flow<String> = flow { present("flow builder", "Message") emit("Message") } suspend fun main() { val users = messageFlow() withContext(CoroutineName("Name1")) { users .flowOn(CoroutineName("Name3")) .onEach { present("onEach", it) } .flowOn(CoroutineName("Name2")) .collect { present("collect", it) } } }

launchIn

  • collect는 플로우가 완료될 때까지 코루틴을 중단하는 중단 연산
  • launch 빌더로 collect를 래핑하면 플로우를 다른 코루틴에서 처리할 수 있음
  • launchIn을 사용하면 인자로 스코프를 받아 collect를 새로운 코루틴에서 시작할 수 있음
  • fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch { collect() }
  • 별도의 코루틴에서 플로우를 시작하기 위해 lauchIn을 주로 사용
  • suspend fun main(): Unit = coroutineScope { flowOf("User1", "User2") .onStart { println("Users:") } .onEach { println(it) } .launchIn(this) }

요약

  • 플로우가 시작될 때, 닫힐 때, 또는 각 원소를 탐색할 때 플로우에 작업을 추가할 수 있음
  • 예외를 잡는 방법과 새로운 코루틴에서 플로우를 시작하는 방법 확인
728x90

'코틀린 스터디' 카테고리의 다른 글

플로우 만들기  (1) 2024.02.02
플로우의 실제 구현  (0) 2024.02.02
플로우란 무엇인가?  (1) 2024.01.04
핫 데이터 소스와 콜드 데이터 소스  (0) 2024.01.04
셀렉트  (0) 2024.01.04