Ошибка делегирования: 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— для отдельных значений, поддерживаетbymutableStateListOf— для коллекций, НЕ поддерживает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.