이 글은 MVC, MVP, MVVM 디자인 패턴을 공부하고 기록하는 두 번째 블로그 글입니다.
MVP 디자인 패턴에 대해서 Android Native App 개발자의 시점에서 알아보도록 하겠습니다.
디자인 패턴 자체도 알아보지만 조금 더 디테일하기 안드로이드 앱 개발에서 해당 디자인 패턴을 어떻게 사용하는지도 함께 알아보겠습니다.
해당 코드에선 DI 환경 구축을 도와주는 라이브러리인 Hilt, dagger 등의 라이브러리를 사용하진 않고 직접 주입해서 간단하게 테스트용 코드로 작성되었습니다.
실제 앱을 개발하면서 항상 DI 환경 구축을 도와주는 Hilt, dagger 등의 라이브러리를 사용했었는데, 해당 라이브러리를 사용하지 않고 직접 의존성을 주입해보니 이전에는 살짝 아리송한 개념을 정리할 수 있었고
"왜 이렇게 의존성을 주입해야하고, 이렇게 의존성 주입을 했을 때 모듈간의 의존성을 낮추고 결합도를 낮춰 유지보수, 확장성을 늘릴 수 있겠구나" 라는 점을 배울 수 있었습니다.
// UserModel.kt
package com.example.model
data class UserModel (
var name : String
)
// UserRepository
package com.example.model
import android.util.Log
class UserRepository {
private var user = UserModel("Default Name")
// 데이터를 가져오는 함수
fun getUser(): UserModel {
return user
}
// 데이터를 업데이트하는 함수
fun updateUserName(newName: String) {
Log.d("유저업데이트네임", "updateUserName: " + newName)
user.name = newName
Log.d("유저업데이트네임2", "updateUserName: "+ user.name)
}
}
// UserPresenterImpl.kt
package com.example.domain
import android.util.Log
import com.example.model.UserRepository
// 구현체 정의
class UserPresenterImpl(
private val view: UserView,
private val repository: UserRepository
) : UserPresenter {
// 유저 이름 가져오고 뷰에 보여주기
override fun loadUserName() {
val user = repository.getUser()
view.showUserName(user.name)
}
// 이름 업데이트
override fun updateUserName() {
val newName = view.getNewUserName()
if (newName.isNotEmpty()) {
repository.updateUserName(newName)
view.showUpdateResult()
loadUserName()
}
}
}
// UserPresenter.kt
package com.example.domain
interface UserPresenter {
// 이름을 로드하고 View에 표시
fun loadUserName()
// 사용자가 입력한 이름을 업데이트
fun updateUserName()
}
// UserView.kt
package com.example.domain
interface UserView {
// 이름을 화면에 표시하는 메서드
fun showUserName(name: String)
// 입력된 새로운 이름을 가져오는 메서드
fun getNewUserName(): String
// 이름 변경 후 성공 메시지를 표시
fun showUpdateResult()
}
// MainActivity.kt
package com.example.mvppatternapp
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.domain.UserPresenterImpl
import com.example.domain.UserView
import com.example.model.UserRepository
import com.example.mvppatternapp.ui.theme.MVPpatternAppTheme
class MainActivity : ComponentActivity(), UserView {
private lateinit var presenter: UserPresenterImpl
private var inputName by mutableStateOf("") // 입력 필드의 값을 관리
private var currentName by mutableStateOf("") // 현재 이름을 저장하는 상태
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = UserPresenterImpl(this, UserRepository()) // 의존성 주입
setContent {
MVPpatternAppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
UserScreen(
currentName = currentName,
onInputNameChange = { inputName = it },
onNameUpdateClick = { presenter.updateUserName() }
)
}
}
}
}
}
// UserView 인터페이스 구현
override fun showUserName(name: String) {
currentName = name // 이름을 업데이트하여 화면에 반영
}
override fun getNewUserName(): String {
return inputName // 사용자가 입력한 새로운 이름 반환
}
override fun showUpdateResult() {
Toast.makeText(this, "Name updated successfully!", Toast.LENGTH_SHORT).show()
}
}
@Composable
fun UserScreen(
currentName: String,
onInputNameChange: (String) -> Unit,
onNameUpdateClick: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
// 현재 이름 표시
Text(text = "Current Name: $currentName", fontSize = 24.sp)
Spacer(modifier = Modifier.height(16.dp))
// 사용자가 이름을 입력할 수 있는 TextField
var inputName by remember { mutableStateOf("") }
TextField(
value = inputName,
onValueChange = {
inputName = it
onInputNameChange(it) // 입력 필드 값 변경 시 전달
},
label = { Text(text = "Enter new name") },
modifier = Modifier.padding(16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
// 버튼 클릭 시, Presenter에 변경 요청
Button(onClick = {
onNameUpdateClick() // Presenter에 업데이트 요청
}) {
Text(text = "Change Name")
}
}
}
DI 환경에 대한 이해도를 높여 느낀점이 많은 공부였고, MVC 와 비교해 어떠한 점이 달라졌는지 명확히 확인 할 수 있어 어떠한 장단점들이 있는지 확실히 알 수 있었습니다. 이를 통해 다음 프로젝트를 진행할 때 어떤 패턴을 사용해서 개발을 설계하면 좋을지 알 수 있을 것 같고 효율적인 개발을 진행할 수 있을 것 같습니다.