ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • '슥삭'의 아키텍쳐 (1) MVVM 디자인패턴
    안드로이드 2020. 3. 11. 22:13

     

    이번 포스팅에서는 내가 개발하고 있는‘슥삭’의 안드로이드 아키텍쳐에 대해서 설명을 해보려고 한다.

    👇아래는 해당 앱의 다운로드 링크 ㅎㅎ

     

    https://play.google.com/store/apps/details?id=com.icoo.ssgsag_android

     

     

    사실 나도 사전 공부할 틈 없이 바로 이 친구들과 부대꼈기 때문에 그냥 감으로 어떻게 쓰는건지 익힌 것 같다.

    디버깅하고 로그찍고 계속 빌드해보면서,,,

    이번 기회에 내 머릿속도 정리하고 부족한 개념도 익히는 겸! 블로그 포스팅을 하려고한다.

    그러니깐 틀린게 있으면 바로바로 태클 감사합니다.

     

     

    우선, 슥삭에서 쓰이는 것들을 정리하면 다음과 같다.

    1. MVVM 디자인 패턴 2. ViewModel(AAC) 3. RxJava 4. Live Data 5. DataBinding

     

    이번 포스팅에서는 먼저 MVVM 디자인 패턴에 대해 정리하고자 한다.

     


    👉MVVM 디자인 패턴?

    처음 안드로이드 개발을 시작한 개발자들이라면, 아마도 Activity나 Fragment에서 모든 로직을 처리하고, UI를 변경하고, 통신을 진행 할 것이다. 이런식으로 개발을 한다면 유지보수가 굉장히 힘들고, 코드는 스파게티처럼 꼬이기 쉽고, 데이터의 변화에 따라 UI를 처리하기가 힘들다.

    이러한 문제점을 해결하기 위한 하나의 방안으로 MVVM 디자인 패턴을 사용한다.

     

     

    MVVM은 Model+View+ViewModel를 합친 용어인데 각각의 역할은 다음과 같다.

     

    * Model:어플리케이션 내에서 사용되는 데이터와 그 데이터를 처리한다.

    (예> dataClass, Repository 등)

     

    * View: UI를 담당하는 역할을한다. 화면에 무엇을 그릴지 결정하고, 사용자와 상호작용하는 부분. 보통 (ViewModel의) 데이터 변화를 감지하기 위한 옵저버를 가지고 있다.(옵저버가 뭐지?🤷‍♀️)

    (예> Activity, Fragment 등)

     

    * ViewModel: UI를 위한 데이터를 가지고 있다. View를위한 Model 이기 때문에 ViewModel이라고 부른다.

     

     

     

     

    👉MVVM 디자인 패턴을 이용한 시스템의 흐름

    시스템의 흐름은 다음과 같다. ‘슥삭’으로 예를 들어보면,

    사용자가 어떤 아이템(피드)의 북마크 버튼을 눌러서 북마크 페이지에 추가하려고 한다.

     

     

    [View에서 일어나는 일]

    1. 사용자가 북마크 버튼을 누르면 ClickListener를 통해 viewModel의 bookmark() 함수를 호출한다.

    private val onFeedItemClickListener 
    	= object : FeedRecyclerViewAdapter.OnFeedItemClickListener { 
        	override fun onBookmarkClicked(feedIdx: Int, isSaved: Int, position: Int) { 
            	viewModel.bookmark(feedIdx, isSaved, position) 
            }
        }

     

     

    [ViewModel에서 일어나는 일]

    2. viewModel의 bookmark함수에서는 북마크 버튼을 누른 것이 북마크를 하려고 한 것인지, 북마크를 해제하려고 한 것인지에 따라 각각 repository(Model)의 bookmarkFeed()와 unbookmarkedFeed()함수를 호출한다.

    fun bookmark(idx: Int, isSave: Int, position: Int){ 
    	if(isSave == 0) { 
        	addDisposable(repository.bookmarkFeed(idx) 
            .subscribeOn(schedulerProvider.io()) 
            .observeOn(schedulerProvider.mainThread()) 
            .subscribe({ 
            	if(it == 200) { 
                	Toast.makeText(SsgSagApplication.getGlobalApplicationContext(), 
                    	"북마크에 추가되었습니다.",Toast.LENGTH_SHORT).show()
                        
                    refreshedFeedPosition = position 
                    refreshedFeedIdx = idx 
                    refreshFeed() 
                 }
             }, { 
             
             })
         	) 
         }else if(isSave == 1){ 
         	addDisposable(repository.unbookmarkFeed(idx) 
            .subscribeOn(schedulerProvider.io()) 
            .observeOn(schedulerProvider.mainThread()) 
            .doOnSubscribe { showProgress() } 
            .doOnTerminate { hideProgress() } 
            .subscribe({ 
            	if(it == 200) { 
                	Toast.makeText(SsgSagApplication.getGlobalApplicationContext(), 
                    "북마크에서 삭제되었습니다.",Toast.LENGTH_SHORT).show() 
                    
                    refreshedFeedPosition = position 
                    refreshedFeedIdx = idx 
                    refreshFeed() 
                 } 
              }, { 
              
              }) 
           ) 
        }else { 
        	return
        }
    }

     

     

    [Model에서 일어나는 일]

    3. FeedRepository에서 NetworkService(api)의 bookmarkFeed(), unbookmarkFeed()를 호출해서 통신을 진행한다.

    class FeedRepositoryImpl (val api: NetworkService, val pref: PreferenceManager) : FeedRepository { 
       override fun bookmarkFeed(feedIdx: Int): Single<Int> = api
               .bookmarkFeed(pref.findPreference("TOKEN", ""), feedIdx)
               .map { it.status }
    
        override fun unbookmarkFeed(feedIdx: Int): Single<Int> = api
            .unbookmarkFeed(pref.findPreference("TOKEN", ""), feedIdx)
            .map { it.status }
    }
    //피드 저장
    @POST("/feed/{feedIdx}")
    fun bookmarkFeed(
        @Header("Authorization") token: String,
        @Path("feedIdx") feedIdx : Int
    ): Single<NullDataResponse>
    
    //피드 저장 취소
    @DELETE("/feed/{feedIdx}")
    fun unbookmarkFeed(
        @Header("Authorization") token: String,
        @Path("feedIdx") feedIdx : Int
    ): Single<NullDataResponse>

     

     

    [ViewModel에서 일어나는 일]

    4. 2번에서 Model의 함수를 호출 한 뒤, 통신 response code가 200이면 토스트 메세지를 띄운다. 그리고 ViewModel 내에 있는 refreshFeed() 함수를 호출하는데, 이 함수는 통신을 진행 한 뒤 바뀐 변경사항을 View(UI)에 적용하기 위해서 북마크 버튼을 누른 feed의 데이터들을 다시 받아온다. (물론 데이터들을 다시 받아오는 일도 Model이 한다. 하지만, 위에서 Model이 어떤 일을 하는지 설명 했기 때문에 다시 Model로 넘어가는 설명은 생략하도록 하겠다.)

    private fun refreshFeed(){
             addDisposable(repository.getFeedRefresh(refreshedFeedIdx)
                 .subscribeOn(schedulerProvider.io())
                 .observeOn(schedulerProvider.mainThread())
                 .doOnSubscribe { showProgress() }
                 .doOnTerminate { hideProgress() }
                 .subscribe({
                     it.run {
                         _refreshedFeed.postValue(this)
                    }
                 }, {
              })
          )
     }

     

     

    [View에서 일어나는 일]

    5. 4번에서 refreshFeed의 값을 postValue를 통해 바꿔 주었다. 그러면 FeedActivity(View)에서 refreshFeed의 데이터의 변화를 관찰하고 있던 옵저버가, 데이터 변경이 일어나면 recyclerview의 item 값을 바꿔주고, recyclerview adapter에게 데이터가 바뀌었다고 알려준다.

     viewModel.refreshedFeed.observe(this, Observer {value ->
                (viewDataBinding.fragCategoryFeedsRv.adapter as? FeedRecyclerViewAdapter)?.run{
                    refreshItem(value, viewModel.refreshedFeedPosition)
                    notifyItemChanged(viewModel.refreshedFeedPosition)
    
                }
            })

     

     

     

    👉MVVM 디자인 패턴의 장점

    위와 같이 피드의 북마크를 누르면 북마크 처리를 하는 일을 MVVM 패턴을 이용해 프로그래밍 해보았다.

    이렇게 Model, View, ViewModel의 역할을 각각 나누어주면

     

    1. 앱 내에서 북마크 버튼을 누르는 모든 행동은 viewModel의 bookmark 함수로 처리해서 코드의 반복을 줄일 수 있고,

    2. 데이터가 변경되면 그 데이터의 변화를 감지하고 있던 activity의 옵저버가 데이터를 바로바로 바꿔줄 수 있으며,

    3. 피드와 관련된 통신은 feedRepository에 모아서 관리하므로 관련된 통신을 한눈에 볼 수 있어 유지보수가 쉽다.

     

     

     

     

     

     

    다음화 예고

    자, 이제 MVVM 패턴으로 프로그래밍 하는 법이 대충 무엇인지 느낌이 왔을 것이라고 생각한다.

    🤷‍♀️하지만 이 번 포스팅을 읽으면서 들었던 의문이 있었을 것이다. 🤷‍♀️

    옵저버는 무엇인가!

     

    이 친구들에 대한 설명은 다음 포스팅에 (이번 포스팅에서 설명하려했지만 못한) ViewModel과 RxJava, LiveData를 살펴보면서 설명하겠다,

     

     

     

     

    첨언: 사실 MVVM을 쓰는 중요한 목적 중에 하나는 테스팅 때문이라던데,, 이 부분은 잘 모르겠다. 갈 길이 멀다.

     

     

     

     

Designed by Tistory.