Android Weekly

Android Weekly #-598 "ViewModel: Events as State are an Antipattern"

베블렌 2023. 12. 12. 11:09

11월 4주차에는 안드로이드 메모리 개선 - 최신 Android Runtime 업데이트에 대한 글입니다.

 

https://proandroiddev.com/viewmodel-events-as-state-are-an-antipattern-35ff4fbc6fb6https://funkymuse.dev/posts/nav-type-safe/#until-next-time

 

ViewModel: Events as State are an Antipattern

In this article Nikita explores how a popular notion about expressing events as states may be a misleading one.

proandroiddev.com

 

 

 

소개

이 글에서는 Kotlin 개발자 커뮤니티에서 논쟁이 되고 있는 "원-오프(one-off) 이벤트"에 대한 안티패턴을 다룹니다. Manuel Vivo의 글 "ViewModel: One-off event antipatterns"에 대한 반론을 제시하며, 이벤트를 상태 변수로 표현하는 것이 왜 문제가 될 수 있는지 설명합니다.

 

내용

  • Vivo의 주장: UI/UI 로직 계층에서 발생하는 일회성 이벤트는 객체 스트림 대신 상태 변수로 표현되어야 함.
  • 반론: ViewModel에서 이벤트를 노출하는 것은 ViewModel이 상태의 진실된 출처가 아니라는 것을 의미하지 않음. 또한, UDF(Unidirectional Data Flow)는 생산자보다 오래 지속되는 소비자에게 이벤트를 전달하는 이점을 언급하지 않음.

이벤트 처리 방식

  • SharedFlow와 Channel을 사용하여 이벤트를 노출하는 방식은 이벤트의 전달 및 처리를 보장하지 않습니다. 이는 일회성 이벤트에 적합하지 않습니다.
  • 예시: SharedFlow를 사용하여 이벤트를 노출하면 구독자가 없을 때 이벤트가 무시됩니다. 이는 여러 구독자가 동일한 이벤트를 처리하는 경우 문제가 발생할 수 있습니다.

상태로서의 이벤트 표현의 문제점

  • 이벤트를 상태로 표현하는 것은 개발자들에게 이해하기 어렵고, 일관성 있는 코드 유지 관리가 어려워집니다. 이는 실제 세계에서 일어나는 사건의 본질과 모순됩니다.

예시 코드

 

1. 이벤트를 상태 변수로 표현하는 방식

// ViewModel 예시
class PaymentViewModel : ViewModel() {
    private val _paymentState = MutableLiveData<PaymentState>()
    val paymentState: LiveData<PaymentState> = _paymentState

    fun completePayment() {
        // 결제 로직 수행
        _paymentState.value = PaymentState.Success
    }
}

// UI 컴포넌트에서 상태 처리 예시
@Composable
fun PaymentScreen(viewModel: PaymentViewModel) {
    val paymentState by viewModel.paymentState.observeAsState()

    when (paymentState) {
        is PaymentState.Success -> {
            // 결제 성공 UI 처리
        }
        // 기타 상태 처리
    }
}

 

2. 이벤트를 별도의 이벤트 스트림으로 처리하는 방식

// ViewModel 예시
class PaymentViewModel : ViewModel() {
    private val _paymentEvent = Channel<PaymentEvent>()
    val paymentEvent = _paymentEvent.receiveAsFlow()

    fun completePayment() {
        // 결제 로직 수행
        _paymentEvent.offer(PaymentEvent.PaymentCompleted)
    }
}

// UI 컴포넌트에서 이벤트 처리 예시
@Composable
fun PaymentScreen(viewModel: PaymentViewModel) {
    val paymentEvent by viewModel.paymentEvent.collectAsState(initial = PaymentEvent.Idle)

    LaunchedEffect(paymentEvent) {
        when (paymentEvent) {
            is PaymentEvent.PaymentCompleted -> {
                // 결제 완료 이벤트 처리
            }
            // 기타 이벤트 처리
        }
    }
}

 

위 두 예시 코드에서 첫 번째 방식은 이벤트를 상태 변수로 관리하여 UI가 상태에 반응하도록 구현한 것이고, 두 번째 방식은 이벤트를 별도의 스트림으로 처리하여 UI가 이벤트 발생에 반응하도록 구현한 것입니다. 첫 번째 방식은 상태 변화에 초점을 맞춘 반면, 두 번째 방식은 이벤트의 발생 자체에 초점을 맞춥니다.

 

 

정리

이 글의 저자는 이벤트를 상태로 처리하는 접근 방식이 자연스럽지 않고, 실제 애플리케이션 개발에서 여러 문제를 야기한다고 주장합니다. 또한, 이러한 접근 방식이 코드의 가독성과 유지 관리 가능성을 저하시키고, 더 큰 버그와 사용자 만족도 저하로 이어질 수 있음을 지적합니다. 따라서 이벤트와 상태를 구분하여 처리하는 것이 더 나은 접근 방식이라고 결론짓는 글 이였습니다.

 

 

 

 

 

 

https://androidweekly.net/

 

Android Weekly - Free weekly Android & Kotlin development newsletter

Android Weekly - Free weekly Android & Kotlin development newsletter

androidweekly.net