seoft

리사이클러뷰 보일러 플레이트 최소화 방안 (with Antonio) 본문

android

리사이클러뷰 보일러 플레이트 최소화 방안 (with Antonio)

seoft 2021. 11. 13. 20:07

개요

최근 들어 리사이클러뷰가 포함되지 않는 앱이 거의 없을 정도이고 앱의 요구사항이 복잡해지면서 멀티타입이나 중첩 구조도 쉽게 접할 수 있습니다. 리사이클러뷰의 구성을 위해서는 아답터와 뷰홀더도 함께 구성해야 하지만, 특이한 케이스가 아니라면 불필요한 보일러플레이트 코드로도 자리매김할 수 있어 보입니다.

이번 포스팅에서는 리사이클러뷰 구성시 아답터와 뷰홀더의 보일러플레이트를 최소화하는 방법과 관련된 라이브러리 또한 소개하고자 합니다.

 

 

 

샘플 프로젝트 소개

여러 방식 비교를 위해 리스트를 페이징 하는 간단한 샘플 프로젝트를 구성하였습니다. (링크)

 

설명을 덧붙이면 다음과 같습니다.

 - 동일 시나리오 세 가지 방식 구성
   - A. 일반적인 리사이클러뷰 구성
   - B. 바인딩을 통한 보일러플레이트 감소 방안의 리사이클러뷰 구성
   - C. Antonio 라이브러리를 사용한 리사이클러뷰 구성 (B방법에서 개선되고 다양한 기능들을 포함하고 있는 라이브러리)
 - MVVM 아키텍처와 연동하여 간단한 페이징 시나리오 구성
 - 세 개의 멀티타입 뷰홀더 구성

 

 

 

공통 구성

 

세 가지 방식에서 차이점은 아답터, 뷰홀더의 유무와 없을 경우에 대체를 위한 처리방안이 차이가 있고, 그 외에는 대부분 구성이 동일합니다. 다음 코드들은 세 가지 구성 중 동일하거나 비슷한 맥락을 가지고 있는 부분입니다.

 

Mock 데이터를 위한 인터페이스이며 서버 데이터라 가정하고 구성돼있습니다.

 

Mock 에서 발행되는 데이터를 각 구성의 Ui모델에 맞게 맵핑하는 역할을 합니다. 여기 코드는 같은 맥락이지만 각각 방식에 따라 다르게 구성되어있습니다.

 

뷰모델에서의 일부 코드입니다.

생성자로 MockApi 인터페이스의 실 구현체가 주입됩니다.

loadMoreUiModels 함수 내에서 Mock 을 통해 리스트를 받아 맵핑을 진행 후  uiModels LiveData로 결과가 구성됩니다.

 

엑티비티에서의 일부 코드입니다.

onCreate 혹은 페이징 로직에서 viewModel.loadMoreUiModels() 를 호출합니다

uiModels LiveData 를 옵저빙하고 있고 각 방식에 맞게 리사이클러뷰 출력을 진행합니다.

 

뷰홀더에서 터치 이벤트로 콜백을 처리하기 위한 인터페이스이며 각각 방식의 모델에 맞게 인자가 구성되어있습니다.

 

뷰홀더 아이템의 xml 파일 중 일부 파일입니다.

각각 방식에 따른 xml 들이 타입마다 3개씩 존재하며, variable 구성도 사용하는 모델과 인터페이스에 알맞게 구성되어있습니다.

 

밑에 세 가지 구성은 모두 해당 공통 구성에 따른 플로우를 따라가게 됩니다.

 

 

 

A. 일반적인 구성

 

일반적으로 리사이클러뷰를 구성할 때의 코드입니다. 뷰홀더가 존재하고 아답터에 포함되며, 아답터는 리사이클러뷰에 주입되어 사용됩니다.

 

일반적인 구성에서만 존재하는 아답터와 뷰홀더입니다.

onCreateViewHolder 함수와 뷰홀더 파일에서 비효율적으로 반복되는 코드가 존재합니다.

 

액티비티 일부 코드입니다.

생성한 NormalAdapter 의 submitList 를 통해 리스트를 출력합니다.

 

 

 

B. 바인딩을 통한 보일러플레이트 감소 방안의 리사이클러뷰 구성

 

보일러플레이트를 최소화하고자 관련된 유틸성 클래스가 사전에 준비되어있어야 합니다. (링크)

 

*A방식과 동일한 뷰모델, 모델, 맵퍼를 사용합니다

 

유틸성 클래스들로 유틸성 뷰홀더와 사용하여 구현된 아답터, UI모델을 layoutId와 매칭 시켜주는 모델로 구성되어 있습니다.

 

액티비티 일부 코드입니다.

