안드로이드 코틀린 MVVM 패턴 기반 앱 구조 설계 방법

안드로이드 앱 개발이 점점 복잡해지는 시대, 제대로 된 설계 없이는 유지보수가 정말 곤욕스러워지죠. 그래서 요즘은 다들 ‘MVVM’을 당연히 쓰는 분위기입니다. 뷰는 가볍게, 로직은 뷰모델로, 데이터 처리는 모델로 분리해두면 나중에 기능을 확장하거나 디버깅할 때 훨씬 수월하거든요. 이 글에서는 MVVM 구조를 기반으로 한 코틀린 앱 설계 방법을, 개발자 관점이 아닌 조금 더 실무적이고 실용적인 관점에서 풀어보려 합니다. 특히 처음 MVVM 구조를 접하는 분들에게도 쉽게 이해될 수 있도록 다양한 예시와 감각적인 표현을 곁들였어요.





  • 1. MVVM 구조를 쓰면 액티비티가 훨씬 깔끔해지고, 화면 회전에도 강해집니다
  • 2. ViewModel에선 LiveData나 StateFlow를 써서 데이터 흐름을 잡아야 하죠
  • 3. Repository 패턴을 활용하면 데이터 소스를 바꾸더라도 앱 로직은 그대로 유지돼요
  • 4. DI는 무조건 Hilt! 테스트 편하고 유지보수 쉬운 구조를 만들어줍니다
  • 5. 실전에서는 Jetpack Compose와도 MVVM이 찰떡궁합이라는 걸 알게 됩니다

1. MVVM 패턴을 쓰면 액티비티가 날씬해져요




안드로이드 앱 만들다 보면, 액티비티에 이것저것 다 넣고 싶은 유혹이 커지잖아요? 버튼 클릭, 네트워크 호출, DB 처리까지. 근데 그렇게 되면 화면 하나 바꿀 때마다 코드 수정이 줄줄이 이어지죠. 이때 MVVM 구조를 도입하면 **UI는 View만 담당하고, 비즈니스 로직은 ViewModel로**, 데이터 처리나 서버 통신은 Model로 분리되니까 전체 구조가 깔끔해져요.

예를 들어, 프로필 화면을 만들 때, Activity는 단지 ViewModel에서 받은 데이터를 화면에 보여주기만 하면 됩니다. 화면 회전이 일어나도 ViewModel은 그대로 살아 있으니까 재요청 없이 상태가 유지되죠. 덕분에 사용자 경험도 끊기지 않고 이어지고요.

2. ViewModel은 LiveData 또는 StateFlow로 상태를 전달해요




MVVM 구조에서 핵심 중 하나가 ViewModel인데요, 이게 사실상 화면의 두뇌 역할을 합니다. 사용자가 버튼을 누르면 어떤 작업을 할지 결정하고, 데이터를 가져와서 UI에 알려주는 게 다 여기서 일어나요. 중요한 건 이 상태 전달을 어떻게 하느냐인데, 보통은 LiveDataStateFlow를 써요.

  • LiveData: 생명주기를 인식해서 UI가 살아있을 때만 업데이트를 받는 구조
  • StateFlow: 코루틴 기반의 상태 스트림, Compose랑 특히 잘 어울려요

개발할 때는 ViewModel에서 `viewModelScope.launch`를 써서 비동기로 API 요청을 보내고, 응답을 받아 LiveData나 StateFlow로 UI에 알리는 방식으로 설계합니다. 이 덕분에 앱의 반응성이 훨씬 좋아지고, 유지관리도 쉬워지죠.

3. Repository를 쓰면 데이터 소스를 마음껏 바꿀 수 있어요

Model 계층에서는 Repository 패턴이 필수입니다. UserRepository, MovieRepository 이런 식으로 만들고, 각각의 저장소는 네트워크나 데이터베이스에서 데이터를 가져오는 역할을 해요. 예를 들어서 서버에서 사용자 정보를 불러오되, 오프라인일 땐 로컬 캐시를 먼저 쓰도록 하는 로직도 Repository에서 처리하면 깔끔하죠.

