Gemstone's Devlog

[Android] MVP 디자인패턴 정리 본문

Kotlin (Android)

[Android] MVP 디자인패턴 정리

Gemstone 2022. 7. 13. 11:48

MVP 패턴이란?

 

Model View Presenter 패턴은 MVC(Model View Controller) 패턴을 기반으로 하는 아키텍처 패턴으로 관심사의 분리를 높이고 단위 테스트를 용이하게 합니다.

 

요약 : MVC 패턴에서 View와 Model의 의존성을 없애고 단위 테스트가 어려웠던 문제점을 해결하기 위해 등장하게 된 패턴이라고 할 수 있습니다.

 

Model

앱에 사용되는 데이터를 관리 담당하는 역할을 한다. 흔히 '비즈니스 로직'이라고 부르는 부분입니다.

모델에는 Network API, 데이터 캐싱, 데이터베이스 등이 포함되고, Repository Pattern을 사용하는 경우 Repository도 포함됩니다.

 

View

사용자 인터페이스 영역이며, Activity, Fragment 등이 포함되고 데이터를 표시하는 역할만 합니다. 오직 Presenter를 통해서 데이터를 요청하고 전달 받기 때문에 Presenter에 의존적입니다.

 

Presenter

View와 Model 사이 중개자 역할을 담당합니다. View에서 사용자 이벤트를 전달 받아 Model에 데이터를 요청하고 전달받은 데이터를 View에 그대로 전달합니다.

(MVVM의 ViewModel과 비교했을 때 ViewModel은 View를 알지 못하고 View에 대한 참조를 가지고 있지 않습니다. 하지만 Presenter는 View와 Model 모두 참조하고 있습니다.)

 

👇 구글 MVP 아키텍처 템플릿 👇

https://github.com/android/architecture-samples/tree/todo-mvp-kotlin

 

GitHub - android/architecture-samples: A collection of samples to discuss and showcase different architectural tools and pattern

A collection of samples to discuss and showcase different architectural tools and patterns for Android apps. - GitHub - android/architecture-samples: A collection of samples to discuss and showcase...

github.com

 

간단한 예제

1. Contract Interface 생성

Presenter와 View 사이에 어떤 기능이 있는지 한눈에 파악할 수 있도록 명시하는 역할 

(그렇지만 Contract는 MVP의 필수요소는 아닙니다)

interface MainContract {

    interface View : BaseView<Presenter> {
        fun showProgress(isShow: Boolean)
        fun setData(str: String)
    }

    interface Presenter : BasePresenter

}

interface BasePresenter {
    fun start()
}

interface BaseView<T> {
    var presenter: T
}

 

2. Presenter Class 생성

Presenter Class에서는 Contract의 Presenter Interface를 구현하고,

Model(여기서는 Repository)과 View를 생성자 매개변수로 받습니다.

class MainPresenter(
    val mainRepository: MainRepository,
    val mainContractView: MainContract.View
) : MainContract.Presenter {

    init {
        mainContractView.presenter = this
    }

    override fun start() {
        mainContractView.showProgress(false)
        val data = mainRepository.getData()
        mainContractView.setData(data)
        mainContractView.showProgress(true)
    }

}

 

3. Model Class 생성

Model은 일반적으로 server 또는 local database에서 데이터를 가져오지만 예제이기 때문에 간단하게 작성하였다.

여기선 Repository를 Model이라고 했지만 Repository Pattern에 따르면 Repository는 여러 개의 datasource(local, remote, database) 에서 필요한 데이터를 선택해 가져오는 Presenter와 Model 사이의 중개자 역할입니다.

또 한가지 object 클래스로 작성했는데 Repository는 singleton 이어야 합니다.

object MainRepository {

    fun getData() = "Hello World"
}

 

Activity에서의 사용 예제

class MainActivity : AppCompatActivity(), MainContract.View {

    private val textView: TextView by lazy {
        findViewById(R.id.textView)
    }

    private val button: Button by lazy {
        findViewById(R.id.button)
    }

    override fun showProgress(isShow: Boolean) {
        textView.isVisible = isShow
    }

    override fun setData(str: String) {
        textView.text = str
    }

    override lateinit var presenter: MainContract.Presenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        MainPresenter(MainRepository, this)
        button.setOnClickListener {
            presenter.start()
        }
    }

}

 

동작 과정

1. MainContract.View를 구현해 presenter와 showProgress(), setData() 함수를 override 해줍니다.

