본문 바로가기

개발일기

8월 한달 토이 프로젝트를 해보자! -05 : Adapter Click

리스트 ui까지 만들어짐을 확인했으니, 이제 다시 디테일 페이지로 진입하는 부분을 건드릴 차례다.

이때, 클릭 이벤트 생성에 앞서, 디테일 페이지에서는 해당 포스트의 주인, 즉 다이어리를 쓴 본인일 경우 수정이 가능해야 할 것이며, 댓글 작성/삭제 역시 가능해야 할 것이다. 이를 위해서는

  1. 디테일 페이지 접속자가 포스트 작성인인가
  2. 댓글 작업을 하려는 포스트의 고유값이 무엇인가

위에 대한 최소한의 처리가 필요하다. 이를 위하여 Diary 모델에 val id: Stringval account: String 두 가지 속성을 추가해주었다. account는 로그인 한 계정정보와 비교할 것이고, id는 포스트 등록시 UUID를 생성하여 부여, 댓글 등을 이와 매칭시켜 추후에 조회, 불러올 것이다.

이제 본격적으로 클릭 이벤트를 추상화 한 인터페이스를 만들어준 후, ListFragment에서 이를 상속 받아 처리 후 DiaryListAdapter에 인자로 넘겨줄 것이다.

우선, function이라는 이름의 패키지를 생성 후 OnClickInterface라는 이름의 인터페이스를 만들어 onClick 함수를 추가해주었다.

OnClickInterface.kt

interface OnClickInterface {
    fun onClick(_id: String)
}

ListFragment에 오버라이딩 시킨다.

ListFragment.kt

class ListFragment : Fragment(), View.OnClickListener, OnClickInterface {
    ...
    override fun onClick(_id: String) {
        TODO("Not yet implemented")
    }
}

이전에 만들어주었던 함수 toDetail()이 있다. 기존에는 아래와 같이 생겼다.

private fun toDetail() {
    val action = ListFragmentDirections.listToDetail()
    Navigation.findNavController(requireActivity(), R.id.main_container)
        .navigate(action)
}

이젠 다이어리의 id를 매개로 할 것이니, 인자로서 id를 추가해준다.

toDetail(_id: String)

그러면 오버라이드 한 onClick(_id: String)의 TODO 부분은 toDetail(_id)가 될 것이다.

어댑터에서 클릭 이벤트가 필요한 것이니, 자, 어댑터에 인자로 인터페이스를 추가해주자. (혹시 View.OnCLickListener를 상속받았다면 지워주도록 하자. 대체할 것이다.)

bind() 함수 내부에 클릭리스너를 추가해준다. 나는 itemView를 사용할 것이다.

 itemView.setOnClickListener { clickInterface.onClick(item.id) }

자, 이제 ListFragment에서 테스트를 위해 부여했던 어댑터를 기억하는지? 아래와 같이 생겼었다.

private fun initTestList() {
    val testList = listOf(
        Diary.TEST(), Diary.TEST(), Diary.TEST(), Diary.TEST(), Diary.TEST())
    listRecycler.adapter = DiaryListAdapter(testList)
    listRecycler.adapter?.notifyDataSetChanged()
}

에러가 뜬다. DiaryListAdapter의 인자로 OnClickInterface가 추가되었기 때문이다. ListFragment가 해당 인터페이스를 상속받았으니 this로 추가만 해주면, 이제 해당 함수를 활용할 수 있다.

또한 인자로 받은 인터페이스의 함수를 실행하는데, 해당 함수는 ListFragment에서 선언한 함수 그 자체다. 이렇게 Fragment를 인자로 처리하지 않고 함수를 사용할 수 있게 되었다.


아직 toDetail(_id: String) 함수의 _id를 활용하지 않았다. Navigation 상에서 해당 인자를 argument로 보내주는 절차가 필요하다.

main_nav로 돌아가서, DetailFragment를 선택한 후 우측 창 Arguments 옆의 플러스 버튼을 눌러 인자를 추가하자. 이후 list_to_detail 액션을 클릭해보면 Argument가 생성된 것을 볼 수 있다.

자, 이제 FragmentListtoDetail(_id: String)의 인자를 활용하면 아래와 같이 구성할 수 있다.

private fun toDetail(_id: String) {
    val action = ListFragmentDirections.listToDetail(_id)
    Navigation.findNavController(requireActivity(), R.id.main_container)
        .navigate(action)
}

이제 보낼 준비는 끝났으니, 받기만 하면 된다. DetailFragmentprivate lateinit var postId: String 변수를 추가해준 후, onCreateView에서 인플레이팅을 하기 전에 arguments 처리를 해준다.

만약 DetailFragmentArgs (규칙은 해당Fragment+Args. 내가 지정한 이름이 아니다.) 가 뜨지 않는다면 빌드를 다시 해줘보자. 아마 제대로 뜰 것이다.

DetailFragment.kt

class DetailFragment : Fragment() {
    ...
    private lateinit var postId: String

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val myArgs: DetailFragmentArgs by navArgs()
        postId = myArgs.diaryId

        return inflater.inflate(R.layout.detail_fragment, container, false)
    }
}

좋아. 예상대로 받아졌는지 확인해보자. action 설정할 때 id의 디폴트 값을 "" 으로 지정해주었다. (받아올 id가 아직 없음) 이에 onViewCreated에서 아래와 같이 토스트를 띄우도록 처리하였다.

if (postId.isEmpty()) {
    Toast.makeText(requireContext(), "Id is empty", Toast.LENGTH_SHORT).show()
}

결과는 아래와 같이 성공적이다.

예상 가능하겠지만, 상술하였던 계정 정보로 포스트를 쓴 본인인지 아닌지를 체크할 것이라 했기에, 로그인 -> 리스트에도 계정 정보를 (필요하다면 암호화 하여) 넘길 것이며, 리스트 -> 디테일 에서도 해당 정보가 필요하게 될 것이다.

이는 기능적인 부분이니 일단은 차치하기로 하고, UI 구성을 계속하도록 하자.