일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Android mvp
- 백준 2096
- 안드로이드 hilt
- Android Room
- 5582 파이썬
- 5582 DP
- 1806 투포인터
- 1753 파이썬
- 10819 파이썬
- 이진 탐색
- flow buffering
- 1806 파이썬
- 자바
- 1003 파이썬
- 백준 10819
- 1644 파이썬
- Coroutine Flow
- 1753 다익스트라
- 6588 파이썬
- 코루틴 플로우
- 2096 파이썬
- java
- 자료구조
- 백준 1644
- git local remote
- 1806 백준
- 백준 5582
- 투포인터 알고리즘
- Jetpack Room
- android hilt
- Today
- Total
Gemstone's Devlog
[Android] MVP 디자인패턴 정리 본문
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
간단한 예제
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
https://velog.io/@bang/Android-MVP-pattern-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0
'Kotlin (Android)' 카테고리의 다른 글
[Kotlin] Sealed Class, Generics Class 정리 (0) | 2022.07.02 |
---|---|
[Coroutine] Atmoic Variables (0) | 2022.07.01 |
[Coroutine Flow] Buffering (0) | 2022.06.30 |
Room 지속성 라이브러리 공부! (0) | 2022.06.28 |
[Coroutine Flow] 콜백 기반 api -> flow 변경 방법 (0) | 2022.06.24 |