[Jetpack Compose] ViewModel

단디·2024년 7월 29일
0

Compose

목록 보기
11/12
post-thumbnail

ViewModel은 UI를 그리기 위해 필요한 데이터를 관리하고, 사용자의 이벤트를 처리하는 책임이 있습니다. 이를 통해 UI 컨트롤러(Activity나 Fragment)와 데이터를 분리하여, UI 로직과 비즈니스 로직의 분리로 코드의 재사용성과 유지보수성을 높입니다.

ViewModel은 Lifecycle Aware Component로, Activity나 Fragment가 완전히 소멸할 때까지 인스턴스가 유지됩니다. 따라서 화면 회전, 다크모드 전환 등 Configuration 변화에 의해 Activity나 Fragment의 인스턴스가 소멸 후 재생성되더라도 데이터가 유지되어 일관된 화면을 나타낼 수 있습니다.

ViewModel은 별도의 클래스로 구현되며, 모델 데이터와 이 데이터를 관리하기 위한 함수들, 그리고 UI의 현재 상태를 나타내는 값을 포함합니다. UI 컨트롤러(Activity나 Fragment)에서는 이러한 상태 값을 관찰하고, ViewModel에서는 UI 컨트롤러에서 발생한 이벤트를 처리하는 함수들을 통해 사용자 이벤트를 처리합니다. 이를 통해 단방향 데이터 흐름을 구현하게 됩니다.

viewmodel

ViewModel 구현하기

ViewModel에서는 UI 컨트롤러에서 관찰할 수 있는(observable) 데이터를 저장하는 것을 주요 목표로 합니다. 따라서, UI 컨트롤러는 ViewModel의 데이터를 관찰하다가 데이터가 변경되었을 때 반응할 수 있습니다.

ViewModel에서 관찰 가능한 데이터를 선언하기 위해서는 3가지 방법이 있습니다.

  1. Composable 상태 메커니즘
  2. LiveData 컴포넌트
  3. Flow (Compose에서 Flow 수집하기 글에서 기술합니다.)

Compose 상태 메커니즘

class MainViewModel: ViewModel() {
    private var _count by mutableStateOf(0)
  
    val count: Int
    	get() = _count

    fun increaseCount() {
        _count++
    }

    fun decreaseCount() {
        _count--
    }
}
  • ViewModel 내 상태 값은 mutableStateOf() 함수를 이용해서 선언합니다.
  • 사용자의 이벤트에 따라서 상태 값이 변경될 수 있도록 함수를 추가합니다.

LiveData 컴포넌트

class MainViewModel: ViewModel() {
    private val _count = MutableLiveData(0)
    val count: LiveData<Int> = _count

    fun increaseCount() {
        _count.value = _count.value?.plus(1)
    }

    fun decreaseCount() {
        _count.value = _count.value?.minus(1)
    }
}
  • ViewModel 내 상태 값을 MutableLiveData() 인스턴스를 생성하고 초깃값을 설정합니다.
  • Backing Property를 사용해서 ViewModel 내부에서만 값을 수정할 수 있고 외부에서는 LiveData 타입으로 관찰만 가능하게 합니다.
  • 사용자의 이벤트에 따라서 상태 값이 변경될 수 있도록 함수를 추가합니다.

ViewModel과 Activity 연결하기

의존성 추가하기

implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.3")

프로젝트에 의존성을 추가합니다. 글을 작성한 시점보다 버전은 업데이트될 수 있습니다.

ViewModel 인스턴스 생성하기

viewModel() 함수로 ViewModel 인스턴스를 생성할 수 있습니다. viewModel() 함수를 사용한다면 Compose가 재구성되더라도 ViewModel의 인스턴스는 유지됩니다.

@Composable
fun ScreenSetup(model: MainViewModel = viewModel()) {
    ...
}

@Composable
fun MainScreen(...) {
    ...
}

Composable 맨 위에 위치한 Composable에서 ViewModel 인스턴스를 파라미터로 전달합니다. 최상단에 위치함으로써 모든 자식 컴포저블로 전달할 수 있습니다.

아래는 Compose 상태 메커니즘을 활용해서 상태 값을 저장하고 있는 ViewModel을 사용한 코드입니다.

class MainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle ? ) {
        super.onCreate(savedInstanceState)
        setContent {
            GeminiSampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background,
                ) {
                    ScreenSetup()
                }
            }
        }
    }
}

@Composable
fun ScreenSetup(
    model: MainViewModel = viewModel(),
) {
	TODO("MainScreen 호출")
}

@Composable
fun MainScreen(
    count: Int,
    increaseCount: () -> Unit,
    decreaseCount: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Count: $count")
        Spacer(modifier = Modifier.size(16. dp))
        Row(
            modifier = Modifier
            .fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            Button(onClick = increaseCount) {
                Text(text = "증가")
            }
            Button(onClick = decreaseCount) {
                Text(text = "감소")
            }
        }
    }
}

Activity 안에서 ViewModel의 LiveData 관찰하기

LiveData를 Compose에서 관찰하고, 그 값을 상태 인스턴스로 변환할 수 있습니다. 이를 위해 observeAsState() 함수를 사용합니다. 상태 인스턴스로 변환되면 다른 Compose 상태 변수처럼 값의 변화에 따라 UI 재구성이 트리거됩니다.

 @Composable
fun ScreenSetup(
    model: MainViewModel = viewModel(),
) {
    var count by model.count.observeAsState(0)
    MainScreen(count, model::increaseCount, model::decreaseCount)
}

실행결과

위 코드를 실행한 화면입니다.

viewmodel

0개의 댓글