2. MainPresenter 클래스를 인스턴스화 합니다. 생성자 매개변수로 Model과 현재 Activity에 MainPresenter.View를 구현했기 때문에 this를 넣어줍니다.

3. button 클릭시 Presenter의 start() 함수를 호출합니다.

4. Presenter는 View로부터 이벤트를 전달받고 Model에서 데이터를 가져와 다시 View에게 전달해줍니다. (전달은 setData() 함수 호출을 뜻합니다.)

5. Activity에서 setData() 함수로 데이터가 들어오게 되고 textView에 데이터를 표시합니다.

 

 

Unit Test (단위 테스트)

MVP 패턴은 단위테스트를 더 쉽게 작성할 수 있다는 장점이 있는데, 위에서 작성한 Presenter의 테스트를 통해 어떻게 하는지 보여드리겠습니다.

 

Mock 객체를 만들기 위한 라이브러리로 dependencies에 추가해 줍니다.

testImplementation "org.mockito:mockito-core:3.5.13"
class MainPresenterTest {

    @Mock private lateinit var mainRepository: MainRepository
    @Mock private lateinit var mainContractView: MainContract.View

    private lateinit var mainPresenter: MainPresenter

    @Before
    fun setupPresenter() {
        MockitoAnnotations.openMocks(this)
        mainPresenter = MainPresenter(mainRepository, mainContractView)
    }

    @Test fun createPresenter_setsThePresenterToView() {
        mainPresenter = MainPresenter(mainRepository, mainContractView)
        verify(mainContractView).presenter = mainPresenter
    }

    @Test
    fun start() {
        mainPresenter.start()

        verify(mainContractView).showProgress(false)
        verify(mainRepository).getData()
        verify(mainContractView).setData(mainRepository.getData())
        verify(mainContractView).showProgress(true)
    }

}

 

@Mock 어노테이션은 가짜 객체를 생성합니다.

@Before 어노테이션은 @Test가 실행되기 전 실행되어 객체를 초기화할 때 사용됩니다.

@Test 어노테이션은 테스트 케이스로 실행될 수 있음을 나타냅니다.

 

@Before가 붙은 setupPresenter() 에서는 @Mock 어노테이션을 사용한 멤버변수들을 생성하고 presenter 객체를 생성합니다.

@Test의 createPresenter_setsThePresenterToView() 에서는 MainPresenter 클래스를 생성했을때 View에 Presenter가 주입되는지 검증하는 테스트 케이스입니다.

@Test의 start() 에서는 Presenter의 start()를 호출하고 Presenter의 start()에 구현되어있는 동작들이 각각 제대로 호출되고 있는지 검증하는 테스트 케이스 입니다.

 

끝으로 MVP 구현 시 안드로이드의 Lifecycle에 영향을 받을 수 밖에 없는데, 만약 View에서 Presenter를 호출하고 View가 destroy 된다면 Presenter는 Model에서 가져온 데이터를 View에 전달해야 하는데, 데이터를 받을 View가 없다면 안되겠죠? 그래서 Presenter에 attach(), detach() 같은 함수를 정의하고 Activity Lifecycle에 맞춰 View객체를 넣거나 null로 해제시켜 줍니다. 그리고 Presenter에서는 View의 함수를 호출하기 전에 View객체의 null 체크를 해주는 방법을 사용할 수 있습니다. 그리고 화면 회전이 발생했을 때 Activity가 재생성되어 보여지고 있던 데이터를 잃어버리기 때문에 이를 caching 해야 합니다. 그래서 Repository를 singleton으로 구성해야 객체가 그대로 남아있어 화면 회전에도 대응할 수 있습니다.

 

https://youngest-programming.tistory.com/111

 

[안드로이드] MVP 디자인패턴 정리 및 예제

[2021-04-13 업데이트] 프로젝트에 MVC 아키텍처만 사용하다가 최근 간단한 공부용 프로젝트를 통해 MVP 아키텍처를 적용해보고있다. 확실히 기존 MVC 구조보다 코드가 정리되는 느낌이 들었다. MVP구

youngest-programming.tistory.com

 

https://velog.io/@bang/Android-MVP-pattern-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0

 

Android MVP pattern 살펴보기

MVP 패턴이란? > Model View Presenter 패턴은 MVC(Model View Controller) 패턴을 기반으로 하는 아키텍처 패턴으로 관심사의 분리 를 높이고 단위 테스트를 용이하게 합니다. > 요약: MVC 패턴에서 View와 Model의

velog.io