이번에는 8번째 시간이다. ㅎㅎ 지난번에 태환님의 동영상 강의를 보면서 AdapterContract 정의를 했고, 이어서 OnClickListener 정의를 해보려고 한다.

AdapterContract View - OnClickListener 정의

OnClickListener 정의는 AdapterContract View에 추가 정의를 통해 간단하게 구현할 수 있다. 기존에 AdapterContact.View에는 다음과 같이 정의했다.

1
2
3
4
5
6
7
interface ImageAdapterContract {

interface View{
fun notifyAdapter()
}

}

여기에 setOnClickListener(OnClickListener listener) 정의를 추가함으로써 Presenter에서 바로 Adapter의 OnClickListener 이벤트를 전달받고, 이를 처리할 수 있게 된다.

이유는 간단하다.

  • AdapterModel / AdapterView를 Presenter에서 들고 있기 때문에 굳이 View에서 이런 이벤트를 받을 필요는 없다. 역시나 귀찮은 부분이 따르므로 아래이 그림과 같이 추가될 수 있다.

AdapterContract View를 통해서 초기화하는 이유는??

AdapterContract.View에서 OnClick을 초기화하고, 이를 ViewHolder에서 정의하는 이유는 다음과 같다.

  • 이미 Presenter에서 AdapterView/AdapterModel을 알고 있다.
  • 실제 View/Model을 한 번에 가지고 있는 Adapter이기 때문에 이를 굳이 View에서 처리할 필요는 없다.

위와 같은 이유로 다시 View에서 setOnClickListener을 하고 이 이벤트를 받아서 Presenter에 넘겨서 처리를 하는 것보다는 바로 Presenter가 받아서 이를 처리하고, View 이벤트를 분리하는게 편리하기 때문이라고 한다. 그래서 이 방법은 배워보도록 하겠다.

해당 View에서도 onClick 처리를 할 수 있지만, Presenter에서 onClick을 들고 있기 때문에 Presenter에서 다양한 이벤트 처리를 바로 할 수 있기 때문에 한 단계 줄여서 바로 Presenter setOnClickListener를 만들어주고 그걸 Adapter에 등록을 시켜주면 onClick이 발생을 했을 때, 이외 처리가 좀 더 간단하게 만들어질 수 있다.

배운 점

Presenter가 View, adapterView, adapterModel을 가지고 있고, context도 보유하고 있으므로 여기서 onClick을 처리할 수 있는 것이다. 굳이 View에서 처리하지 않아도 된다는 뜻인 셈이다.

ImageAdapterContract.View에 var onClickFunc : ((Int) -> Unit)? 을 선언해준다. 이는 코틀린의 특성 중 하나로 변수에 바로 사용할 함수를 담는 것이다. 이렇게 하면 Adapter는 ImageAdapterContract.View를 구현하기 때문에 Adapter에서는 onClickFunc 변수를 오버라이드 한다.

그리고 onBindViewHolder의 역할을 ImageViewHolder의 onBind에서 처리하고 있으므로 onClick에 대한 이벤트 처리도 ImageViewHolder에 위임하여 처리를 넘겨준다. [이 부분도 어댑터에서 진행하는 부분이다.]

1
2
3
4
5
6
7
8
9
10
fun onBind(item: ImageItem, position: Int){
ImageAsync(context,imageView).execute(item.resource)
textView.text = item.title
itemView.setOnClickListener {
onClickFunc?.invoke(position)
// int를 변수로 넘겨줘야 하기 때문에
// null이 아닌 경우를 체크하고 int인 position을 넘겨준다.
}

}

그러면 itemView에 대한 클릭이 발생했을 때, onClickFunc은 null이 아닌 경우를 체크하고 position을 넘겨준다. 넘어간 position은 아래와 같은 코드를 통해서 받는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    override var adapterView: ImageAdapterContract.View? = null
set(value) {
field = value

// 아래 부분 중요
field?.onClickFunc = { onClickListener(it)}
// 위와 같이 지정을 해주면 onClick이 발생하면 아래의 함수가 동작한다.

}

// onClick시 아래 함수 동작
private fun onClickListener(position: Int){
adapterModel.getItems(position).let {
view.showToast(it.title)
}
}

set block 안에서 position을 it으로 받아서 onClickListener 함수를 호출한다. onClickListener는 다시 adapterModel에게 getItem을 요청한다.

1
override fun getItems(position: Int): ImageItem = imageList[position]

위와 같은 로직으로 Adapter에서 onClick 이벤트를 처리할 수 있다. 조금 복잡하지만, 익숙해진다면 로직을 분리할 수 있는 장점과 결합도를 낮출 수 있을 것 같다.

참고