Как в MVI реализовать загрузку списка
Комментарии (3)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация загрузки списка в MVI на Android
В архитектуре MVI (Model-View-Intent) загрузка списка реализуется через строгое разделение на компоненты с односторонним потоком данных. Вот пошаговая реализация:
1. Определение состояний (State)
Первым шагом создаем sealed class для всех возможных состояний экрана со списком:
sealed class ListState {
object Loading : ListState()
data class Success(val items: List<Item>) : ListState()
data class Error(val message: String) : ListState()
object Empty : ListState()
}
2. Определение намерений (Intent)
Intent'ы представляют пользовательские действия и системные события:
sealed class ListIntent {
object LoadItems : ListIntent()
object RefreshItems : ListIntent()
data class ItemClicked(val item: Item) : ListIntent()
}
3. Реализация ViewModel с редюсером
В ViewModel создаем StateFlow для состояния и обрабатываем интенты:
class ListViewModel(
private val repository: ItemsRepository
) : ViewModel() {
private val _state = MutableStateFlow<ListState>(ListState.Loading)
val state: StateFlow<ListState> = _state.asStateFlow()
private val _intent = Channel<ListIntent>()
init {
handleIntents()
sendIntent(ListIntent.LoadItems)
}
fun sendIntent(intent: ListIntent) {
viewModelScope.launch {
_intent.send(intent)
}
}
private fun handleIntents() {
viewModelScope.launch {
_intent.consumeAsFlow().collect { intent ->
when (intent) {
is ListIntent.LoadItems -> loadItems()
is ListIntent.RefreshItems -> refreshItems()
else -> {/* обработка других интентов */}
}
}
}
}
private suspend fun loadItems() {
_state.value = ListState.Loading
try {
val items = repository.getItems()
_state.value = if (items.isEmpty()) {
ListState.Empty
} else {
ListState.Success(items)
}
} catch (e: Exception) {
_state.value = ListState.Error(e.message ?: "Unknown error")
}
}
private suspend fun refreshItems() {
loadItems() // или отдельная логика обновления
}
}
4. Реализация UI слоя
В Activity/Fragment наблюдаем за состоянием и отображаем соответствующий UI:
class ListActivity : AppCompatActivity() {
private val viewModel: ListViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.state.collect { state ->
render(state)
}
}
}
}
private fun render(state: ListState) {
when (state) {
is ListState.Loading -> {
showLoading()
hideList()
hideError()
}
is ListState.Success -> {
hideLoading()
showList(state.items)
hideError()
}
is ListState.Error -> {
hideLoading()
hideList()
showError(state.message)
}
is ListState.Empty -> {
hideLoading()
showEmptyState()
hideError()
}
}
}
// Обработка кликов
private fun onItemClick(item: Item) {
viewModel.sendIntent(ListIntent.ItemClicked(item))
}
}
5. Оптимизация с дополнительными эффектами (SingleLiveEvent)
Для разовых событий (навигация, тосты) используем дополнительный канал:
class ListViewModel(...) : ViewModel() {
// ... существующий код ...
private val _effects = Channel<ListEffect>()
val effects: Flow<ListEffect> = _effects.receiveAsFlow()
sealed class ListEffect {
data class ShowMessage(val text: String) : ListEffect()
data class NavigateToDetails(val itemId: String) : ListEffect()
}
private fun handleItemClick(item: Item) {
viewModelScope.launch {
_effects.send(ListEffect.NavigateToDetails(item.id))
}
}
}
Ключевые преимущества такого подхода:
- Предсказуемость состояния: Все возможные состояния явно декларированы
- Тестируемость: ViewModel легко тестируется, так как содержит чистую бизнес-логику
- Воспроизводимость: Любое состояние можно воссоздать, воспроизведя последовательность интентов
- Декларативность: UI просто отражает текущее состояние, не содержит логики принятия решений
Важные моменты для production-реализации:
- Кэширование данных: Добавьте кэш в репозиторий для избежания лишних запросов
- Пагинация: Для больших списков реализуйте
LoadMoreинтент - Retry логика: Добавьте
RetryLoadинтент для обработки ошибок - DiffUtil: Используйте
ListAdapterдля эффективных обновлений RecyclerView - Сохраняемость состояния: Конфигурационные изменения не нарушат загрузку благодаря StateFlow
Эта реализация обеспечивает чистую одностороннюю архитектуру, где состояние экрана полностью определяется последовательностью интентов, что упрощает отладку и поддержку кода.