일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |
- 클래스
- Rxjava Observable
- 멤버변수
- 안드로이드 os 구조
- 서비스 vs 쓰레드
- 자바 다형성
- 안드로이드 라이선스 종류
- 2022 플러터 안드로이드 스튜디오
- rxjava hot observable
- 2022 플러터 설치
- 안드로이드 라이선스
- 스택 자바 코드
- 안드로이드 유닛 테스트 예시
- 안드로이드 유닛테스트란
- 플러터 설치 2022
- 안드로이드 유닛 테스트
- 스택 큐 차이
- rxjava cold observable
- 객체
- ar vr 차이
- jvm이란
- android ar 개발
- rxjava disposable
- ANR이란
- android retrofit login
- 서비스 쓰레드 차이
- 안드로이드 레트로핏 crud
- jvm 작동 원리
- 큐 자바 코드
- 안드로이드 레트로핏 사용법
- Today
- Total
나만을 위한 블로그
[Android] MVP vs MVVM 본문
안드로이드에서 MVC 패턴을 사용하는 방법은 생략한다. 어차피 MVC 형태로 만들다 보면 그 결과로 발생하는 스파게티 코드와 뚱뚱한 액티비티, 프래그먼트를 보는 게 역겨워져서 MVP든 MVVM이든 찾게 될 것이다.
먼저 MVP 패턴이다. MVP 패턴은 Model, View, Presenter 3개 단어의 앞글자를 따서 만든 것으로 사용자가 View를 통해 어떤 이벤트를 일으키면 Presenter가 Model에 접근해서 View 대신 데이터를 갖다 준다. 즉 Presenter는 View, Model 사이에 위치해서 View가 원하는 데이터를 갖다 주는 역할을 한다. View는 이 데이터를 통해 UI를 업데이트하게 된다.
긴 말 필요없고 코드를 보자. 레트로핏을 통한 서버 통신 로직과 DI 처리, Model 클래스 작성은 생략하고 MVP스러워 보이게 짠 예제기 때문에 이걸 그대로 쓰기보단 코드 흐름에 집중하자.
먼저 인터페이스다. 네이밍은 보통 어떻게 하는지 모르겠지만 찾아본 결과 Contract가 가장 많아서 접미어를 Contract로 설정했다.
interface ViewActionContract {
fun updateUI(message: String)
fun getUserInformation()
}
Contract에 정의한 추상 함수들은 Presenter에서 재정의할 것이다. 이 때 UI 수정 로직을 Presenter에 구현하기도 하는데 액티비티, 프래그먼트에서 구현하는 편이 코드를 읽을 때 자연스럽기 때문에 뷰에서 구현하기도 한다. 강제되는 것은 아니니 이 부분은 자기가 만드는 앱의 경우 어떤 방법으로 짜야 코드가 읽기 쉬워지는지 고민하고 적용하자.
다음은 Contract 인터페이스를 구현할 클래스다.
import android.content.Context
import android.widget.Toast
class ViewActionPresenter(
private val context: Context
): ViewActionContract {
override fun updateUI(message: String) {
Toast.makeText(context, "updateUI() 호출. message : $message", Toast.LENGTH_SHORT).show()
}
override fun getUserInformation() {
Toast.makeText(context, "getUserInformation() 호출", Toast.LENGTH_SHORT).show()
}
}
토스트를 띄울 것이기 때문에 Context가 필요하다. 그래서 생성자로 Context를 받도록 했지만 이것이 좋은 방법인지는 확인하지 않고 사용했으니 실제 코드 작성에 참고할 경우 다른 문서나 블로그들을 참고하자.
위에서 만든 Contract 인터페이스를 구현하고 인터페이스에 만든 함수 2개들을 재정의한다. 그리고 액티비티를 만들어준다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".mvp.ViewActionActivity">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="버튼"
android:layout_marginBottom="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.kotlinprac.databinding.ActivityViewActionBinding
class ViewActionActivity : AppCompatActivity() {
private lateinit var presenter: ViewActionPresenter
private val binding: ActivityViewActionBinding by lazy {
ActivityViewActionBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
init()
}
private fun init() {
presenter = ViewActionPresenter(this)
presenter.getUserInformation()
binding.run {
button.setOnClickListener {
presenter.updateUI("호출")
}
}
}
}
뷰에선 Presenter를 구현한 다음 화면에 진입하거나 버튼을 눌렀을 때 알맞은 함수를 호출하게 한다. 이것이 MVP 패턴을 적용한 경우 나타나는 소스코드 형태다.
여기까지 작성했을 때 패키지 안에는 3가지 코틀린 파일이 만들어져 있을 것이다.
인터페이스의 경우 여러 액티비티를 만들더라도 쓰는 함수가 같다면 새로 만들 필요는 없겠지만 Presenter는 다르다. 한 뷰에 사용한 Presenter를 다른 뷰에서 사용할 수 있게 만들기는 어렵거나 애매하기 때문에 보통 하나의 액티비티(프래그먼트)에서 Presenter를 인스턴스화해서 사용하게 된다. 즉 View와 Presenter는 1:1 대응 관계를 갖게 된다.
MVC 패턴에 비해 비즈니스 로직이 분리된 것이야 좋은 일이지만 서버 통신 로직이 추가된다면 View -> Presenter -> View 순서로 왔다갔다하면서 코드 흐름을 파악해야 할 수도 있고, 1:1 대응 관계기 때문에 화면 하나 새로 만들 때마다 Presenter도 새로 만들어야 할 가능성이 높다. 즉 프로젝트 규모가 커질수록 패키지 안의 파일 개수들도 덩달아 많아진다.
그리고 뷰모델이 없기 때문에 안드로이드 생명주기에 따른 처리를 좀 더 세세하게 해 줘야 한다.
위와 같은 형태로 MVVM 패턴을 구성하더라도 뷰모델 팩토리를 만들지 않는 이상 코드 형태는 별로 바뀌지 않는다. 그러나 뷰모델을 사용하게 됨으로써 개발자는 생명주기의 압박에서 다소 자유로워질 수 있고 하나의 뷰모델을 여러 액티비티(프래그먼트)에서 돌려쓸 수 있으니 MVP에 비해 상대적으로 뷰와 뷰모델 간 결합이 느슨해져 1:N 대응 관계가 성립된다.
그러나 데이터바인딩과 LiveData, Flow를 사용해야 MVVM스러운 코드가 만들어지기 때문에 러닝커브가 높다. 그리고 뷰모델에서 처리한 데이터들을 뷰에 표시하기 위해 데이터바인딩을 사용하기 때문에, XML 코드가 필연적으로 증가할 수밖에 없다.
현재 안드로이드 대세는 MVVM이라고 하지만 굳이 MVVM 원툴로 프로젝트를 설계해야 할 이유는 없다. 프로젝트가 커지면 MVVM도 단점이 생기기 마련이라 MVP, MVVM 어떤 걸 쓰냐에 대한 정답은 없다. 내가 만드는 프로젝트가 MVP를 쓰면 좋은지, MVVM을 쓰면 좋은지를 고민하고 더 좋은 걸 쓰면 그만이다.
참고 사이트)
'Android' 카테고리의 다른 글
[Android] Jetpack Navigation, Room DB, Flow 같이 사용하기 - 1 - (0) | 2022.12.12 |
---|---|
[Android] Navigation 사용 시 FragmentDirections가 자동 생성되지 않을 때 (0) | 2022.12.09 |
[Android] EventBus란? EventBus 사용법 (0) | 2022.11.21 |
[Android] 코틀린으로 registerForActivityResult() 써서 갤러리에서 이미지 가져오기 (0) | 2022.11.10 |
[Android] 날짜 변환 시 사용하는 enum과 확장 함수 모음 (0) | 2022.11.10 |