ViewHolder에 Retrofit... 넣어도 되나요

Hanseul Lee·2022년 8월 29일
0

거두절미하고, 안됩니다.

하지만 아무것도 모르는 나는 ViewHolder에 Retrofit을 넣을 수밖에 없었던 피치못할 사정이 있었는데....

얼레벌레 내가 맞닥뜨린 상황

사용자가 검색한 가수와 비슷한 느낌의 가수를 추천해주는 API를 쓰고 있었다. 근데 이 API가 이미지를 제공하지 않아서 카카오 이미지 검색 API를 활용해 어울리는 가수 이미지를 찾으려 했다. 그래서 가수 정보 데이터를 활용하는 ViewHolder 안에 카카오 이미지 검색 API와 통신하는 Retrofit을 넣었다.

그러니까 정리하면,

  1. RecyclerView에 API A가 준 데이터를 넣어야 하는데,
  2. API A의 데이터에 API B가 준 데이터를 추가하고 싶지만
  3. 어떻게 데이터를 수정하고,
  4. 어디서 어떻게 어댑터를 선언하면 좋을지 모르겠어서
  5. 냅다 Retrofit을 ViewHolder에 넣었다!!!!
  • 최대한 주어진 데이터 클래스 내에서 해결하려 했지만 어려웠다. 어떻게 데이터 수정? ⇒ API가 주는 데이터 클래스는 수정하면 안 된다고 착각했다!!!!! 내가 새롭게 변수를 만들어도 null 값으로 채워지는데…
  • Retrofit 통신들이 중구난방으로 진행되어서 제대로 선언이 어렵다. 어떻게 어댑터 선언? ⇒ 동기와 비동기 이해가 부족했다. enqueue는 비동기 처리를 하는데 깊게 생각하지 않았다.

문제 코드

inner class SearchArtistViewHolder(binding: LayoutMusicListSearchedBinding) : RecyclerView.ViewHolder(binding.root) {
        private val image: ImageView = binding.musicListImg
        private val name: TextView = binding.musicListTitle
        private val genre: TextView = binding.musicListSinger
        private val favorite: ImageView = binding.musicListIcon

        fun bindArtist(info: SimilarArtist) {
            name.text = info.Name
            genre.text = info.wTeaser
            favorite.setImageResource(R.drawable.icon_favorite)
            searchingImg(info.Name)
        }

        private fun searchingImg(keyword: String) {
            ...

            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", "이미지 찾기 | 통신 성공")
                        Glide.with(context).load(body.documents[0].thumbnail_url)
                            .apply(RequestOptions().placeholder(R.drawable.album_art)).fitCenter()
                            .into(image)
                    }
                    else {
                        Log.d("Retrofit","이미지 찾기 | 바디 null")
                    }
                }

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

일단 의도대로 동작은 하지만.... ViewHolder 내에 Retrofit이 있는 게 영 신경 쓰여서 코드를 뜯어 고쳤다.

코드 개선 시작~

1. 데이터를 담을 변수 선언

private val artistList = ArrayList<SimilarArtist>()
private val artistNameList = ArrayList<String>()

2. 데이터 초기화 메서드

private fun initializeData() {
        artistList.clear()
        artistNameList.clear()

        searchingMusic()
        similarArtist()
    }

3. 비슷한 가수를 찾아주는 API 메서드

전체 코드는 복잡하니까 가장 하단에 넣겠다. 우선은 중요한 부분부터 뜯어보자!

private fun initializeData() {
        artistList.clear()
        artistNameList.clear()

        searchingMusic()
        similarArtist()
    }
  • 검색한 가수는 Info로, 비슷한 가수들은 Results로 데이터가 제공되기 때문에 따로 분리했다.
  • artistList에 RecyclerView에 출력할 데이터를 넣는다.
  • 향후 이미지 검색 API를 활용해서 이미지를 가져와야 하니까 artistNameList에 가수 이름을 받아둔다.
for (i in 0 until artistList.size) {
                        Log.d("Retrofit","${i+1} / ${artistList.size} 번째 도는 중")
                        getArtistImg(artistNameList[i], i)
                    }
  • artistName 안에 있는 이름을 하나씩 꺼내서 이미지 검색 API를 활용하는 메서드에 넣는다.

4. 이미지 검색 API 메서드

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)
                    }
                }
  • RecyclerView에 출력할 데이터인 artistList에 검색한 이미지의 url을 넣는다.
  • 비동기로 움직이고 있기 때문에 따로 count를 세준다.
  • count값이 artistList 사이즈와 일치하는 경우에는 모든 데이터를 알맞게 넣는 거니까, 그때 adapter를 세팅한다.

전체 코드

private val artistList = ArrayList<SimilarArtist>()
private val artistNameList = ArrayList<String>()

private fun initializeData() {
        artistList.clear()
        artistNameList.clear()

        searchingMusic()
        similarArtist()
    }

private fun similarArtist() {
        val baseUrl = " https://tastedive.com/api/"
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val service: SearchingService = retrofit.create(SearchingService::class.java)
        val searchingArtist = service.searchSimilarArtist(targetArtist = keyword, info = 1, type = "music", limit = 3)

        searchingArtist.enqueue(object : Callback<MusicSimilarData> {
            override fun onResponse(call: Call<MusicSimilarData>, response: Response<MusicSimilarData>) {
                Log.d("Retrofit", "가수 찾기 | 현재 키워드 : $keyword")
                val body = response.body()

                if (body != null) {
                    Log.d("Retrofit", "가수 찾기 | 통신 성공")

                    for (i in 0 until body.Similar.Info.size) {
                        artistList.add(body.Similar.Info[i])
                        artistNameList.add(body.Similar.Info[i].Name)
                    }

                    for (i in 0 until body.Similar.Results.size) {
                        artistList.add(body.Similar.Results[i])
                        artistNameList.add(body.Similar.Results[i].Name)
                    }

                    for (i in 0 until artistList.size) {
                        Log.d("Retrofit","${i+1} / ${artistList.size} 번째 도는 중")
                        getArtistImg(artistNameList[i], i)
                    }
                }
                else {
                    Log.d("Retrofit","가수 찾기 | 바디 null")
                }
            }

            override fun onFailure(call: Call<MusicSimilarData>, t: Throwable) {
                Log.d("Retrofit", "가수 찾기 | 통신 실패", t)
            }

        })
    }

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)
            }
        })

    }

물론 지금 코드도 엉망진창이지만.. 적어도 ViewHolder에서는 구출했다~
벨로그 처음 써서 쉽지 않네.... 나중에 정리를 좀 해야겠다.

0개의 댓글