이번에는 Android에서 RecyclerView를 사용할 때, 각 아이템의 ClickListener를 다는 방법에 대해서 설명하려고 한다. 여러가지 방법이 있겠지만, 이번에는 Kotlin의 특징 중 하나인 함수를 매개변수로 넘길 수 있다는 특징을 사용하려고 한다.

필자가 진행 중인 프로젝트의 일부 코드를 기반으로 설명할 예정이다.

구현

  1. 먼저, MainFragment에서 RecyclerView를 사용할 것이다. 그리고 RecyclerView의 각 아이템을 눌렀을 때 어떤 동작을 할 것인지를 미리 정의해둔다.
1
2
3
4
5
6
7
private fun startToDetailActivity(id: Int, message: String) {
val intent = Intent(context, MissionDetailActivity::class.java)
intent.putExtra("id", id)
intent.putExtra("completeMessage", message)
intent.putExtra("main","main")
startActivity(intent)
}

Int 타입인 id와 String 타입인 message를 매개변수로 받는 함수이다. 이 함수는 id, message를 가지고 MissionDetailActivity로 이동하는 함수이다.

  1. 이제는 이 함수를 RecyclerView에서 사용될 Adapter에 매개변수로 넘기는 과정이 필요하다. 이 과정은 두 단계로 나눠보도록 하겠다.
  • Adapter에서 함수를 인자로 받을 수 있도록 수정.
  • MainFragment에서 Adapter 객체를 만들 때 위에서 만든 함수를 전달한다.

Adapter에서 함수를 인자로 받을 수 있도록 수정해보도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MainMissionAdapter(private var onMainMissionItemClick: (Int, String) -> Unit) :
RecyclerView.Adapter<MainMissionViewHolder>() {

private var itemsMock = ArrayList<MissionFeedResponse>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainMissionViewHolder {
val binding = ItemListMainMissionBinding.inflate(
LayoutInflater.from(parent.context)
, parent, false
)
return MainMissionViewHolder(binding, onMainMissionItemClick)
}
// 생략.
}

MainMissionAdapter의 기본 생성자를 호출하면서 (Int, String) -> Unit을 확인할 수 있다. 이는 Int, String을 인자로 받고 반환값이 없는 함수를 의미한다. 따라서 Int, String을 인자로 받고 반환값이 없는 함수를 매개변수를 받겠다는 의미이다.

그리고 onCreateViewHodler에서 MainMissionViewHolder에 매개변수로 받은 onMainMissionItemClick 함수를 넘기고 있다. ViewHolder는 위의 남은 단계가 끝나고 살펴볼 예정이다.

그러면 MainFragment에서 함수를 전달해보도록 하자. 함수를 전달할 때는 람다식을 사용할 수 있다. 이유는 익명 함수이기 때문에 람다식을 이용해서 가독성을 높이고 쉽게 처리할 수 있기 때문이다.

1
2
3
mainMissionAdapter = MainMissionAdapter { id, message ->
startToDetailActivity(id, message)
}

초기화 하는 코드이다. MainMissionAdapter의 인자로 id, message를 받는 익명함수가 전달되어야 하기 때문에 람다식을 통해서 id, message를 넘겨 받는다. 이는 위에서 구현해놓은 startToDetailActivity() 함수의 매개변수로 넘겨줘야 한다.

이것은 실제로 값을 받는 코드가 아니다. 이렇게 값을 받을 것이다라고 선언만 해놓은 것이지 아직 코드가 실행되지 않는다.
어떻게 보면 startToDetailActivity 함수를 Adapter에 넘기는 것이다. 이 함수는 위치는 MainFragment에 존재하는 것이고 이 함수의 호출을 대신하기 위해서 함수를 넘기는 것이다.

따라서 함수를 넘긴다는 것의 의미는 실제로 이 함수를 넘기는 것이 아니라 원하는 값을 얻어 이 함수에 담아서 호출하기 위함이다. 그래서 Adapter에서 ViewHolder로 넘기면 ViewHolder에서 onMainMissionItemClick 이라는 익명 함수를 받는다. 코드를 먼저 보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MainMissionViewHolder(
private val binding: ItemListMainMissionBinding
, private val onMainMissionItemClick: (Int, String) -> Unit
) : RecyclerView.ViewHolder(binding.root) {

fun onBind(item: MissionFeedResponse) {
binding.item = item
binding.executePendingBindings()

binding.itemMainImage.setOnClickListener {
onMainMissionItemClick.invoke(item.mission.id, item.mission.category.completeMessage)
}
}
}

ViewHolder에서 필자가 원하는 아이템에 setOnClickListener를 통해서 클릭 리스너를 달아준다. 그리고 그 안에서 onMainMissionItemClick.invoke()를 호출함으로써 넘겨받은 함수를 대신 호출할 수 있도록 해준다. invoke() 함수가 함수를 대신 호출하는 의미를 가진듯 하다.

그래서 여기서 호출하게 되면 MainFragment에서 정의했던 startToDetailActivity() 함수가 호출되고 원하는 동작을 할 수 있게 된다.

내가 원하는 값을 인자로 받는 부분은 ViewHolder에서 이루어지게 되고 실제 함수는 MainFragment에 존재한다. 그리고 호출도 이 함수를 호출하게 되는 것이다.

설명을 두서없이 하였다. 다음에 Kotlin의 사용자 정의 get(), set()을 통해 변경된 코드로 다시 한번 설명하도록 하겠다. 그 때는 더 깔끔하게 글을 쓰도록 할 예정이다. 마지막으로 이 글을 쓴 목적은 코틀린스럽게 코드를 작성하고자 하는 사람들이 영어로 된 자료말고 이 자료를 통해서 더 쉽게 해당 지식을 얻어갔으면 하는 바램에서 작성하게 되었다.