Imgur: The magic of the Internet
버튼을 누르면 동적으로 리사이클러뷰 아이템이 추가됩니다.
이 추가된 하나의 아이템은 내부에 상세정보를 담고있는 리사이클러뷰가 하나 더 있는
중첩리사이클러뷰 형태입니다. (사진 참조)
중첩리사이클러뷰아이템(이하 자식아이템)도 아이템을 추가 및삭제 가능합니다.
MVVM의 패턴을 적용하려고 하고있습니다. 그리고 아직 DB는 로컬 DB를 사용할지 서버를 사용할지
파이어베이스를 사용할지 결정을 못했습니다..
아무튼 결과만 말씀드리면 일단 동작은 잘합니다. 부모 아이템의 추가 및 삭제도 잘되고
자식 아이템도 추가 및 삭제가 잘됩니다.
그런데 자식 아이템을 추가하는 과정에서의 방식이 올바른지 모르겠습니다.
처음에 자식 아이템의 추가,삭제 코드(addDetail,deleteDetail)를 짰을때 아무 반응이 없길래 왜 그런가 했는데,
프래그먼트에서 items를 observe하는데 items의 변화를 관찰하는게 items의 추가/삭제에 관해서만
관찰을 하더라구요..즉 viewmodel 클래스의 addDetail에서 진행하는 해당 표지션의 부모 아이템의 자식 아이템 추가는 관찰하지 못해서 아무런 반응이 없었어요..
그래서 이걸 강제로 observe 시키기 위해서 _items.value = _items.value 라는 코드를 넣어
프래그먼트에서 강제로 items를 관찰하도록 했더니 잘 동작하더라구요..
_items.value = _items.value 코드가 예전에 스택오버플로우에서 모르는거 검색하다가 추가한 코드인데..
지금은 삭제하니 잘돼서.. 뭐때문에 추가했는지 기억은 잘안납니다.
아무튼 이게 과연 올바른 방식인지 모르겠습니다.. 조언좀 부탁드려요
부모아이템.kt
data class RoutineModel(
val workout: String, // 운동 종목
val unit: String, // 무게 단위 (kg or lbs)
var routineDetail: ArrayList<RoutineDetailModel> = arrayListOf()
) {
init {
// 루틴 생성시 최소 1개의 상세 아이템을 가지고 있게 하기 위함
val detail = RoutineDetailModel("10","test")
routineDetail.add(detail)
}
fun getSubItemList() : ArrayList<RoutineDetailModel> = routineDetail
fun getSubItemSize() = routineDetail.size
fun addSubItem(item: RoutineDetailModel) {
routineDetail.add(item)
}
fun deleteSubItem() {
routineDetail.removeLast()
}
}
Fragment
class WriteRoutineFragment : Fragment() {
private lateinit var adapter : RoutineAdapter
private val vm : WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWriteRoutineBinding.inflate(inflater, container, false)
adapter = RoutineAdapter(::addDetail, ::deleteDetail)
binding.rv.adapter = this.adapter
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// LiveData를 observe()하는 것이므로 onViewCreated()가 적합한 위치
getTabPageResult()
// 추가된 리사이클러뷰 아이템 업데이트,
// 테스트위해 notifyDataSetChanged() 사용 추후 DiffUtil로 변경 예정
vm.items.observe(viewLifecycleOwner) { updatedItems ->
adapter.setItems(updatedItems)
}
}
private fun getTabPageResult() {
val navController = findNavController()
navController.currentBackStackEntry?.also { stack ->
stack.savedStateHandle.getLiveData<String>("workout")?.observe(
viewLifecycleOwner, Observer { result ->
vm.addRoutine(result) // 루틴 추가
stack.savedStateHandle?.remove<String>("workout")
}
)
}
}
private fun addDetail(pos: Int) {
vm.addDetail(pos) // 상세추가
}
private fun deleteDetail(pos: Int) {
vm.deleteDetail(pos) // 상세삭제
}
}
ViewModel
class WriteRoutineViewModel : ViewModel() {
private var _items: MutableLiveData<ArrayList<RoutineModel>> = MutableLiveData(arrayListOf())
val items: LiveData<ArrayList<RoutineModel>> = _items
fun addRoutine(workout: String) {
val item = RoutineModel(workout, "TEST")
_items.value?.add(item)
// _items.value = _items.value
}
fun addDetail(pos: Int) {
val detail = RoutineDetailModel("TEST", "TEST")
_items.value?.get(pos)?.addSubItem(detail) // 부모아이템의 데이터만 변화시켜서는 관찰하지못함
_items.value = _items.value // 올바른 방식인가
}
fun deleteDetail(pos: Int) {
if(_items.value?.get(pos)?.getSubItemSize()!! > 1) // Detail 아이템이 1일경우에는 Routine을 삭제
_items.value?.get(pos)?.deleteSubItem() // 올바른 방식인가
else
_items.value?.removeAt(pos)
_items.value = _items.value // 올바른 방식인가
}
}
Adapter
class RoutineAdapter(val addDetailClicked: (Int) -> Unit,
val deleteDetailClicked: (Int) -> Unit)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items: ArrayList<RoutineModel> = arrayListOf()
private val viewPool = RecyclerView.RecycledViewPool()
companion object {
private const val TYPE_ROUTINE = 1
private const val TYPE_ROUTINE_FOOTER = 2
}
fun setItems(items: ArrayList<RoutineModel>) {
this.items = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
lateinit var itemView: View
return when (viewType) {
TYPE_ROUTINE -> {
val binding = ItemRoutineBinding.inflate(LayoutInflater.from(parent.context), parent, false)
RoutineViewHolder(binding) // return
}
else -> {
itemView = LayoutInflater.from(parent.context).inflate(R.layout.add_routine_button, parent, false)
RoutineFooterViewHolder(itemView) // return
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(getItemViewType(position)) {
TYPE_ROUTINE -> {
val item = items[position]
holder as RoutineViewHolder
holder.bind(items[position])
initDetailLayoutManager(holder, item, position)
}
else -> holder as RoutineFooterViewHolder
}
}
override fun getItemViewType(position: Int): Int {
return when(position) {
items.size -> TYPE_ROUTINE_FOOTER
else -> TYPE_ROUTINE
}
}
override fun getItemCount(): Int = items.size + 1 // footer 때문에 +1
private fun initDetailLayoutManager(holder : RoutineViewHolder, item: RoutineModel, pos: Int) {
val lm = LinearLayoutManager( // Detail RV를 위한 lm
holder.subRV.context,
LinearLayoutManager.VERTICAL,
false
)
lm.initialPrefetchItemCount = item.getSubItemList().size
val detailAdapter = RoutineDetailAdapter(items[pos].getSubItemList())
holder.subRV.layoutManager = lm
holder.subRV.adapter = detailAdapter
holder.subRV.setRecycledViewPool(viewPool)
}
inner class RoutineViewHolder(private val binding: ItemRoutineBinding) : RecyclerView.ViewHolder(binding.root) {
val subRV = binding.rv
init {
binding.add.setOnClickListener {
addDetailClicked(adapterPosition)
}
binding.delete.setOnClickListener {
deleteDetailClicked(adapterPosition)
}
}
fun bind(list: RoutineModel) {
binding.workout.text = list.workout
}
}
inner class RoutineFooterViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
private val button: ConstraintLayout = itemView.findViewById(R.id.add_routine)
init {
button.setOnClickListener { view ->
view.findNavController().navigate(R.id.action_writeRoutineFragment_to_workoutListTabFragment)
}
}
}
}