Jak obsługiwać w architekturze MVI zdarzenia, które nie muszą być zapisywane?
sobes.tech AI
Odpowiedź od AI
Aby obsługiwać unikalne zdarzenia (np. wyświetlenie toasta, nawigacja) w MVI, które nie powinny przetrwać ponownego tworzenia widoku ani być przechowywane w stanie, stosuje się różne podejścia:
-
Efekty uboczne (SingleLiveEvent / Channels):
Specjalny mechanizm do wysyłania zdarzeń z ViewModel do View.
SingleLiveEvent(w starszych projektach lub bibliotekach typuandroidx.lifecycle:lifecycle-livedata-ktx), lubChannelz Flow (w nowoczesnych projektach). Zapewniają, że zdarzenie zostanie obsłużone tylko raz.// ViewModel z Flow i Channel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch class MyViewModel : ViewModel() { private val _sideEffect = Channel<SideEffect>(Channel.BUFFERED) val sideEffect = _sideEffect.receiveAsFlow() fun doSomething() { // Logika biznesowa... viewModelScope.launch { _sideEffect.send(SideEffect.ShowToast("Operacja zakończona sukcesem!")) } } } sealed class SideEffect { data class ShowToast(val message: String) : SideEffect() object NavigateNext : SideEffect() } // W widoku (Fragment/Activity) obserwujemy SideEffect import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch class MyFragment : Fragment() { private val viewModel: MyViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewModel.sideEffect.collect { effect -> when (effect) { is SideEffect.ShowToast -> showToast(effect.message) SideEffect.NavigateNext -> navigateToNextScreen() } } } } private fun showToast(message: String) { // Implementacja wyświetlania toast } private fun navigateToNextScreen() { // Implementacja nawigacji } } -
Zdarzenia jednorazowe w stanie:
Mniej zalecane, ale czasami używane w prostych przypadkach. Dodaje się flagę lub pole w samym stanie
UiState, które wskazuje na obecność zdarzenia jednorazowego. Po obsłudze zdarzenia, widok wysyła nowyIntentdo ViewModel, aby zresetować tę flagę/pole w stanie. Może to skomplikować logikę i potencjalnie prowadzić do ponownego wywołania przy nieprawidłowym obsługiwaniu lub ponownym tworzeniu widoku.// Przykład stanu z flagą jednorazową data class MyUiState( val data: List<Item> = emptyList(), val isLoading: Boolean = false, val showSuccessToast: Boolean = false // Flaga zdarzenia jednorazowego ) // W ViewModel po udanej operacji fun processSuccess() { _uiState.update { it.copy(showSuccessToast = true) } } // W widoku, gdy showSuccessToast = true if (state.showSuccessToast) { showToast("Sukces!") // Ważne: wysłać Intent, aby zresetować flagę viewModel.onToastShown() } // W ViewModel, aby zresetować flagę fun onToastShown() { _uiState.update { it.copy(showSuccessToast = false) } }
Najlepszym zalecanym podejściem w nowoczesnym Androidzie z Flow i MVI jest użycie Efektów ubocznych przez Channel. To czyste, bezpieczne i przewidywalne rozwiązanie dla zdarzeń jednorazowych.