Flow를 보기전에 동기에서 데이터 처리하는 것을 먼저 공부해둘 필요가 있다.
fun main() {
val startTime = System.currentTimeMillis()
calculateFactorialOf(5).forEach {
printWithTimePassed(it,startTime)
}
}
// factorial of n (n!) = 1 * 2 * 3 * 4 * ... * n
private fun calculateFactorialOf(number: Int): List<BigInteger> = buildList {
var factorial = BigInteger.ONE
for (i in 1..number) {
Thread.sleep(10)
factorial = factorial.multiply(BigInteger.valueOf(i.toLong()))
add(factorial)
}
}
리스트를 만들때 buildList 를 통해 리스트를 만들수 있는데,
다음과 같이 구성하면 각요소들이 전부 계산이 완료된 후 forEach를 통해 출력한다.
그래서 실행하고 시간을 보면 다 비슷한 시간에 출력되는걸 볼 수 있다.
fun main() {
val startTime = System.currentTimeMillis()
calculateFactorialOf(5).forEach {
printWithTimePassed(it,startTime)
}
println("Ready for more work!")
}
// factorial of n (n!) = 1 * 2 * 3 * 4 * ... * n
private fun calculateFactorialOf(number: Int): Sequence<BigInteger> = sequence {
var factorial = BigInteger.ONE
for (i in 1..number) {
Thread.sleep(10)
factorial = factorial.multiply(BigInteger.valueOf(i.toLong()))
yield(factorial)
}
}
sequence 는 지연 연산을 지원하는 인터페이스이다.
Java의 Stream이랑 비슷한대, 요소들이 연산될 필요가 있을때만 계산한다.
빌드 리스트랑 출력 결과가 다른데, 각 요소별로 계산이 될때마다 출력을 한다.
그렇기 때문에 최종적으론 buildList 와 결과를 출력하는 시간이 비슷하지만, 각 요소를 출력하는 시간은 마지막 요소 5!를 제외하고 더 빠르게 출력된다.
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
launch {
calculateFactorialOf(5).collect {
printWithTimePassed(it, startTime)
}
}
println("Ready for more work!")
}
// factorial of n (n!) = 1 * 2 * 3 * 4 * ... * n
private fun calculateFactorialOf(number: Int): Flow<BigInteger> = flow {
var factorial = BigInteger.ONE
for (i in 1..number) {
delay(10)
factorial = factorial.multiply(BigInteger.valueOf(i.toLong()))
emit(factorial)
}
}.flowOn(Dispatchers.Default)
flow같은 경우도 각 요소 마다 출력해 Sequence랑 유사해 보인다.
하지만 차이점이 있는데, 비동기 작업이라는 장점이 있지만 오버헤드가 크다는 단점또한 있다.
컨텍스트 전환과 스레드 관리 비용 때문에 간단한 작업에서는 Sequence가 더 효율적일 수 있다.
하지만 데이터의 규모가 커지거나 네트워크 연결 등 비동기 작업을 할때 비동기라는 장점때문에 Flow가 더 효율적이라고 볼 수 있다.
안드로이드에서 데이터 관리라함은 대부분 서버 등 데이터 베이스에서 비동기로 자료를 받는다.
그래서 Flow를 자주 쓰게 되는데, 우선 이 flow를 만드는 메소드 부터 알아보자
suspend fun main() {
val firstFlow = flowOf<Int>(1).collect { emittedValue ->
println("firstFlow: $emittedValue")
}
val secondFlow = flowOf<Int>(1, 2, 3)
secondFlow.collect { emittedValue ->
println("secondFlow: $emittedValue")
}
listOf("A", "B", "C").asFlow().collect { emittedValue ->
println("asFlow: $emittedValue")
}
flow {
delay(2000)
emit("item emitted after 2000ms")
emitAll(secondFlow)
}.collect { emittedValue ->
println("flow{}: $emittedValue")
}
}
flowOf로 객체를 생성하고 바로 collect를 하면 관찰이 된다.
firstFlow를 사용하지 않고도 로직이 수행된다.
.collect만 달아주면 LiveData처럼 관찰 할 수 있다.
.asFlow()를 통해 Flow로 변환또한 가능한데, Array List Range등에 쓸수 있다.
기본적인 flow빌더론 그냥 단순하게 flow {} 블럭을 사용 할 수 있다.
emit을 사용해 요소을 collect에서 출력한다.
emitAll을 사용하면 다른 Flow의 요소들도 출력이 가능하다.
suspend fun main() {
val scope = CoroutineScope(EmptyCoroutineContext)
scope.launch {
intFlow()
.onCompletion { throwable ->
if (throwable is CancellationException) {
println("Flow got cancelled.")
}
}
.collect {
println("Collected $it")
if (it == 2) {
cancel()
}
}
}.join()
}
private fun intFlow() = flow {
emit(1)
emit(2)
emit(3)
}
이 함수에선 flow가 캔슬되었을때 메시지를 출력 한다.
우선 EmptyCoroutineContext로 기본 코루틴context를 넣어준다.
Job,Dispatcher등이 자동 생성되며, 부모 코루틴이 있다면 부모 코루틴의 context를 받아 사용한다.
여기선 onCompletion을 이용해서 flow가 끝났을때 작업을 처리한다.
flow에서 2를 출력하면 cancel이 되면서 에러가 나는대,
이때 어찌되었든 flow가 종료되었으니 onCompletion이 작동하고 거기서 throwable을 받아 메시지를 출력한다.
ensureActive()를 사용해 코루틴이 active상태가 아니면 취소시킬 수도 있다.
fun Flow.cancellable(): Flow
cancellable을 사용해서 각 방출에 대한 취소 상태를 확인하고 flow 콜렉터가 취소된 경우 해당 취소 원인을 던지는 flow를 반환할 수 있다.