[Android App] MVP 디자인 패턴

김재만·2024년 10월 1일
0

알쓸개잡

목록 보기
9/9
post-thumbnail

이 글은 MVC, MVP, MVVM 디자인 패턴을 공부하고 기록하는 두 번째 블로그 글입니다.

MVP 디자인 패턴에 대해서 Android Native App 개발자의 시점에서 알아보도록 하겠습니다.

디자인 패턴 자체도 알아보지만 조금 더 디테일하기 안드로이드 앱 개발에서 해당 디자인 패턴을 어떻게 사용하는지도 함께 알아보겠습니다.

MVP 패턴

특징

  • Model, View, Presenter 로 구성되어 Presenter 가 View-Model을 통제
  • Model과 View 의 분리를 위해 개발된 디자인패턴으로 서로 직접적인 의존성을 제거
  • Presenter 와 View는 1:1 관계를 가짐

장점

  • Presenter 에서 기능을 구현하고 View 에서 보여주기에 코드의 분리가 가능
  • view - model간의 의존성 분리를 통해 결합도를 낮출 수 있음
  • 기능과 화면이 명확하고 분리가되었기 때문에 기능 테스트, 유지 보수성이 증가

단점

  • Presenter가 들어오고 코드가 분리되면서 의존성에 대해서 생각을 해야하며 의존성 주입(DI)이라는 개념이 등장하여 유기적인 관계를 생각하며 개발해야함
  • 이로 인해 MVC 패턴에 비해 비교적 높은 러닝커브가 발생

코드

해당 코드에선 DI 환경 구축을 도와주는 라이브러리인 Hilt, dagger 등의 라이브러리를 사용하진 않고 직접 주입해서 간단하게 테스트용 코드로 작성되었습니다.

실제 앱을 개발하면서 항상 DI 환경 구축을 도와주는 Hilt, dagger 등의 라이브러리를 사용했었는데, 해당 라이브러리를 사용하지 않고 직접 의존성을 주입해보니 이전에는 살짝 아리송한 개념을 정리할 수 있었고

"왜 이렇게 의존성을 주입해야하고, 이렇게 의존성 주입을 했을 때 모듈간의 의존성을 낮추고 결합도를 낮춰 유지보수, 확장성을 늘릴 수 있겠구나" 라는 점을 배울 수 있었습니다.

  1. Model
// 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)
    }
}
  1. Domain(Presenter)
// 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()
}
  1. View(app) - jetpack compose 사용
// 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 와 비교해 어떠한 점이 달라졌는지 명확히 확인 할 수 있어 어떠한 장단점들이 있는지 확실히 알 수 있었습니다. 이를 통해 다음 프로젝트를 진행할 때 어떤 패턴을 사용해서 개발을 설계하면 좋을지 알 수 있을 것 같고 효율적인 개발을 진행할 수 있을 것 같습니다.

profile
기록으로 남기고 공유를 위한 벨로그입니다.

0개의 댓글