본문 바로가기

내일배움캠프

본 캠프 57일 차 - 팀 프로젝트

Search 기능 구현하기

  • youtube api를 통해 가져올 데이터를 데이터 클래스로 만들기
  • SearchViewModel을 만들어서 api를 통해 데이터 가져오는 함수 만들기
  • SearchAdapter 만들기
  • SearchFragment에 adapter와 viewmodel을 연결시키기
  • view에 가져온 데이터들을 눈에 보이게 만들기

 

문제1

저번에 개인과제를 진행 할 때는 가져온 데이터를 따로 내가 만든 data type에 끼워 넣어서 이걸 어댑터에 연결하여 표시하였는데, 이번에는 팀원들이 그냥 가져온 데이터 그대로 써도 될 거 같다고 해서 그대로 써보기로 했다.

 

첫 난관은 ViewModel에 있는 LiveData에 어떻게 집어 넣는지부터 시작

 

그런데 그냥 가져온 데이터를 여기서 내가 필요한걸 뽑는게 아니라 그냥 데이터 리스트에 집어 넣어주기만 하면 된다고 한다.

    fun searchItem(query: String) {
        var receiveItems = ArrayList<ReceiveItem>()
        viewModelScope.launch {
            Log.d(VMTag,"#search query = $query")
            val response = RetrofitInstance.youtubeRetrofit.getSearchItem(
                query,
                "snippet,contentDetails,statistics",
                10,
                "relevance",
                "video"
            )
            response.searchItems.forEach {
                receiveItems.add(
                    ReceiveItem(
                        it.info?.title.orEmpty(),
                        it.info?.publishedAt.orEmpty(),
                        it.info?.description.orEmpty(),
                        it.info?.thumbnails?.standard?.url.orEmpty(),
                        it.statistics?.viewCount,
                        it.info?.channelId.orEmpty(),
                        it.details?.duration.orEmpty()
                    )
                )
            }
            val currentList = searchItemList.value.orEmpty().toMutableList()
            currentList.addAll(receiveItems)
        }
    }

 

해결1

이게 원래 내가 쓸 내용을 뽑아서 받은 리스트를 따로 만들고 그 안에 내가 만든 data type으로 집어 넣은 후 이것을 다시 라이브 데이터에 집어넣었는데 이를 여기서는 리스트에만 집어넣고 빼서 쓰는건 adapter나 fragment에서 해주면 된다고 한다. 따라서 간단하게 바꿔버리면 가져오는 데이터 타입 그대로 라이브 데이터 타입에 넣어주고 

    fun searchItem(query: String) {
        viewModelScope.launch {
            Log.d(VMTag,"#search query = $query")
            val response = RetrofitInstance.youtubeRetrofit.getSearchItem(
                query,
                "snippet,contentDetails,statistics",
                10,
                "relevance",
                "video"
            )
            _searchItemList.value = response.copy().searchItems.toMutableList()
        }
    }

이렇게 viewmodel에서 검색해서 데이터 가져오는 함수는 이거로 마무리 된 것 같다...?

 

문제2

retrofit2.HttpException: HTTP 400 에러

로그캣을 확인 해보니 retrofit2.HttpException: HTTP 400 이렇게 뜨고 contentDetails에 error가 떠 있었다. 에러 메세지를 더 읽어 보니 얘를 가져오지 못한다는 것 같다.

 

해결2

그래서 위에 part쪽에 적어놓은 "snippet, contentDetails,statistics"에서 contentDetails를 빼고 해보았다. 근데 또 똑같이 400에러가 뜨고 이번엔 statistics가 떠서 최종으로 snippet만 part에 넣어주는 거로 했더니 이젠 데이터를 가져온다.

 

 

문제3

가져온 데이터를 리사이클러 뷰에 넣어서 데이터들을 화면에 노출시키기가 안됨.

로그를 계속 찍어보았는다. ListAdapter를 쓰는 중인데 디버깅 모드로 

    companion object {
        val diffUtil = object : DiffUtil.ItemCallback<SearchItem>() {
            override fun areItemsTheSame(oldItem: SearchItem, newItem: SearchItem): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: SearchItem, newItem: SearchItem): Boolean {
                return oldItem == newItem
            }
        }
    }

위 코드에서 리턴 두 곳에 찍어두고 디버깅 모드로 확인을 해보는데 oldItem과 newItem의 데이터가 같은 거로 뜬다.

도저히 해결을 못해서 튜터님을 찾아갔다.

 

시도3

