Android발행일 2024. 11. 14.원본 https://blog.naver.com/jword_/223660267651 ↗

안드로이드 앱 개발 공부 - MVVM 패턴 익히기

안드로이드 앱 개발 공부 - MVVM 패턴 익히기 — #개발자의도구들 #안드로이드앱개발공부 #MVVM #MVVM패턴 🚨📝 📖 📒✏️💡🔍 AI스...

#Android Studio#Naver Blog

#개발자의도구들 #안드로이드앱개발공부 #MVVM #MVVM패턴

🚨📝 📖 📒✏️💡🔍

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.

\* 해당 프로젝트 이전에 공부한 내용을 보시려면 여기를 눌러주세요.

안드로이드 앱 개발 공부 시작

기본적인 앱 아키텍쳐

현재 Spring boot를 사용하여 서버를 제작하고 있습니다. 기존에는 API 테스트를 Postman을 사용하여 진행하였었습니다. 하지만, 안드로이드 앱 개발 공부를 시작하기로 하면서 jetpack compse를 사용하여 프론트 화면을 직접 만들고 네트워크 통신을 직접 해보는 과정까지 진행하기로 하였습니다.

​

최종 목표는 X에서 제공되는 기능들을 최대한 뽑아내어, 직접 구현을 해보고, 공부한 내용을 최대한 깔끔하게 정리해 두는 것입니다. (언젠가는 다시 보면서 구현할 것이거든요)

​

이번글에는 안드로이드 앱 설계의 가장 기본이되는 MVVM 구조를 누구나 빠르게 적용할 수 있도록 정리하였습니다.

Index

  • Purpose
  • MVVM이란?
  • VIEWMODEL 사용이유
  • MVVM 적용하기
  • ApiInterface
  • Repository
  • VieModel
  • View
  • Question
  • Answer
  • Feedback
💡 Tips

Purpose

MVVM 패턴을 여러번 공부했는데, 막상 적용하려니 헷갈리는 부분이 굉장히 맣았습니다. 특히나 Spring Server의 Controller, Service, Repository 때문에 더 헷갈렸던 것 같습니다 예를들어 MVVM을 적용한 Android의 Repository와 Spring의 MVC의 Repository가 다르다는 점입니다.

​

이번 글을 통해서 MVVM 패턴이 무엇인지 확실하게 알아보고, 매핑되는 클래스를 제시하여 클린코드의 가이드가 되었으면 좋겠습니다.

MVVM이란?

안드로이드 앱은 Model-View-ViewModel 세 개의 Layer 구조를 사용하고 있습니다. 이름이 거창해 보이지만 사실 MVC아 별 차이가 없습니다.

​

Spring에서 MVC가 View가 프론트 영역으로 변경되면서, MSC가 된 것 처럼, 안드로이드에서 MVC가 MVVM으로 된거로 해석해도 될 것 같습니다.

javascript 코드 예제
                                    🧑‍💻 VIEW

VIEW레이어는 사용자의 화면에 직접 보여지는 부분을 의미합니다.
javascript 코드 예제
                                    🧑‍💻 MODEL

MODEL은 데이터 저장소와 직접적으로 연계되는 레이어입니다.
안드로이드 내부의 DB와 연계하거나, API 호출이 이뤄지는 부분으로 볼 수도 있습니다.
javascript 코드 예제
                                    🧑‍💻 MODEL

MODEL은 데이터 저장소와 직접적으로 연계되는 레이어입니다.
안드로이드 내부의 DB와 연계하거나, API 호출이 이뤄지는 부분으로 볼 수도 있습니다.
🤔 VIEWMODEL을 왜 사용하는지?

Viewmodel이 없던 시절을 생각해보면, Viewmodel이 왜 사용하기 편리한지 이해할 수 있습니다.

​

ViewModel 적용 전

javascript 코드 예제
                                    // viewmodel 적용 이전 (jetpack compose)
@Composable
fun CounterScreen() {
    var count by remember { mutableStateOf(0) }

    Text(text = "Count: $count", style = MaterialTheme.typography.headlineMedium)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { count++ }) {
            Text("Increment")
        }
}

CounterScreen(View layer)에서 직접적으로 count라는 데이터를 관리합니다. 그럼 아래와 같은 문제점이 발생할 수 있습니다.

​

  • 현재 상황: View layer에 데이터가 함께 관리되는 상황이다.
  • 컴포즈 UI: 데이터가 초기화 되면 새롭게 UI가 그려진다.
  • 데이터 변화: count가 증가하면 새롭게 UI가 그려지는데 이때, 데이터가 초기화 된다. (⚠️)