ViewModel은 그저 Repository로부터 데이터만 받아서 처리하면 되기 때문에, 데이터 소스를 DB에서 Firebase로 바꿔도 ViewModel은 건드릴 일이 없습니다. 이게 바로 ‘관심사의 분리’가 주는 가장 큰 이점이에요.

4. DI는 Hilt 하나면 충분합니다

DI, 그러니까 의존성 주입이 처음엔 좀 어려워 보일 수 있는데, Hilt를 쓰면 이걸 굉장히 쉽게 구현할 수 있어요. 예전에는 Dagger에 복잡한 모듈과 컴포넌트가 필요했지만, Hilt는 안드로이드 라이프사이클에 맞춰 자동으로 필요한 객체를 생성해줘요.

예를 들어 ViewModel에서 Repository를 주입받을 때, 그냥 생성자에 `@Inject` 달고, 클래스 위에 `@HiltViewModel` 붙이면 끝. 나머지는 Hilt가 알아서 합니다. 덕분에 테스트 코드 작성할 때도 필요한 객체만 교체하면 되고요.

5. Jetpack Compose에서도 MVVM은 여전히 강력해요

최근엔 Compose로 UI를 짜는 경우가 많아졌죠. 이걸 쓰면 기존 XML보다 훨씬 선언적으로 코드를 짤 수 있어요. 근데 Compose를 쓴다고 해서 MVVM을 버릴 필요는 전혀 없어요. 오히려 더 잘 어울려요.

Compose의 `@Composable` 함수는 ViewModel의 StateFlow나 MutableState를 구독해서 자동으로 UI를 갱신합니다. 예전처럼 `findViewById` 쓰면서 이벤트 달고 업데이트하고 그런 복잡함이 없어요. 그냥 ViewModel에서 상태만 잘 관리해주면 UI는 알아서 그려져요. 말 그대로 코드가 절반으로 줄어요.

6. 실제 코드로 보는 ViewModel 예시

@HiltViewModel
class LoginViewModel @Inject constructor(
  private val userRepository: UserRepository
) : ViewModel() {

  val loginResult: MutableLiveData = MutableLiveData()

  fun login(username: String, password: String) {
    viewModelScope.launch {
      val result = userRepository.requestLogin(username, password)
      loginResult.postValue(result)
    }
  }
}

이 코드는 간단하지만 MVVM 구조의 핵심을 잘 보여줘요. ViewModel은 Repository에 로그인 요청을 보내고, 결과를 LiveData로 UI에 전달하죠. Activity는 이 LiveData만 관찰하면 끝. 정말 간결해집니다.

7. 실전 적용 시 주의할 점과 팁

  • ViewModel에는 절대 UI 코드 넣지 마세요! (예: Toast, Dialog)
  • 비동기 처리할 땐 코루틴을 적극 활용하세요. RxJava보다 훨씬 쉬워요
  • LiveData를 너무 남발하면 오히려 데이터 흐름 추적이 힘들어지니, ViewModel 단에서 상태를 캡슐화해 주세요
  • DI 설정을 제대로 하지 않으면 테스트가 매우 귀찮아지니, Hilt 셋업은 처음에 정확히 해두는 게 좋아요

그리고 무엇보다 중요한 건, 이 구조를 팀원들과 공유하고 유지하는 거예요. 혼자서만 MVVM 쓰면 아무 소용 없거든요. 같이 쓰고, 같이 개선해 나가야 진짜로 유지보수가 편한 앱이 되니까요.


마무리하며

MVVM은 단지 디자인 패턴이 아니라, 더 나은 개발 문화를 위한 방향성입니다. 설계가 견고하면 기능 추가도, 버그 수정도 쉽게 됩니다. 코틀린의 강력한 언어적 특성과 함께 MVVM 구조를 잘 활용하면, 안드로이드 개발이 정말 재미있고 유연해져요. 오늘 설명한 구조를 실제 프로젝트에 적용해보면, 여러분도 금방 그 효율성과 깔끔함에 빠져들게 될 거예요. 특히 Compose와 결합하면 UI 구현 속도도 확 올라가고요 😎

이제 MVVM은 선택이 아니라 기본이에요. 제대로 된 구조를 익히고 적용한다면, 어느 팀에서든 인정받는 개발자가 되는 건 시간문제겠죠?

댓글 남기기