조언받고 비즈니스 로직이랑 뷰를 위한 클래스랑 나누면서 최대한 해봤는데 잘 맞는지 모르겠습니다.봐주세요...
Epoxy관련 코드들은 올리면 글자수가 오버되어 뺐습니다 혹시 필요하시면 사진으로..
궁금한것이.. ViewModel 클래스의 addDetail과 deleteDetail 함수에서 궁금한것이 있는데
1.먼저 addDetail()에서 방법1과 방법2로 Detail을 두가지 방법으로 추가할 수있는데요,
방법1같은 경우에는 비즈니스로직이 되는 모델 클래스에서 내부에서 상세 리스트를 가져와서
addDetail()에서 add를 사용하여 추가를 하는것이고
2. 방법2는 아예 addSubItem을 구현하여 RoutineModel 클래스 내에서 추가하는 방법입니다.
delteDetail()도 마찬가지로 ViewModel에서 삭제하는것과
Model 클래스의 함수에서 삭제하는 로직을 넣는것. 두가지중 어느것이 더 바람직한가요?,
두가지 다 실행결과 똑같이 정상 동작하기는 합니다...
나머지는 뭐 코드는 제대로 짜였나요..
RoutineModel
// 비지니스 로직에 사용될 Model 클래스
data class RoutineModel(
val id: String, // 고유 id (UUID 사용)
val workout: String, // 운동 종목
val unit: String, // 무게 단위 (kg or lbs)
private var routineDetail: ArrayList<RoutineDetailModel> = arrayListOf()
) {
init {
// 루틴 생성시과 동시에 1개의 상세 아이템을 가지고 있게 하기 위함
val detail = RoutineDetailModel("1","test","33")
routineDetail.toMutableList().add(detail)
}
fun getSubItemList() : ArrayList<RoutineDetailModel> = routineDetail
fun getSubItemSize() = routineDetail.size
fun addSubItem(item: RoutineDetailModel) { // 방법2
// 여기다가 구현하는것과 뷰모델에 구현하는것의 차이?
routineDetail.add(item)
}
fun deleteSubItem() {
// 여기다가 구현하는것과 뷰모델에 구현하는것의 차이?
}
}
RoutineItem
// 가공된 데이터를 RV(Epoxy)에 나열하기 위한 클래스
sealed class RoutineItem(
val id: String
) {
class RoutineModel(
id: String, // id
val workout: String, // 운동 종목
val unit: String, // 무게 단위 (kg or lbs)
// var routineDetail: List<DetailModel> = listOf() // 단순 RV에 보여주기 위한것이므로 여기서는 상세 프로퍼티가 필요없을듯
) : RoutineItem(id)
class DetailModel(
val set: String, // 세트
val reps: String = "1",
val weight: String
) : RoutineItem(set)
}
EpoxyController
class RoutineItemController(
private val addDetailClicked: (String) -> Unit,
private val deleteDeatailClicked: (String) -> Unit)
: EpoxyController() {
private var routineItem : List<RoutineItem>? = emptyList()
override fun buildModels() {
routineItem?.forEachIndexed { index, it -> // it == routineItem
when(it) {
is RoutineItem.RoutineModel ->
EpoxyRoutineModel_()
.id(index)
.pos(it.id)
.workout(it.workout)
.add_listener(addDetailClicked) // setter의 개념으로 초기화하는 듯.
.delete_listener(deleteDeatailClicked)
.addTo(this)
is RoutineItem.DetailModel ->
EpoxyDetailModel_()
.id(it.set)
.addTo(this)
}
}
}
fun setData(items: List<RoutineItem>) {
routineItem = items
requestModelBuild()
}
}
ViewModel
class WriteRoutineViewModel : ViewModel() {
private val _items: MutableLiveData<List<RoutineModel>> = MutableLiveData(listOf())
private val rmList = arrayListOf<RoutineModel>()
val items: LiveData<List<RoutineModel>> = _items // 읽기 전용
fun addRoutine(workout: String) {
val rmItem = RoutineModel(UUID.randomUUID().toString(), workout, "TEST")
rmItem.getSubItemList().add(RoutineDetailModel("2","3","3123"))
rmList.add(rmItem)
_items.postValue(rmList) // _items에 저장하며 값이 변화된것을 프래그먼트에서 observe 할수 있도록함
}
fun addDetail(pos: String) { // 눌려진 버튼 key 값에 알맞은 위치에 Detail 아이템 추가
// 모든 아이템은 id를 가지고 있고 고유하기때문에 non-null을 사용해도 될것같다
rmList.find { it.id == pos }!!.getSubItemList().add(RoutineDetailModel("3","3","23")) // 방법 1
// rmList.find { it.id == pos }!!.addSubItem(RoutineDetailModel("3","3","23")) // 방법 2
_items.postValue(rmList) // _items에 저장하며 값이 변화된것을 프래그먼트에서 observe 할수 있도록함
}
fun deleteDetail(pos: String) {
// 모든 아이템은 id를 가지고 있고 고유하기때문에 non-null을 사용해도 될 것 같다.
val item = rmList.find { it.id == pos }!!
when(item.getSubItemSize()) { // 상세 아이템의 수가 1개일때 삭제버튼을 누르면, RoutineModel까지 함께 삭제
1 -> rmList.remove(item)
else -> item.getSubItemList().removeLast()
}
_items.postValue(rmList) // _items에 저장하며 값이 변화된것을 프래그먼트에서 observe 할수 있도록함
}
// 뷰를 위한 데이터로 가공하기, 즉 RV에 뿌려질 데이터로 변경, _items의 변화가 감지되면 호출됨
fun getListItems() : List<RoutineItem> {
val listItems = arrayListOf<RoutineItem>()
for(testRM in rmList) {
listItems.add(RoutineItem.RoutineModel(testRM.id,testRM.workout,testRM.unit))
val childListItems = testRM.getSubItemList().map { detail ->
RoutineItem.DetailModel("2","23","55")
}
listItems.addAll(childListItems)
}
return listItems
}
}
Fragment
class WriteRoutineFragment : Fragment() {
private var _binding : FragmentWriteRoutineBinding? = null
private val binding get() = _binding!!
private val vm : WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
private lateinit var epoxyController : RoutineItemController
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWriteRoutineBinding.inflate(inflater, container, false)
epoxyController = RoutineItemController(::addDetail, ::deleteDetail)
binding.rv.adapter = epoxyController.adapter
binding.rv.itemAnimator = null // Epoxy 추가삭제 애니메이션 제거
binding.addExercise.setOnClickListener {
findNavController().navigate(R.id.action_writeRoutineFragment_to_workoutListTabFragment)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getTabPageResult() // Tab에서 받아온 정보를 토대로 Routine 추가
// RecyclerView(Epoxy) Update
vm.items.observe(viewLifecycleOwner) { updatedItems ->
epoxyController.setData(vm.getListItems())
}
}
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(key: String) {
vm.addDetail(key)
}
private fun deleteDetail(key: String) {
vm.deleteDetail(key)
}
}