Retrofit2 enqueue(비동기)를 동기로 바꾸기

Hanseul Lee·2022년 8월 29일
1

데이터가 들어오면 그 순서대로 처리가 되는 줄 알았는데 중구난방이었다.

당연함. 난 Retrofit을 enqueue로 실행시켰고, 이건 비동기기 때문임.

.....생각 않고 코드를 짰다가 낭패를 봤다…

우선 예제로 돌릴 나의 삽질 코드부터 보자.

1. 순서대로 Retrofit을 생성해 돌릴 거니까, 반복문에 넣자!

for (i in 0 until artistList.size) {
		Log.d("Retrofit","${i+1} / ${artistList.size} 번째 도는 중")
		getArtistImg(artistNameList[i], i)
}

2. getArtistImge()는 이렇게 생겼다!

private fun getArtistImg(keyword: String, time: Int) {
        val baseUrl = "https://dapi.kakao.com/v2/search/"
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val service: SearchingService = retrofit.create(SearchingService::class.java)
        val searchingMusic = service.searchArtistImg(auth = BuildConfig.KAKAO_IMAGE_API_AUTH, target = keyword, page = 1, size = 1)

        searchingMusic.enqueue(object: Callback<ArtistImgData> {
            override fun onResponse(
                call: Call<ArtistImgData>,
                response: Response<ArtistImgData>
            ) {
                Log.d("Retrofit", "이미지 찾기 | 현재 키워드 : $keyword")
                val body = response.body()

                if (body != null) {
                    Log.d("Retrofit", "이미지 찾기 | 통신 성공")
                    artistList[time].imgUrl = body.documents[0].thumbnail_url
                    Log.d("Retrofit","${time+1}번째 잘 들어갔나.... ${artistList[time].imgUrl}")
                    if (time + 1 == artistList.size) {
                        setArtistAdapter(artistList)
                    }
                }
                else {
                    Log.d("Retrofit","이미지 찾기 | 바디 null")
                }
            }

            override fun onFailure(call: Call<ArtistImgData>, t: Throwable) {
                Log.d("Retrofit", "이미지 찾기 | 통신 실패", t)
            }
        })
    }

로그를 찍어서 정말 순서대로 돌아가는지 확인해볼까?

  • n / 4 번째 도는 중 → 반복문이 돌아가면서 retrofit 실행
  • 실행된 retrofit은 순서대로 움직이는 것이 아니라 임의다.

왜 이렇게 멋대로 될까 싶었는데 생각해보니 바로 이게 비동기구나.

그래서 다음과 같이 Thread를 활용해 동기로 바꿔보았다.

Thread {
            searchingMusic.enqueue(object : Callback<ArtistImgData> {
                override fun onResponse(
                    call: Call<ArtistImgData>,
                    response: Response<ArtistImgData>
                ) {
                    Log.d("Retrofit", "이미지 찾기 | 현재 키워드 : $keyword")
                    val body = response.body()

                    if (body != null) {
                        Log.d("Retrofit", "이미지 찾기 | 통신 성공")
                        artistList[time].imgUrl = body.documents[0].thumbnail_url
                        Log.d("Retrofit", "${time + 1}번째 잘 들어갔나.... ${artistList[time].imgUrl}")
                        if (time + 1 == artistList.size) {
                            setArtistAdapter(artistList)
                        }
                    } else {
                        Log.d("Retrofit", "이미지 찾기 | 바디 null")
                    }
                }

                override fun onFailure(call: Call<ArtistImgData>, t: Throwable) {
                    Log.d("Retrofit", "이미지 찾기 | 통신 실패", t)
                }
            })
        }.start()

        try {
            Thread.sleep(50)
        } catch (e: Exception) {
            e.printStackTrace()
        }


캡쳐는 하나 뿐이지만 여러 번 진행해도 동기로 잘 돌아가서 따로 추가하진 않았다.

그런데 하나 의문이 생긴다. 아무리 50밀리초와 같이 작은 시간을 딜레이 시킨다 해도 어쨌든 딜레이인데…… 문제가 생길 거 같다. 어떻게 처리하면 좋을까?

어차피 내가 동기로 실행시키길 원했던 이유는, 올바른 위치에서 어댑터를 세팅하기 위한 것이기 때문에 횟수를 따로 변수 선언해서 카운트하는 게 나을 거 같다.

var count = 0
    private fun getArtistImg(keyword: String, time: Int) {
        val baseUrl = "https://dapi.kakao.com/v2/search/"
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val service: SearchingService = retrofit.create(SearchingService::class.java)
        val searchingMusic = service.searchArtistImg(auth = BuildConfig.KAKAO_IMAGE_API_AUTH, target = keyword, page = 1, size = 1)

        searchingMusic.enqueue(object : Callback<ArtistImgData> {
            override fun onResponse(
                call: Call<ArtistImgData>,
                response: Response<ArtistImgData>
            ) {
                Log.d("Retrofit", "이미지 찾기 | 현재 키워드 : $keyword")
                val body = response.body()

                if (body != null) {
                    Log.d("Retrofit", "이미지 찾기 | 통신 성공")
                    artistList[time].imgUrl = body.documents[0].thumbnail_url
                    count++
                    Log.d("Retrofit", "$count 번째 잘 들어갔나.... ${artistList[time].imgUrl}")
                    if (count == artistList.size) {
                        setArtistAdapter(artistList)
                    }
                } else {
                    Log.d("Retrofit", "이미지 찾기 | 바디 null")
                }
            }

            override fun onFailure(call: Call<ArtistImgData>, t: Throwable) {
                Log.d("Retrofit", "이미지 찾기 | 통신 실패", t)
            }
        })

    }

약간 제목에서 벗어났지만... 어쨌든 Retrofit 동기와 비동기에 대해 배웠으니까…!

cf) 코루틴 훔쳐보기

겸사겸사 코루틴 코드도 살펴봤는데 정말 깔끔하다...

private fun getArtistImg(keyword: String, time: Int) {
        val baseUrl = "https://dapi.kakao.com/v2/search/"
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val service: SearchingService = retrofit.create(SearchingService::class.java)
        val searchingMusic = service.searchArtistImg(
            auth = BuildConfig.KAKAO_IMAGE_API_AUTH,
            target = keyword,
            page = 1,
            size = 1
        )


        val response = searchingMusic.execute()
        CoroutineScope(Dispatchers.Main).launch {
            val body = response.body()

            if (response.isSuccessful) {

                if (body != null) {
                    Log.d("Retrofit", "이미지 찾기 | 통신 성공")
                    artistList[time].imgUrl = body.documents[0].thumbnail_url
                    Log.d("Retrofit", "${time + 1}번째 잘 들어갔나.... ${artistList[time].imgUrl}")
                    if (time + 1 == artistList.size) {
                        setArtistAdapter(artistList)
                    }
                } else {
                    Log.d("Retrofit", "이미지 찾기 | 바디 null")
                }

            } else {
                Log.d("Retrofit", "이미지 찾기 | 통신 실패")
            }
        }
    }

1개의 댓글

comment-user-thumbnail
2022년 11월 2일

저도 비슷한 상황인데 코드 한번만 봐주실 수 있나요 ㅠㅠ

답글 달기