우선 oldItem과 newItem의 결과가 같게 나온 이유는 Fragment에서 observe 할 때 로그를 확인 하기 위해서 내가 찍어 둔 코드가 문제였다.

    private fun getObservingFromViewModel() {
        viewModel.searchItemList.observe(viewLifecycleOwner) {
            mAdapter.submitList(it)
            Log.d("SearchFragment",#search observe Item = $it")
        }
    }

여기에서 리스트를 mAdapter.submitList(it)으로 리스트를 이미 여기서 불렀는데 로그창에서도 부르는게 문제였다.

Log에서 하는 함수는 영향을 주지 않고 로그만 보여주는 줄 알았는데 여기서도 똑같이 작동한다는 걸 알아간거 같다.

일단 여기서 oldItem과 newItem이 첫 검색에서 동일하다고 나오는 이유를 알게 되었는데, 제일 큰 문제는 화면에 데이터들이 안뜬다는 것이 남았다.

 

우선 gridlayoutmanager를 사용하여 1을 주고 vertical로 한 개씩 아래로 띄운다는 걸 만들었는데 굳이 그리드로 쓸 필요가 있냐고 물으셨는데, 생각해보니 굳이 그럴 필요가 없긴 했다. 그래서 LinearLayoutManager(requireContext())만 넣고 돌려 보았으나 변화가 없었다. 혹시 어댑터쪽이 문제인가 하고 보았으나 찾지 못하고, xml쪽도 찾아보았으나 결국 찾지 못하고 튜터님이 github에 올려보라고 직접 찾아보신다고 하셨다. 그리고 다시 자리로 와서 팀원들과 더 찾아보았다.

 

해결3

Log는 거짓말을 하지 않는다...

어댑터의 아이템 리스트에 내용이 들어간다는 것까지 로그를 찍어서 확인을 하였다. 그러면 이제 

    override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
//        bind(holder, position)
        val item = currentList[position]
        Log.d("SearchAdapter","#search adapter item = $item")

        holder.itemView.setOnClickListener {
            itemClick?.onClick(it,position)
        }
//            playtime.text = item.details?.duration
        holder.date.text = item.info?.publishedAt
        holder.title.text = item.info?.title
        holder.channel.text = item.info?.channelId
        holder.img.load(item.info?.thumbnails?.medium?.url)
    }

여기 문제긴 할 텐데 우선 어차피 details는 contentDetails라 받아오지 못해 null이라 주석처리를 해둔 상태이다. 일단 이게 해결 되면 나중에 더 받아와서 저 부분도 데이터를 추가적으로 받아와야 할 것 같다.

해결방법은 저기에 나온 로그에 있었다.

 

#search adapter item = SearchItem(id=YoutubeID(kind=youtube#video, channelId=null), info=Snippet(title=[골라봐야지] 슈가맨 방청객에서 일어난 찐가수 모음집.zip |슈가맨, description=다들 깜짝 놀랐다구요ㅠㅠㅠㅠ 패널들에게도 비밀로 하고 가수 빼돌린(?) 제작진들ㅋㅋㅋㅋㅋㅋㅋ #슈가맨 #JTBC봐야지 #골라 ..., thumbnails=Thumbnails(default=Default(height=90, url=https://i.ytimg.com/vi/oy3Ph2G5Pp0/default.jpg, width=120), high=High(height=360, url=https://i.ytimg.com/vi/oy3Ph2G5Pp0/hqdefault.jpg, width=480), medium=Medium(height=180, url=https://i.ytimg.com/vi/oy3Ph2G5Pp0/mqdefault.jpg, width=320), standard=null, maxres=null), channelId=UCF3UEVPt5zZTlBVJwDG317g, publishedAt=2020-10-20T08:20:43Z), details=null, statistics=null)

 

위 내용이 로그 내용이다. 자세히 보면 섬네일 값에 default, high, medium은 값이 있는데 standard,와 maxres는 null이 들어온다. 즉 값이 없어서 나오지 않는 상태였던 것이다...

앞으로도 로그를 잘 확인 해 봐야할 것 같다.

 

 

문제4

높이를 match로 줘도 높이가 저렇게 한정적으로 변하고 더 이상 늘어나지 않는다. 검색을 해보니 리사이클러뷰 지금 선택된 저 공간에서만 나오고 스크롤도 저기서만 되기 때문에 즉 한개의 아이템씩 밖에 나오지 않았다.

이유를 찾아보니 진짜 너무 간단했다.

 

해결4

NestedScrollview의 속성값

저기 있는 검색결과와 리사이클러뷰가 동시에 스크롤이 되게 하고 그 위는 스크롤이 되지 않게 하려고 scrollview를 쓰려 했는데 되지 않았다...팀원들에게 물어보니 nestedscrollview를 사용하면 된다고 하기에 써 보니 이제 스크롤 안에 텍스트뷰와 리사이클러뷰가 동시에 감싸졌다. 

이제 여기서 속성 하나를 추가 안했었는데

android:fillViewport="true"

이 코드를 nestedscrollview 안에 집어 넣으니

이제 정상적으로 넓어진 것을 볼 수 있다.

그리고 추가적으로 recyclerview에서 nestedScrollingEnabled 속성값을 false로 해주어야 한다고 한다.

이는 원래 recyclerview도 스크롤이 되는데 이를 없애기 위해서라고 한다.

 

 

진짜 뭔가 나간게 없는 느낌인데 걱정이 좀 많이 된다...어렵다...