질문하신 내용은 많은 초보분들이 지속적으로 하는 내용입니다. 먼저 이해하셔야 할 것은 서버나 데이터베이스같은 데이터소스에서 가져와야 하는 데이터와 화면에 보여줘야 하는 데이터의 형태가 다른 경우가 거의 대부분이라는 점을 염두에 두셔야 합니다. 따라서 다른 화면과의 커뮤니케이션은 이 데이터소스에 있는 데이터나 키값을 가지고 하도록 설계해야 님이 겪고 있는 문제점이 생기지 않습니다.
다른 한가지는 어댑터나 뷰홀더는 데이터를 보여주는 역할이 전부입니다. 여기에서 발생하는 이벤트, 아이템 클릭 같은 것들은 지금 님이 하신 것처럼 뷰홀더 내부에서 처리하시면 좋지 않습니다. 한가지 주된이유는 이 어댑터는 해당 화면에서만 사용이 가능하고 다른 화면에서는 사용할 수가 없습니다. 마치 텍스트뷰가 한 화면에서만 사용할 수 있는 것과 마찬가지가 됩니다. 따라서 이런 종속적인 코드는 밖으로 빼내는 것이 좋습니다. 그게 ItemClickListener 같은 인터페이스의 역할입니다.
해결방법은 어댑터에 아이템클릭에 대한 이벤트를 사용하는 겁니다.
먼저, 님이 어댑터의 아이템 클래스로 사용하고 계신 클래스를 Bar라고 칭하겠습니다. Bar 클래스를 Serializable 이나 Kotlin Parcelize 플러그인을 이용해서 Parcelable로 만드세요. 이건 추후에 화면 간에 데이터를 Bar 인스턴스로 넘기기 위한 겁니다.
이 부분도 초보 분들이 대부분 모르시는 내용인데, 안드로이드에서는 클래스 타입의 객체를 넘길 때는 Serializable이나 Parcelable이라는 인터페이스 타입을 사용합니다. 오브젝트를 압축해서 효율적으로 사용하기 위한 것이라고 보시면 됩니다.
class Bar : Serializable
@Parclelize
class Bar: Parcelable
class BarAdapter (
private val onBarClicked: (Bar) -> Unit
): RecyclerView.Adapter<BarAdapter.CustomerViewHolder> {
override fun onCreateViewHolder(....): BarAdapter.CustomerViewHolder {
val itemView = ...
return CustomerViewHolder(itemView, onBarClicked)
}
override fun onBindViewHoler(holder: BarAdapter.CustomerViewHolder, position: Int) {
val bar = barList[position]
holder.bind(bar);
}
class CustomerViewHolder(
itemView: View,
private val onBarClicked: (Bar) -> Unit) : RecyclerView.ViewHolder(itemView) {
// 기존에 onBindViewHolder에 있던 코드
fun bind(bar: Bar) {
barnum.text = bar.num
barPicture.setImageResource(...)
barName.text(...)
barInfo.text(...)
itemView.setOnClickListener { onBarClicked(bar) }
}
}
}
간단히 부연설명을 하면,
class BarAdapter (
private val onBarClicked: (Bar) -> Unit
): RecyclerView.Adapter<BarAdapter.CustomerViewHolder> {
BarAdapter에 onBarClicked라는 lambda 함수를 넘김니다. 코틀린 사용하고 계시므로, 람다함수는 아주 자주 쓰이기 때문에, 기본적으로 아시리라 생각합니다. 모르시면 공부를 하시길 권장합니다. 람다는 함수를 타입으로 만든 겁니다. 즉 Bar타입을 파라미터로 받아서 Unit을 리턴하는 함수라는 타입을 동적으로 선언한 겁니다. 이건 내부적으로 자바의 Function 인터페이스를 생성합니다.
class CustomerViewHolder(
itemView: View,
private val onBarClicked: (Bar) -> Unit) : RecyclerView.ViewHolder(itemView)
onBarClicked 람다를 onCreateViewHolder에서 넘겨줍니다. 님의 경우는 어댑터와 뷰홀더가 한 클래스에 있기 때문에 어댑터에 있는 onBarClicked를 직접 가져다 쓰셔도 될 것 같은데, 개인적으로는 선호하는 스타일이 아니라서 생성자에 넘겨주었습니다.
fun bind(bar: Bar) {
barnum.text = bar.num
barPicture.setImageResource(...)
barName.text(...)
barInfo.text(...)
itemView.setOnClickListener { onBarClicked(bar) }
}
어댑터에서 뷰홀더에 접근해서 바인딩을 하지말고 뷰홀더 내에서 하는 것이 더 좋은 접근방법입니다. 이유는 데이터를 바인딩하는 로직은 뷰홀더가 알아야하는 것이지, 어댑터가 알 필요가 없기 때문에 분리하는 것입니다. 자기의 역할이 아닌 것은 해당 역할을 하는 곳에 위치하는 것이 좋습니다. 이런 패턴을 Separation of Concern(관심사의 분리?)라고 부릅니다.
어댑터를 사용하는 곳에서는 onBarClicked를 넘겨주고 onBarClicked를 호출받으면 액티비티를 띄우면 됩니다.
class BarActivity : AppCompatActivity() {
private val barAdapter by lazy {
BarAdapter { bar -> barClicked(bar) }
}
override fun onCreate(...) {
super.onCreate(...)
setContentView(...)
setupRecyclerView()
}
private fun setupRecyclerView() {
recyclerView.adapter = barAdapter
}
private fun barClicked(bar: Bar) {
val intent = BarItemActivity.barItemIntent(bar)
startActivity(intent)
}
}
// 제가 볼 때는 이 클래스는 Activity이지 Item이 아니므로, BarActivityItem보다는 BarItemActivity가 더 적합한 이름입니다.
class BarItemActivity: AppCompatActivity() {
companion object
private val BAR_ITEM = "키값으로 아무 문자열이나 사용해도 됩니다"
fun barItemIntent(bar: Bar, newTask: Boolean = false): Intent {
// apply: Kotlin scope function. 모르시면 꼭 공부하시길 추천 함.
Intent(this, BarItemActivity::class.java).apply {
// 저는 Serializable 을 사용할게요. Parcelable도 별반 다르지 않습니다.
intent.putSerializable(BAR_ITEM, bar)
if (newTask) {
// Service, BroadcastReceiver 같은데서 액티비티를 띄울 때 필요한 Flag
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
}
}
}
// Kotlin Lazy Property :apply만큼 너무 자주 쓰이는 기본적인 코틀린 프로퍼티 읽는 방식
private val bar : Bar by lazy { intent.getSerializableExtra(BAR_ITEM) as Bar }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
bindBar()
}
private fun bindBar() {
barnum.text = bar.num
barPicture.setImageResource(...)
barName.text(...)
barInfo.text(...)
}
}
넘겨주는 쪽에서 Serializable을 넘겨주고 받는 쪽에서도 Serializable로 넘겨주면 됩니다. Parcelable과 Serializable 의 차이점은 Serializable 은 자바에서 지원하는 범용적인 타입이고 Parcelable 은 안드로이드 전용 타입인데, 복잡한 오브젝트의 경우는 Parcelable 이 퍼포먼스가 더 좋습니다. 위의 경우는 Serializable도 별반 차이가 없으므로 Serializable을 사용했습니다.