​

ViewModel 적용 후

javascript 코드 예제
                                    // viewmodel 적용 이전 (jetpack compose)

class CounterViewModel: ViewModel() {
    val count = mutableStateOf(0)

    incrementCount() {
        count.value += 1
    }
}
javascript 코드 예제
                                    // viewmodel 적용 이전 (jetpack compose)

@Composable
fun countAppScreen(viewModel: CounterViewModel) {
    Text(text = "Count: ${viewModel.count.value}", style = MaterialTheme.typography.headlineMedium)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { viewModel.incrementCount() }) {
            Text("Increment")
        }
}
  • View 레이어와 데이터 관리 레이어를 분리한다.
  • 실제 데이터 변화는 ViewModel에서 발생한다
  • 데이터 변화시 Screen이 갱신되어도 데이터가 초기화되지 않는다.

​

마지막으로 정리하자면, ViewModel은 Model에서 불러온 데이터를 각 View에 맞게 적절하게 관리하는 레이어입니다. method를 직접 가지고 있어 데이터를 ViewModel을 통해 관리할 수 잇습니다.

MVVM 적용하기

자, 이론은 이정도면 크게 문제가 없으리라 생각됩니다. 이제는 ViewModel Layer를 직접 적용해 보겠습니다.

​

👉 해당 코드는 의존성 주입 Hilt가 적용되었습니다. Hilt에 대해서는 차후 추가로 정리하겠습니다.

이미지

대략적인 전체 구조는 이렇습니다. Model Layer에서 두 부분으로 나눠지는데, 왼쪽의 경우 API 통신 없이 안드로이드 내장 DB를 사용하는 경우입니다. 이 부분에 대해서는 따로 다루겠습니다.

​

이번에 정리할 구조는 API를 사용하여 외부에서 데이터를 가져오는 구조입니다.

​

사실 왼쪽과 오른쪽은 데이터를 어디서 가져오느냐에 따라 추가로 레이어가 생기거나 축소되는 것일 뿐 큰 차이는 없습니다.

javascript 코드 예제
                                    * 게시글 API를 통해 서버에서 데이터를 가져와 UI에 뿌려주는 상황

​

🧑‍💻 API Interface​API를 정의Spring의 Conroller와 유사함 SQLitre의 ROOM과 역할이 비슷
javascript 코드 예제
                                    @Singleton
interface BoardApiInterface {
    @GET("/boards")
    fun getAll(): Call<List<BoardResponse>>

    @GET("/board")
    fun getById(@Body id: Long): Call<BoardResponse>
}

Spring의 Controller처럼 url 및 method를 정의합니다. 이후 Repository에 넘겨줍니다.

🧑‍💻 Repository​network 통신Spring Service와 유사함
javascript 코드 예제
                                    @ActivityScoped
@Singleton
class BoardRepository @Inject constructor(
    private val boardApiInterface: BoardApiInterface
){
    fun getAll(callback: (Result<List<BoardResponse>>) -> Unit) {
        boardApiInterface.getAll().enqueue(object : Callback<List<BoardResponse>> {
            override fun onResponse(
                call: Call<List<BoardResponse>>,
                response: Response<List<BoardResponse>>
            ) {
                if (response.isSuccessful) {
                    val boardResponse = response.body()
                }
            }

            override fun onFailure(p0: Call<List<BoardResponse>>, p1: Throwable) {
                TODO("Not yet implemented")
            }
        })
    }
}

Repository는 API 톹신을 담당하는 부분입니다. 실질적으로 API를 호출하여 데이터를 받아보고, 응답을 처리하는 레이어입니다.

​

Spring의 Service와 유사한 레이어입니다.

🧑‍💻 ViewModel
javascript 코드 예제
                                    class TestViewModel @Inject constructor(
    private val boardRepository: BoardRepository
) : ViewModel() {

    // BoardApi 호출
    fun loadBoards() = liveData<> {
        emit(boardRepository.getAll())
    }
}

Repository를 호출하여 데이터를 관리합니다.

🧑‍💻 View
javascript 코드 예제

@Composable
fun TestView() {
    val viewModel: TestViewModel = viewModel()
    val boards by viewModel.loadBoards().observeAsState(initial = emptyList())

    // Drew your UI
}

View는 실질적으로 UI를 그리는 부분과, viewModel을 호출하여 데이터를 불러와 관리합니다.

Questtion

🤔 1. 전체적인 구조가 맞는지?2. ViewModel 사용이유 ⚠️ 부분

Feedback

​