위의 유틸클래스를 통해 아답터를 양식에 맞게 생성하며 옵저빙 하는 곳에서 submitList 직전 모델에 layout 을 매칭시켜주고 있습니다.

 

A방법과 비교하였을 때 아답터와 뷰홀더에 대한 코드에 대한 일부 매칭하는 부분만 작성해주면 되고 타입이 다양할수록 보일러플레이트는 감소합니다.

 

이렇게 구성이 가능한 이유는 xml에서 사용되는 variable의 변수 네임이 BR클래스로 할당되고 ViewDataBinding 의 setVariable 함수를 통해 BR클래스에 매칭되는 variable 변수에 특정 값을 넣을 수 있기 때문입니다.

A방법의 아답터의 onCreateViewHolder와 뷰홀더의 반복되는 코드들이 엑티비티에서 아답터 구성을 시작으로 뷰홀더의 xml 까지 딜리버리 되면서 대체되게 됩니다.

 

A방법의 아답터와 뷰홀더의 보일러플레이트는 많이 감소하였지만, BindingItem 클래스에 한번 감싸 져 있는 게 코드 사용처로부터 신경 쓰면서 구현해야 된다는 점과 submitList 직전 각 데이터 타입에 따라 전처리가 필요한 점은 아쉽습니다.

 

 

 

C. Antonio 라이브러리를 사용한 리사이클러뷰 구성

 

NaverZ 사에서 제공하는 리사이클러뷰, 뷰페이져 구성을 위한 오픈소스라이브러리입니다. 실제 메타버스 프로젝트인 ZEPETO 서비스에서 활용되는 것으로 알고 있습니다.

 

https://github.com/naverz/Antonio

 

GitHub - naverz/Antonio: Android library for the adapter view (RecyclerView, ViewPager, ViewPager2)

Android library for the adapter view (RecyclerView, ViewPager, ViewPager2) - GitHub - naverz/Antonio: Android library for the adapter view (RecyclerView, ViewPager, ViewPager2)

github.com

 

부분적으로 B방법에서 비슷한 맥락으로 구성되는 부분도 포함돼있지만 사용 방식이 훨씬 개선되었습니다.

뿐만 아니라 다음 장점들이 있습니다.

 - DiffUtil 을 통한 구성 외의 기존 notifyXXX 등 다양한 지원범위

 - 중첩의 중첩 관계에서 뷰 관리를 위한 LiveData 옵저빙 지원

 - LifeCycleOwner 자동 주입

 - annotation 을 통한 생성 자동화

 - 등등...

 

라이브러리 사용을 위해서 사전에 그래들 설정이 필요합니다

 

A, B방식과 다르게 UI모델에서 AntonioBindingModel 의 구현이 필요하며 여기서 사용할 xml과 UI모델이 매칭될 xml variable 변수를 결정합니다.

 

Antonio 에서는 리사이클러뷰 상태를 관리하는 고수준 클래스인 SubmittableRecyclerViewState 라는 개념이 사용되고 있고, 이는 DIffUtil 만 구성하여 손쉽게 구성할 수 있습니다.

setState 라는 확장함수를 통하 리사이클러뷰에 설정이 되며 사용방법은 동일하게 submitList로 사용할 수 있습니다.

 

B방식과 다르게 고수준으로 캡슐화돼있어 별도로 신경 쓸 사항 없이 보일러플레이트를 최소화하면서 리사이클러뷰를 구성할 수 있습니다.

 

내부 원리는........ 저는 아직 잘 이해를 하지 못한 상태입니다.

 

하지만 2021 드로이드나이츠에서 발표한 내용으로 Antonio 관련한 개요부터 발전단계와 내부 구성에 대한 세부 설명도 포함되어있으니 참고 바랍니다.

 

https://youtu.be/mqY3GWid7ug

 

 

 

결론

일반적으로 리사이클러뷰를 구성하는 방식과 보일러플레이트를 낮추는 전략, 관련된 라이브러리까지 함께 알아보았습니다. 리사이클러뷰나 뷰페이져의 특별한 구성이 필요할 경우에는 세부적인 커스텀을 위해서 직접 아답터와 뷰홀더를 구성하는 것도 필요하겠지만 대부분의 요구사항은 보일러 플레이트를 감소시키면서 구성할 수 있을 것 같습니다.

또한 프로젝트 구성에 따라 리사이클러뷰가 여러 페이지에서 반복되지 않는 상황이면 B, C방식을 이해하고 도입하는 버든이 더 들 수도 있어 보이지만, 일반적으로는 잘 사용될 수 있는 방안일 것 같습니다.

 

긴 글 읽어주셔서 감사합니다.

Comments