[팀 프로젝트] 첫 코드 리뷰 (1)

HEETAE HEO·2022년 1월 26일
0

TeamProject

목록 보기
1/3
post-custom-banner

팀 프로젝트의 시작 및 진행과정

Team 박카스는 5명으로 구성되어 있습니다. 현재 진행중인 프로젝트는 MVVM패턴을 이용한 동네마켓 배달 어플리케이션을 만드는 것입니다. 사용하고 있는 언어는 Kotlin이며 각각 일정 부분을 맡아 일주일뒤 발표 예정자는 자신이 작성한 코드를 공유를 하고 나머지 조원들은 다시 일주일이라는 기간 동안 공유 받은 코드를 공부하며 궁금한 것이 생기거나 보완이 필요하다고 생각되는 부분을 작성자와 이야기 하여 수정을 합니다. 일주일이 끝나면 발표자는 처음에 작성한 코드를 설명한 후 피드백을 통해 개선된 부분을 설명함으로써 모두가 코드를 이해하고 숙지할 수 있도록 하였습니다.

Model 구현

BaseModel

abstract class Model(
	open val id: Long,
    open val type: CellType
){
	companion object {
    	val DIFF_CALLBACK = object : DiffUtil.ItemCallback <Model> (){
  			override fun areItemsTheSame(oldItem:Model, newItem:Model): Boolean{
  			return oldItem.id == newItem.id && oldItem.type == newItem.type
  		}  
  @SuppressLint("DiffUtilEquals")
            override fun areContentsTheSame(oldItem: Model, newItem: Model): Boolean {
                return oldItem == newItem 

위에 작성된 Model 클래스는 는 다른 Model클래스들의 기반이 될 추상클래스로 실시간으로 데이터의 변경을 관찰하고 변경되는 부분을 업데이트 해 주기 위해 DiffUtil.ItemCallback을 사용한다.

DiffUtil이란?

DiffUtil은 ListItem이 변경되었을 때 변경전의 Item과 비교하여 변경된 부분 만을 업데이트 작업을 수행하는 유틸리티 클래스이다. 만약 DiffUtil을 사용하지 않는다면 데이터의변경을 반영하기 위해서 notifyDataSetChanged() 명령어를 이용하여 업데이트를 해주는 과정을 거쳐야합니다.
하지만 notifyDataSetChanged()명령어는 DiffUtil과는 다르게 변경된 부분만을 업데이트하는 것이 아닌 해당 리스트를 처음부터 재생성하기때문에 데이터의 변경이 일어날때 마다 리스트의 삭제와 생성이 반복되어 굉장히 비효율적인 동작을 하게된다. 그렇기에 데이터의 변경이 자주 일어나는 ListItem을 사용할 때는 DiffUtil을 사용하는게 도움이 됩니다.

companion object안의 코드의 동작 순서를 보면
1. 기존에 있던 item의 id와 type가 같은지를 비교한다. 만약 다른 부분이 이때 발생하게 된다면 ViewHolder에서 부터 새로 생성한다. 같다면 True를 반환한다.

  1. Contents의 내용을 비교한다 즉 해당 Item의 리뷰의 수나 배달 시간 또는 상품의 갯수와 같은 것들을 비교하고 변경되는 것들이 있다면 해당되는 Contents만 변경시켜준다.
 data class TownMarketModel(
    override val id: Long,
    val marketName: String,
    val isMarketOpen: Boolean,
    val locationLatLngEntity: LocationLatLngEntity,
    val marketImageUrl: String,
    val distance: Float, 
    override val type: CellType = CellType.HOME_TOWN_MARKET_CELL
) : Model(id, type)
 data class HomeItemModel(
    override val id: Long,
    val homeListCategory: HomeListCategory,
    val homeListDetailCategory: HomeListDetailCategory,
    val itemImageUrl: String,
    val townMarketModel: TownMarketModel,
    val itemName: String,
    val originalPrice: Int,
    val salePrice: Int,
    val stockQuantity: Int,
    val likeQuantity: Int,
    val reviewQuantity: Int,
    override val type: CellType = CellType.HOME_ITEM_CELL
): Model(id, type) {

앞의 Model클래스를 상속받아 작성한 HomeItemModel과 TownMarktetModel입니다.
단순히 사용될 변수들만 선언을 했기 때문에 이 클래스들에 대한 설명은 넘어가겠습니다.

HomeListFragment

private val resourcesProvider by inject<ResourceProvider>()

HomeListFragment 부분에서 이러한 변수 선언이 되어있다. 나는 발표자의 코드를 받아 공부할 때 ResourceProvider interface를 왜 inject을 하는지 궁금하였고 리뷰를 할때 물어봐 이유를 알아내었다. 어떠한 부분을 구현할 때 기능에 해당되는 class를 메모리에 할당하고 그리고 적용되어진다고 하였다. 이러한 작업들이 반복적으로 일어나게 된다면 오버헤드가 일어나게 되고 메모리의 효율적인 부분에서도 좋지않아 해당 데이터부분에 의존성을 주입하고 해당하는 데이터를 처음부터 interface 형식으로 메모리에 올려놔 그 값을 필요로할때 메모리로 할당하는 과정을 없애고 바로 적용할 수있도록 했다는 것이였다. appModule쪽에서 의존성 주입한 부분을 다시 보고 공부하였다.

DI를 주입할때 위의 이미지 처럼 싱글톤으로 생성을 해준다. DefualtResourceProvider의 매개변수를 왜 androidContext()로 해놓았냐라는 의문점을 가질 수 있는데 특정 Activity의 Context를 주어주게 된다면 Activity가 onDestroy되었을때 의존성 또한 같이 종료되어 사용할 수 없기 때문에 Context의 최상 Context라고 할 수 있는 androidContext()를 매개변수로 주어 DI를 해준다.

ModelRecyclerAdapter

> class ModelRecyclerAdapter<M : Model, VM : BaseViewModel>(
  private var modelList: List<Model>,
  private val viewModel: VM,
  private val resourcesProvider: ResourcesProvider,
  private val adapterListener: AdapterListener
) : ListAdapter<Model, ModelViewHolder<M>>(Model.DIFF_CALLBACK) { override fun getItemViewType(position: Int): Int = modelList[position].type.ordinal
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModelViewHolder<M> {
      return ViewHolderMapper.map(parent, CellType.values()[viewType], viewModel, resourcesProvider)
  }
  @Suppress("UNCHECKED_CAST")
  override fun onBindViewHolder(holder: ModelViewHolder<M>, position: Int) {
      holder.bindData(modelList[position] as M)
      holder.bindViews(modelList[position] as M, adapterListener)
  }  override fun submitList(list: List<Model>?) {
      list?.let { modelList = it }
      super.submitList(list)
  }

ModelRecyclerAdapter에서 이 글의 초반부에 설명했던 DiffUtil이 사용된다.
생성자 파라미터로 DiffUtil을 받아 해당 Recycler의 값의 변화가 생긴다면 바로바로 업데이트를 해주어 최신 정보를 사용자에게 보여주게 된다.

느낀점

일주일이라는 기간 동안 다른 조원의 코드에 대한 공부와 피드백을 줌으로써 코드의 수정과 보완작업을 반복했고 이 과정이 모두가 코드에 대한 이해도를 높이는 결과를 만든 것 같다.
Team 박카스의 팀프로젝트에서 최대한 많은 것을 배워가고 알아가는 시간이 되었으면 한다.

다음 2편으로 코드리뷰 다음 내용을 작성하도록 하겠습니다. 읽어주셔서 감사합니다.

profile
Android 개발 잘하고 싶어요!!!
post-custom-banner

0개의 댓글