Avatar ·

Ошибка делегирования: State<List<User>?> не имеет метода getValue в Jetpack Compose

Привет! Я изучаю Jetpack Compose и пытаюсь использовать mutableStateListOf для списка пользователей, но получаю странную ошибку компиляции.

Вот мой код:

class MyViewModel : ViewModel() {
    var users: MutableStateList<User?> = mutableStateListOf()
    
    fun loadUsers() {
        viewModelScope.launch {
            val result = api.getUsers() // List<User>
            users.clear()
            users.addAll(result)
        }
    }
}

@Composable
fun UserListScreen(viewModel: MyViewModel) {
    val users by viewModel.users // <--- ОШИБКА ЗДЕСЬ
    LazyColumn {
        items(users) { user ->
            Text(user?.name ?: "Неизвестно")
        }
    }
}

Компилятор выдаёт:

Type 'MutableStateList<User?>' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate

Я уже использовал by делегирование с обычными mutableStateOf и всё работало. Почему с mutableStateListOf не работает? Как правильно наблюдать за списком в Compose? Я перепробовал разные варианты, но ничего не помогает. Буду благодарен за объяснение!

<<>>

Проблема: MutableStateList не поддерживает делегирование через by

Вы столкнулись с типичной ошибкой новичков в Jetpack Compose. Дело в том, что mutableStateListOf() возвращает MutableStateList<T>, который не реализует интерфейс ReadWriteProperty или ObservableProperty, необходимый для делегирования через by.

В отличие от mutableStateOf(), который создаёт MutableState<T> с поддержкой делегатов, MutableStateList — это специализированный список, оптимизированный для изменений на уровне элементов. Он не предоставляет getValue/setValue операторы.

Правильное решение (3 варианта)

Вариант 1: Использовать обычный State со списком (рекомендуется)

class MyViewModel : ViewModel() {
    private val _users = mutableStateOf<List<User>>(emptyList())
    val users: State<List<User>> = _users
    
    fun loadUsers() {
        viewModelScope.launch {
            val result = api.getUsers()
            _users.value = result
        }
    }
}

@Composable
fun UserListScreen(viewModel: MyViewModel) {
    val users by viewModel.users // Теперь работает!
    LazyColumn {
        items(users) { user ->
            Text(user.name)
        }
    }
}

Вариант 2: Использовать MutableStateList без делегирования

class MyViewModel : ViewModel() {
    val users = mutableStateListOf<User>()
    
    fun loadUsers() {
        viewModelScope.launch {
            val result = api.getUsers()
            users.clear()
            users.addAll(result)
        }
    }
}

@Composable
fun UserListScreen(viewModel: MyViewModel) {
    val users = viewModel.users // Без "by"
    LazyColumn {
        items(users) { user ->
            Text(user.name)
        }
    }
}

Этот вариант работает, но вы не можете использовать by. Композер будет перерисовываться при любом изменении списка (добавление/удаление элементов).

Вариант 3: Правильная обёртка с делегированием (продвинутый)

class MyViewModel : ViewModel() {
    private val _users = mutableStateListOf<User>()
    val users: List<User> get() = _users.toList() // snapshot
    
    fun loadUsers() {
        viewModelScope.launch {
            _users.clear()
            _users.addAll(api.getUsers())
        }
    }
}

@Composable
fun UserListScreen(viewModel: MyViewModel) {
    // Используем remember с ключом для отслеживания изменений
    val users = remember(viewModel.users.hashCode()) { viewModel.users }
    
    LazyColumn {
        items(users) { user ->
            Text(user.name)
        }
    }
}

Почему это важно?

  • mutableStateOf — для отдельных значений, поддерживает by
  • mutableStateListOf — для коллекций, НЕ поддерживает by
  • Для списков лучше использовать State<List<T>> с полной заменой значения

Рекомендую Вариант 1 — он самый простой и производительный. Compose оптимизирован для работы с неизменяемыми списками: при изменении _users.value = newList будет перерисована только изменившаяся часть.

<<>>

Альтернативный подход: Использование collectAsState с Flow

Если вы работаете с архитектурой, основанной на реактивных потоках, можно использовать StateFlow и collectAsState():

class MyViewModel : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()
    
    fun loadUsers() {
        viewModelScope.launch {
            _users.value = api.getUsers()
        }
    }
}

@Composable
fun UserListScreen(viewModel: MyViewModel) {
    val users by viewModel.users.collectAsState()
    
    LazyColumn {
        items(users) { user ->
            Text(user.name)
        }
    }
}

Этот подход особенно полезен, если вы получаете данные из Room или других источников, поддерживающих Flow. collectAsState() автоматически подписывается на изменения и обновляет композицию.

Плюсы:

  • Чистая архитектура с разделением данных
  • Поддержка жизненного цикла (отписка при уничтожении)
  • Простота тестирования

Минусы:

  • Требуется дополнительная зависимость (обычно уже есть в проекте)
  • Немного больше шаблонного кода

Выбирайте этот вариант, если ваш проект уже использует Kotlin Coroutines и Flow.