결론 부터 얘기하자면
해당 글에서 얻을 내용이 많지 않고, 심지어 해결을 하지 못했습니다상황을 해결해나가는 과정을 적고자 합니다.
해결이 되지 않은 내용은
다음과 같이 상단에 해결 못했다고 안내글을 남기도록 하겠습니다.

TextField와 Button 그리고 결과내용이 보일 Text가 있는 화면이 있다
여기서 Button을 누르면 remote API를 호출하고 검색 결과를 가져오고 화면에 출력하고 있는 list에 addAll을 한다
여기서 remote API 호출을 할 때 retrofit2을 사용했고
내부call.enqueue{}로 수행하는데 내부적으로 비동기 처리를 하고있다.
kotlin code
val list = mutableListOf<Document>()
...
// button (onclick)
onSearch = {
val results = searchImage(query = query)
list.addAll(results)
Log.d("TAG", "onCreate: list $list")
}
그러면 기대하는 상황
D/REMOTE DATA SOURCE: API 응답 성공
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2016-03-13T20:12:36.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/yangdreamcommunity/BmPG/289, height=200.0, imageURL=https://t2.search.daumcdn.net/argon/0x200_85_hr/7cTaHuAj0uD, thumbnailURL=null, width=265.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2004-04-02T22:45:16.000+09:00, displaySitename=Daum카페, docURL=http://cafe.daum.net/luxury206/PsUB/30, height=350.0, imageURL=http://cafe159.daum.net/_c21_/pds_down_hdn?grpid=nnXB&fldid=Pl8Q&dataid=21&grpcode=luxury206&realfile=yk37.jpg, thumbnailURL=null, width=467.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2009-04-10T17:27:01.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/wolnam16/VFO/337, height=285.0, imageURL=http://cfile293.uf.daum.net/image/1175340D49DF05F3D6617D, thumbnailURL=null, width=401.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2023-04-10T18:59:17.000+09:00, displaySitename=Daum카페
...
D/TAG: onCreate: list [Document(collection=cafe, datetime=2016-03-13T20:12:36.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/yangdreamcommunity/BmPG/289, height=200.0, imageURL=https://t2.search.daumcdn.net/argon/0x200_85_hr/7cTaHuAj0uD, thumbnailURL=null, width=265.0), Document(collection=cafe, datetime=2004-04-02T22:45:16.000+09:00, displaySitename=Daum카페, docURL=http://cafe.daum.net/luxury206/PsUB/30, height=350.0, imageURL=http://cafe159.daum.net/_c21_/pds_down_hdn?grpid=nnXB&fldid=Pl8Q&dataid=21&grpcode=luxury206&realfile=yk37.jpg, thumbnailURL=null, width=467.0), Document(collection=cafe, datetime=2009-04-10T17:27:01.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/wolnam16/VFO/337, height=285.0, imageURL=http://cfile293.uf.daum.net/image/1175340D49DF05F3D6617D, thumbnailURL=null, width=401.0), Document(collection=cafe, datetime=2023-04-10T18:59:17.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/woori032/LzMX/9238, height=316.0, imageURL=https://t1.da ...
하지만 나온 결과
D/TAG: onCreate: list []
D/REMOTE DATA SOURCE: API 응답 성공
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2016-03-13T20:12:36.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/yangdreamcommunity/BmPG/289, height=200.0, imageURL=https://t2.search.daumcdn.net/argon/0x200_85_hr/7cTaHuAj0uD, thumbnailURL=null, width=265.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2004-04-02T22:45:16.000+09:00, displaySitename=Daum카페, docURL=http://cafe.daum.net/luxury206/PsUB/30, height=350.0, imageURL=http://cafe159.daum.net/_c21_/pds_down_hdn?grpid=nnXB&fldid=Pl8Q&dataid=21&grpcode=luxury206&realfile=yk37.jpg, thumbnailURL=null, width=467.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2009-04-10T17:27:01.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/wolnam16/VFO/337, height=285.0, imageURL=http://cfile293.uf.daum.net/image/1175340D49DF05F3D6617D, thumbnailURL=null, width=401.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2023-04-10T18:59:17.000+09:00, displaySitename=Daum카페
...
Flutter에서 Dart로 했다면..
searchImage()가 자체 비동기 처리를 하고 있을 때
final list = <Document>[];
...
// button (onclick)
onSearch () async {
val results = await searchImage(query)
list.addAll(results)
log("TAG", "onCreate: list $list")
}
이렇게 위처럼 await 키워드 추가하면 끝인데..
searchImage 액션을 기다리고 list.add 하면 list에 결과가 잘 담길텐데..
kotlin에서는 그러면 어떻게 할까?
[kotlin 공식문서] 비동기 프로그래밍
https://kotlinlang.org/docs/async-programming.html
오래걸리는 함수로 인해 UI가 block된다고 가정
fun postItem(item: Item) {
val token = 오래걸리는첫번째함수()
val post = 두번째함수(token, item)
세번째함수(post)
}
fun 오래걸리는 함수(): Token {
...
return token
}
단점은?
공식문서에는 스레드 작성법이 따로 안 나와있지만Thread 작성법이 궁금하시다면 이 블로그를 참고
하나의 함수를 매개변수로 전달하고프로세스가 완료되면 함수가 호출되도록 하는 것
fun postItem(item: Item) {
오래걸리는첫번째함수 { token ->
두번째함수(token, item) { post ->
세번째함수(post)
}
}
}
fun 오래걸리는첫번째함수(callback: (Token) -> Unit) {
...
}
javascript와 같은 이벤트 루프 아키텍처에서 매우 일반적이지만Promise 또는 Reactive Extensions와 같은 다른 접근 방식을 사용합니다
Threading, CallBack, Future, promises, .. 등 비동기 작업에 다양한 접근 방식이 있지만 Kotlin에선 코루틴을 사용한다
[kotlin 공식문서] Coroutines
official Library
: kotlinx.coroutinesJetBrains에서 개발한 풍부한 코루틴용 라이브러리입니다
나머지 코드와 동시에 작동하는 코드 블록을 실행해야 한다는 점에서
개념적으로 스레드와 유사합니다
: 코루틴은 가벼운 스레드로 생각할 수 있지만 스레드와 다른 중요한 차이점이 많음
: 코루틴은 특정 스레드에 바인딩 되지 않음, 한 스레드에서 실행을 일시중단하고 다른 스레드에서 다시 시작할 수 있음
구조화된 동시성(Structured concurrency)
code:
fun main() = runBlocking {
launch { // new coroutine
delay(timeMillis = 3000L) // non-blocking delay for 3 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello")
}
result:
Hello
(3초 뒤)
World!
코드 분석:
runBlocking{} 일반 코드와 코투틴 코드를 연결해주는 coroutine builder
: 내부의 모든 코루틴이 실행을 완료할 때 까지 호출 기간 동안 이를 실행하고 있는 main thread가 blocked(차단됨)을 의미
: 스레드는 값 비싼 리소스이고 스레드를 차단하는 것은 비효율적이며 바람직하지 않아서 실제 코드 내에선 거의 사용되지 않고, runBlocking은 자주 보임
launch{} coroutine builder
: 새로운 코루틴을 시작
: 단독으로 못씀
(꼭 연결해주는 runBlocking{}과 같은 coroutine builder 안에 사용 해야 됨)
delay(timeMillis = 3000L) special suspending function
: 특정시간 코루틴을 일시 중단
함수 추출(extract function) 리팩토링을 하면
suspending function이 생김
fun main() = runBlocking {
launch { doWorld() }
println("Hello")
}
// suspending function 정지 함수
suspend fun doWorld() {
delay(1000L)
println("World!")
}
delay와 같은 suspending 함수를 사용할 수 있음coroutineScope 빌더를 사용해, 범위를 선언runBlocking과 마찬가지로 body와 모든 자식이 완료될 때 까지 기다림runBlocking vs coroutineScope
runBlocking{} 대기를 위해 현재 스레드 차단
: 일반 함수
coroutineScope{} 대기를 위해 일시 중단하고
다른 용도를 위해 기본 스레드를 해제
: 정지 함수
두개의 코루틴을 실행하면?
code:
fun main() {
runBlocking {
doWorld()
println("Done")
}
}
suspend fun doWorld() {
coroutineScope {
launch {
delay(2000L)
println("World 2")
}
launch {
delay(1000L)
println("World 1")
}
println("Hello")
}
}
result:
Hello
World 1
World 2
Done
코드 분석:
coroutineScope{} 내 일반 코드(print)와 코루틴 코드(launch) 있음
launch{} 2개의 코드블럭과 println("Hello") 동시에 실행
시작부터 1초 뒤 "World1" 프린트 되고, 시작부터 2초 뒤 "World2" 프린트
coroutineScope는 모든 함수 처리가 완료 된 후에 반환됨
: 그래서 2초 뒤 "World2" 프린트 후 doWorld이 반환 후 "Done" 프린트
명시적 작업..? 이라는 뜻이 맞는지 정확히 모르겠네요..ㅠ
아무튼 lanch{}가 Job 타입 인데
그 안에 job.join() 을 넣어 코루틴에게 기다리라고 명령 합니다.
code:
fun main() {
runBlocking {
println("---START---")
val job = launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello")
job.join() // wait until child coroutine completes
println("Done")
}
println("---END---")
}
result:
---START---
Hello
World!
Done
---END---
job.join()이 빠졌다면 "Hello" 출력 후 바로 "Done"이 출력 되었을 텐데job.join()을 넣어 "World!" 출력을 기다리고 끝나고 나서야 "Done " 출력JVM 스레드보다 코루틴은 자원 집약도가 낮습니다.
5만개의 서로 다른 코루틴을 실행해 매우 적은 메모리를 사용하면서 "A" 출력
code:
fun main() {
runBlocking {
repeat(50000) { // 함수를 5만번 실행
launch {
print("A")
}
}
}
result:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ...
여기 위 까지 공식문서 내용인데 용어들이 낯설고 어렵다..
그래서 여태 위에 설명된 공식문서대로 내 문제를 대입 시키면?
할 수 있는게 없다
아까 봤던 코드를 다시보면
val list = mutableListOf<Document>()
...
// button (onclick)
onSearch = {
val results = searchImage(query = query)
list.addAll(results)
Log.d("TAG", "onCreate: list $list")
}
searchImage() 함수의 반환타입을 비동기 함수로 선언할 수 있는 방법을 안 것도 아니고searchImage() 함수 내에서 call.enqueue{} 를 수행하는데 Asynchronously send 라서 코드 자체에 이해도가 부족한 내가 비동기 함수 선언하는 코드 몇자 배웠다고 할 수 있는게 아니였다.현재 .. 매우 절망 상태..
결론적으론 다시.. 어떻게든
retrofit2(http 통신 라이브러리) + compose 조합으로
화면 변화를 일으킬 수 있는 코드를 작성해보려한다.