[TIL][내일배움캠프]

[내일배움캠프][TIL] 24.02.13 (화) - 팀 프로젝트(심화) : 네이버 백과사전 UI 구현하기

kimlaurant 2024. 2. 13. 22:40
1. 팀 프로젝트(심화)

 

지난 4일 동안 설 연휴를 보내면서 살을 찌웠지만, 팀 프로젝트는 찌우지 못하였다. 이게 시간이 날 때마다 틈틈이 했으면 좋았겠지만 이상하리만치 프로젝트를 만질 시간이 없었다.

 

그리하여 이제는 주요 기능들을 하루빨리 완성해야만 하는 순간이 다가왔다.

그 주요 기능 중에서 가장 핵심은 바로 API 연결인데 그 중에서 유튜브 API는 다른 팀원이 맡아서 작업하였고, 나는 네이버 백과사전 API를 담당하여 작업하였다.

 

 

네이버 백과사전 UI

 

우선, 네이버 백과사전 API를 활용하려면 몇 가지 단계가 필요하다,

 

1. 검색 화면에서 동물을 클릭한다.

2. 그 동물의 정보를 검색 결과 프래그먼트로 보낸다.

3. 받은 정보를 바탕으로 검색 결과 프래그먼트에서 네이버 API를 가동시킨다.

 

 

그리고 이를 실행하려면 몇 가지가 필요하다.

 

1. 동물의 data class를 Parcelize 하기

2. 동물 카테고리를 클릭하면 그 동물에 해당하는 data class를 검색 결과 프래그먼트에 전달하기

3. 동물의 이름을 query로 받아서 Retrofit 완성하기

 

하나하나 살펴보도록 하자.

(참고로 Adapter 부분에 대한 설명은 생략하였다)

 

 

1. 동물의 data class를 Parcelize 하기

다음은 얼추 완성된 검색 화면이다.

참고로 맨 밑에는 동물들을 추가할 수 있는 '추가' 버튼도 있다.

이 때문인지 data class도 다음과 같이 되어 있었다.

 

CtItem.kt

@Parcelize
sealed class CtItem : Parcelable{
    data class CategoryItem(var Id: Int, var animalIcon: Int, var animalName: String ): CtItem()
    data class CategoryPlus(var Id: Int, val PlusIcon: Int, val PlusName: String ): CtItem()
}

 

하나의 클래스에 두 가지의 data class를 담아놓은 형태다.

이런 형태의 클래스는 sealed class에 Parcelable를 적어놓으면 그 하위 data class 전부 Parcalable이 가능한 형태가 된다.

 

 

2. 동물 카테고리를 클릭하면 그 동물에 해당하는 data class를 검색 결과 프래그먼트에 전달하기

그 다음에는 동물을 클릭하면 이 data class를 검색 결과 프래그먼트로 보내는 방법이다.

 

SearchFragment.kt

