MVVM 아키텍처에서는 ViewModel에서 Api통신을 한다.
그 결과에 따라 성공하면 페이지를 이동시키고, 실패하면 토스트 메시지를 보내는 이런 분기가 많다.
하지만, ViewModel 내부에서는 context를 사용할 수 없다. context는 @Composable 안에서만 사용할 수 있기 때문이다.
어떻게 이를 해결할 수 있을까?
Api에 대한 상태는 Empty, Loading, Success, Error로 크게 4가지로 나눌 수 있다.
이를 코드로 나타내면?
sealed class ApiState<out T> {
data class Success<T>(val data: T) : ApiState<T>()
data class Error(val message: String) : ApiState<Nothing>()
object Loading : ApiState<Nothing>()
object Empty : ApiState<Nothing>()
}
제네릭으로 둔 이유는 경우에 따라 String이나, Int로 StatusCode를 메시지로 넣을 수 있도록 한 것이다.
ViewModel에서는 Api를 Fetch하는 함수가 있을 것이다.
api를 호출 하는 함수는 비동기적이어야 하므로 보통 viewModelScope.launch
를 많이 사용한다.
class MyViewModel : ViewModel() {
private val _apiState = mutableStateOf<ApiState<String>>(ApiState.Empty)
val apiState: State<ApiState<String>> = _apiState
fun fetchData() {
viewModelScope.launch {
_apiState.value = ApiResult.Loading
try {
val result = apiCall()
_apiState.value = ApiResult.Success(result)
} catch (e: Exception) {
_apiState.value = ApiResult.Error(e.message())
}
}
}
}
이제 Api에 대한 결과를 View에서 분기처리 해야한다.
LaunchedEffect를 통해 apiState의 값이 바뀌면 분기마다 처리할 수 있도록 한다.
@Composable
fun MyScreen(viewModel: MyViewModel, navController: NavController) {
val context = LocalContext.current
val apiState = viewModel.apiState.value
LaunchedEffect(apiState) {
when (apiState) {
is ApiState.Loading -> {
// 로딩 상태 처리
}
is ApiState.Success -> {
// 성공 시 페이지 이동
navController.navigate("nextPage")
}
is ApiState.Failure -> {
// 실패 시 토스트 메시지 띄우기
Toast.makeText(context, (apiResult as ApiResult.Failure).error, Toast.LENGTH_SHORT).show()
}
is ApiState.Empty -> {
// 아무것도 안함
}
}
}
// 화면 구성
// Column() {}
}
Compose에 대해 공부하며 LaunchedEffect, DisposableEffect 등 많았는데, 이를 좀 정리해서 포스팅하도록 하겠다.