안드로이드 코루틴 플로우 및 기본 연산

SSY·2022년 12월 15일
0

Flow

목록 보기
1/5
post-thumbnail

목차
1. 시작하며
2. 리액티브 프로그래밍과 코루틴 플로우
3. 코루틴 플로의 기본 연산자들

1. 시작하며

이전까진 코루틴의 각종 개념들에 대해서 포스팅해보았다. 혹시 보지 않은 분들은 아래 포스팅을 보고 오면 아주 도움이 될거라 생각한다.

하지만 코루틴을 공부하며 마치 동반자로 따라오는 녀석이 있다. 바로, Flow이다. 이녀석은 코루틴에 리액티브 프로그래밍을 지원하기 위한 요소이다.

2. 리액티브 프로그래밍과 코루틴 플로우

리액티브 프로그래밍이 무엇인지 알기 위해선 명령형 프로그래밍이 무엇인지 먼저 알 필요가 있다.

명령형 프로그래밍이란?
우리가 소프트웨어에 어떤 일을 시키기 위해 '명령'을 내려 실행시키게 된다. 우리가 로그를 찍든, 메소드를 호출하든, 명령을 일일이 내려줘야 한다는 뜻이다. 다음의 코드는 명령형 프로그래밍이다.

fun doSometing() = listOf(1,2,3,4,5,6,7,8,9,10)


dataBinding.button1.setOnClickListener {
    doSometing1().forEach {
        Log.i("doSomething", "$it 번째 데이터 출력")
    }
}

우리는 버튼을 눌렀을 때 1부터 10까지 로그를 찍어주고 싶다고 가정하자. 그럴때, 우리는 위와 같이 doSomthing() 메소드를 우선적으로 호출해줘야 한다. 그리고 doSomething()메소드 내부 연산이 모두 끝난 후, List객체를 반환한다. 그리고 그렇게 반환한 객체의 데이터를 사용한다.

그렇다면 리액티브 프로그래밍은? 반응하는 프로그래밍이란 뜻이다. 즉, 우리가 일일이 명령을 내려줌으로써 동작을 시켜주는 것이 아닌, '데이터 변경'에 반응하여 동작을 시켜준다는 뜻이다. 아래는 리액티브 프로그래밍의 예시이다.

fun doSometing(): Flow<Int> {
    return flow {
        repeat(10) {
            Log.i("doSomething", "$it 번째 데이터 발행")
            emit(it)
        }
    }
}

dataBinding.button2.setOnClickListener {
    CoroutineScope(Dispatchers.IO).launch {
        doSometing().collect {
            Log.i("doSomething", "$it 번째 데이터 수신")
        }
    }
}

첫 번째의 예시는 1부터 10까지의 로그를 출력해주기 위해 doSomething메소드의 연산을 모두 끝내야만 했다.

하지만, 두 번째의 예시는 doSomething메소드의 연산이 모두 끝나지 않아도 된다. 데이터가 하나하나 발행될 때마다 이를 옵저버 쪽에 알려주는 것이다.

그렇다면 위의 코드를 통해서 코루틴의 플로우는 뭔가 '데이터를 발행하는 부분'이 있고, '데이터를 수신하는 부분'이 있다는 것을 눈치했을 것이다. 맞다. 그리고 여기에 한가지 요소가 더 들어간다. 바로 '데이터를 변형해주는 중개자 부분'이다.

리액티브 프로그래밍의 구성 요소
1. 데이터 발행 부분
2. 데이터 수신 부분
3. 데이터를 변형해주는 중개자 부분

우리가 위에서 다루었던 코드 부분, Flow를 반환하는 메소드(=doSomething)은 바로 데이터를 발행하는 'Producer'이다. 그리고 이러한 데이터를 수신하는 부분은(=collect)은 Consumer이다.

그리고 우리는 이제 중간에서 데이터를 변형하는 부분에 대해 알아볼까 한다. 이 부분이 바로, 위 그림의 Intermediary에 해당한다.

3. 코루틴 플로우의 기본 연산자들

3.1. map

이 연산자는 플로우를 원하는 값으로 변경시킨 후, 최종적으로 반환시킬 수 있는 연산자이다.

dataBinding.button2.setOnClickListener {
    CoroutineScope(Dispatchers.IO).launch {
        (1..10).asFlow()
            .map {
                "map을 통해 변형됨 $it"
            }
            .collect {
            Log.i("doSomething", "결과값 : $it")
        }
    }
}

3.2. transform

transform연산자도 위의 map연산자처럼 중간에 값을 변경시켜서 최종적으로 방출시켜줄 수 있는 연산자이다. 하지만 한 가지 다른게 있는데, 그것은 데이터 방출을 여러개 할 수 있다는 점이다.

dataBinding.button2.setOnClickListener {
    CoroutineScope(Dispatchers.IO).launch {
        (1..10).asFlow()
            .transform {
                emit("map을 통해 변형됨1 $it")
                emit("map을 통해 변형됨2 $it")
            }
            .collect {
            Log.i("doSomething", "결과값 : $it")
        }
    }
}

위 코드를 보면 알겠지만, transform연산자도 map연산자처럼 중간에 값을 변경한 후, emit해주고 있다. 하지만 이를 여러개를 해주는걸 볼 수 있다.

3.2. reduce

이는 Int형 플로우의 값을 누적해서 방출시켜주는 연산자이다.

dataBinding.button2.setOnClickListener {
    CoroutineScope(Dispatchers.IO).launch {
        val result = (1..10).asFlow()
            .reduce { accumulator, value -> accumulator + value }
        Log.i("doSomething", "결과값 : $result")
    }
}

위 reduce연산자를 보면 두 가지의 변수가 있다. 하나는 accumulator, 다른 하나는 value이다. 우선, value의 경우는 순차적인 값을 의미하며 (1 ~ 10) accumulator는 이들을 누적한 값을 의미한다. 그리고 위에서 accumulator + value를 해주고 있기에, 이 값들이 계속 누적되는 것이다.

3.4. fold

이 연산자도 위의 reduce연산자와 거의 똑같다고 보면 된다. Int형 값들을 누적해주는 연산자까지는 공통된 부분이지만, recuce와는 다르게 초기값을 설정할 수 있다.

dataBinding.button2.setOnClickListener {
    CoroutineScope(Dispatchers.IO).launch {
        val result = (1..9).asFlow()
            .fold(100) { accumulator, value -> accumulator + value }
        Log.i("doSomething", "결과값 : $result")
    }
}

초기값을 100으로 둔다. 그리고 1부터 10까지의 총 합을 구한 결과값이다.

3.5. zip

이 연산자는 여러개의 플로우를 합성하는 연산자이다.

dataBinding.button2.setOnClickListener {
    CoroutineScope(Dispatchers.IO).launch {
        val numFlow = (1..3).asFlow() // numbers 1..3
        val strFlow = flowOf("one", "two", "three") // strings
        numFlow.zip(strFlow) { a, b -> "$a -> $b" } // compose a single string
            .collect { println(it) } // collect and print
    }
}

위의 코드는 두 개의 플로우가 있다. 하나는 Int를 방출하는 Flow. 다른 하나는 str을 방출하는 Flow이다. 그리고 이 두개의 플로우를 합성할 수 있다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글