import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch abstract class StateAndEventViewModel(initialState: UiState) : ViewModel() { private val events = MutableSharedFlow(replay = 0) private val _uiState: MutableStateFlow = MutableStateFlow(initialState) val uiState: StateFlow = _uiState.asStateFlow() init { viewModelScope.launch { events.collect { event -> handleEvent(event) } } } protected abstract suspend fun handleEvent(event: Event) protected fun updateUiState(update: UiState.() -> UiState) { _uiState.update { _uiState.value.update() } } fun onEvent(event: Event) { viewModelScope.launch { events.emit(event) } } } @HiltViewModel class DetailViewModel @Inject constructor( private val getItemDetail: GetItemDetailUseCase, private val navigator: NavigationService, ) : StateAndEventViewModel(DetailUIState(null)) { private fun loadItemDetail() { viewModelScope.launch { getItemDetail.getDetail() .onStart { updateUiState { copy(isLoading = true) } } .catch { exception -> updateUiState { copy(error = exception) } } .collect { updateUiState { copy(itemData = it, isLoading = false) } } } } private fun handleBack() { navigator.goBack() } private fun handleSearchDetailClick() { navigator.navigateTo("detail/search") } override suspend fun handleEvent(event: DetailUIEvent) { when (event) { is DetailUIEvent.Dismiss -> handleBack() is DetailUIEvent.LoadItemDetail -> loadItemDetail() is DetailUIEvent.SearchDetailClick -> handleSearchDetailClick() } } }