adapter.animalClick = object : SearchAdapter.AnimalClick {
	override fun onClick(item: CtItem, position: Int) {
	val resultFragment = SearchResultFragment.newInstance(item)
	requireActivity().supportFragmentManager.beginTransaction().apply {
		replace(R.id.main_frame, resultFragment)
		setReorderingAllowed(true)
		addToBackStack("")
	}.commit()
}

 

이렇게 한 뒤에, 검색 결과 프래그먼트에 newInstance에 Parcelize한 파라미터를 추가한다.

 

SearchResultFragment.kt

@JvmStatic
fun newInstance(ctItem: CtItem) =
	SearchResultFragment().apply {
		arguments = Bundle().apply {
			putParcelable("EXTRA_ANIMAL", ctItem)
		}
	}​

 

 

 

3. 동물의 이름을 query로 받아서 Retrofit 완성하기

마지막으로, 이 동물의 이름을 query로 받아서 Retrofit을 완성시켜보자.

 

다음은 검색 결과 프래그먼트의 전체 코드이다.

 

SearchResultFragment.kt

class SearchResultFragment : Fragment() {

    private lateinit var binding: FragmentSearchResultBinding
    private lateinit var mContext: Context
    private lateinit var dictionaryAdapter: DictionaryAdapter
    private var animalData: CtItem.CategoryItem? = null
    var resItem: ArrayList<NaverModel> = ArrayList()

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mContext = context
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentSearchResultBinding.inflate(inflater, container, false)
        initAnimal()
        initDictionary(inflater, container)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

    }

    private fun initAnimal() {
        animalData = arguments?.getParcelable<CtItem>("EXTRA_ANIMAL") as CtItem.CategoryItem?
        binding.tvAnimal?.text = animalData?.animalName

        val animal = binding.tvAnimal.text.toString()
        fenchNaverResult(animal)
    }

    private fun fenchNaverResult(query: String) {
        NaverRetrofit.naverApiService.naverDic(Constants.NAVER_CLIENT_ID, Constants.NAVER_CLIENT_SECRET, query)
            ?.enqueue(object: Callback<NaverData> {
                override fun onResponse(call: Call<NaverData>, response: Response<NaverData>) {
                    val body = response.body()

                    body?.let {
                        response.body()!!.items.forEach{ item ->
                            val title = item.title
                            val description = item.description
                            val url = item.thumbnail
                            resItem.add(NaverModel(title, description, url))
                        }
                    }

                    dictionaryAdapter.items = resItem
                    dictionaryAdapter.notifyDataSetChanged()
                    Log.d("네이버api 검사", "응답")
                }

                override fun onFailure(call: Call<NaverData>, t: Throwable) {
                    Log.d("네이버api 검사", "응답실패")
                }
            })
    }

    private fun initDictionary(inflater: LayoutInflater, container: ViewGroup?) {
        with(binding){
            dictionaryAdapter = DictionaryAdapter(mContext)
            searchDictionary.adapter = dictionaryAdapter
            searchDictionary.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            searchDictionary.itemAnimator = null
        }

    }

    companion object {

        @JvmStatic
        fun newInstance(ctItem: CtItem) =
            SearchResultFragment().apply {
                arguments = Bundle().apply {
                    putParcelable("EXTRA_ANIMAL", ctItem)
                }
            }
    }
}

 

여기서 query를 뽑아내기 위해서 따로 함수를 하나 만들었다.

    private fun initAnimal() {
        animalData = arguments?.getParcelable<CtItem>("EXTRA_ANIMAL") as CtItem.CategoryItem?
        binding.tvAnimal?.text = animalData?.animalName

        val animal = binding.tvAnimal.text.toString()
        fenchNaverResult(animal)
    }

지금은 CtItem 중에서 CategoryItem만 필요로 하기 때문에 이 부분만 우선적으로 뽑아내고, 그 중에서 동물의 이름이 필요하기 때문에 animalName만 표시한다.

 

    private fun fenchNaverResult(query: String) {
        NaverRetrofit.naverApiService.naverDic(Constants.NAVER_CLIENT_ID, Constants.NAVER_CLIENT_SECRET, query)
            ?.enqueue(object: Callback<NaverData> {
                override fun onResponse(call: Call<NaverData>, response: Response<NaverData>) {
                    val body = response.body()

                    body?.let {
                        response.body()!!.items.forEach{ item ->
                            val title = item.title
                            val description = item.description
                            val url = item.thumbnail
                            resItem.add(NaverModel(title, description, url))
                        }
                    }

                    dictionaryAdapter.items = resItem
                    dictionaryAdapter.notifyDataSetChanged()
                    Log.d("네이버api 검사", "응답")
                }

                override fun onFailure(call: Call<NaverData>, t: Throwable) {
                    Log.d("네이버api 검사", "응답실패")
                }
            })
    }

그리고 네이버의 data들 중에서 title, description, url만 필요로 하기 때문에 따로 Model을 만들어서 이것만 취급했다.

 

 

그 결과는 다음과 같다.

 

(아직 영상 API는 연결하지 않아서 그대로 뒀다)

고양이를 클릭하면 이렇게 title, 썸네일, 설명이 잘 나온다.

 

 

이제 남은 과제는 UI 다듬기랑, 중간중간에 섞여 있는 저 <b>, <b/>을 제거하기, 동물과 관련된 유튜브 API도 연결시키기, 그리고 영상을 클릭하면 상세 화면으로 넘어가기이다.

 

 

트러블슈팅

 

  • 처음에 실행시켰을 때, 앱이 강제종료가 되서 이게 뭔가 싶었는데 알고보니 url에서 null값이 튀어나오더라. (개중에는 썸네일 사진이 없다는 뜻이었다) url 변수에 null-safety를 시키니 그제서야 제대로 작동이 되었다.