Я создаю демонстрационный проект для использования jetpack compose с mvvm, я создал модельный класс, который содержит список пользователей ... эти пользователи отображаются в списке, а сверху есть кнопка, которая добавляет нового пользователя в список при нажатии ... когда пользователь нажимает на кнопку и лямбда-обновление обновляет информацию о ней, а операция вызывает viewmodel, которая добавляет данные в список и обновляет данные о деятельности, используя liveata, теперь, когда модель получает новые данные, она не обновляет составную функцию об этом и, следовательно, пользовательский интерфейс список не обновляется .. вот код

@Model
data class UsersState(var users: ArrayList<UserModel> = ArrayList())

Деятельность

class MainActivity : AppCompatActivity() {
    private val usersState: UsersState = UsersState()
    private val usersListViewModel: UsersListViewModel = UsersListViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        usersListViewModel.getUsers().observe(this, Observer {
            usersState.users.addAll(it)
        })
        usersListViewModel.addUsers()
        setContent {
            UsersListUi.addList(
                usersState,
                onAddClick = { usersListViewModel.addNewUser() },
                onRemoveClick = { usersListViewModel.removeFirstUser() })
        }
    }

}

ViewModel

class UsersListViewModel {

    private val usersList: MutableLiveData<ArrayList<UserModel>> by lazy {
        MutableLiveData<ArrayList<UserModel>>()
    }
    private val users: ArrayList<UserModel> = ArrayList()
    fun addUsers() {
        users.add(UserModel("jon", "doe", "android developer"))
        users.add(UserModel("john", "doe", "flutter developer"))
        users.add(UserModel("jonn", "dove", "ios developer"))
        usersList.value = users
    }

    fun getUsers(): MutableLiveData<ArrayList<UserModel>> {
        return usersList
    }

    fun addNewUser() {
        users.add(UserModel("jony", "dove", "ruby developer"))
        usersList.value = users
    }

    fun removeFirstUser() {
        if (!users.isNullOrEmpty()) {
            users.removeAt(0)
            usersList.value = users
        }
    }
}

Составная функция

@Composable
    fun addList(state: UsersState, onAddClick: () -> Unit, onRemoveClick: () -> Unit) {
        MaterialTheme {
            FlexColumn {
                inflexible {
                    // Item height will be equal content height
                    TopAppBar( // App Bar with title
                        title = { Text("Users") }
                    )
                    FlexRow() {
                        expanded(flex = 1f) {
                            Button(
                                text = "add",
                                onClick = { onAddClick.invoke() },
                                style = OutlinedButtonStyle()
                            )

                        }
                        expanded(flex = 1f) {
                            Button(
                                text = "sub",
                                onClick = { onRemoveClick.invoke() },
                                style = OutlinedButtonStyle()
                            )
                        }
                    }
                    VerticalScroller {
                        Column {
                            state.users.forEach {
                                Column {
                                    Row {
                                        Text(text = it.userName)
                                        WidthSpacer(width = 2.dp)
                                        Text(text = it.userSurName)
                                    }
                                    Text(text = it.userJob)

                                }
                                Divider(color = Color.Black, height = 1.dp)

                            }
                        }
                    }
                }

            }

        }
    }

Весь исходный код доступен здесь

Я не уверен, что я делаю что-то не так или это потому, что Jetpack Comose все еще находится в предварительном просмотре для разработчиков, поэтому буду признателен за любую помощь .. спасибо

5
Kushal Dave 1 Дек 2019 в 15:46

2 ответа

Ваша модель не соблюдается, поэтому изменения не будут отражены. В этой статье в разделе "Собираем все" вместе 'Список добавлен.

val list = +memo{ calculation: () -> T}

Пример для вашего списка:

@Composable
fun test(supplier: UserState) {
   val list = +memo{supplier.users}
   ListConsumer(list){
       /* Do other stuff for your usecase */
   }
}
0
2jan222 1 Дек 2019 в 16:53
Если вы хотите изменить значения своей UserModel, вам может потребоваться добавить аннотацию @Model к этому классу данных.
 – 
2jan222
1 Дек 2019 в 16:56
Мой класс usersState, содержащий список userModels, уже аннотирован с помощью @model, и после вашего предложения я попытался использовать приведенный выше код с + memo, но все еще имел ту же проблему
 – 
Kushal Dave
1 Дек 2019 в 17:24

Эй!

Шон из Android Devrel здесь. Основная причина, по которой это не обновляется, заключается в том, что список ArrayList в UserState.users не является наблюдаемым - это просто обычный ArrayList, поэтому изменение его не приведет к обновлению compose.

Модель делает все свойства класса модели наблюдаемыми

Похоже, это может сработать, потому что UserState аннотируется @Model, что делает вещи автоматически наблюдаемыми Compose. Однако наблюдаемость применяется только на один уровень в глубину. Вот пример, который никогда не приведет к перекомпоновке:

class ModelState(var username: String, var email: String)

@Model
class MyImmutableModel(val state: ModelState())

Поскольку переменная state неизменяема (val), Compose никогда не инициирует перекомпоновку при изменении email или username. Это потому, что @Model применяется только к свойствам аннотированного класса. В этом примере state можно наблюдать в Compose, но username и email - это просто обычные строки.

Исправить вариант №0: вам не нужна @Model

В этом случае у вас уже есть LiveData от getUsers() - вы можете наблюдать это в compose. Мы еще не отправили наблюдение Compose в выпусках для разработчиков, но можно написать его, используя эффекты, пока мы не отправим метод наблюдения. Только не забудьте удалить наблюдателя в onDispose {}.

Это также верно, если вы используете любой другой наблюдаемый тип, например Flow, Flowable и т. Д. Вы можете передать их непосредственно в функции @Composable и наблюдать за ними с эффектами, не вводя промежуточных @Model класс.

Исправить вариант №1: использование неизменяемых типов в @Model

Многие разработчики предпочитают неизменяемые типы данных для состояния пользовательского интерфейса (такие шаблоны, как MVI, поощряют это). Вы можете обновить свой пример, чтобы использовать неизменяемые списки, а затем, чтобы изменить список, вам нужно будет назначить свойство users, которое будет наблюдаться Compose.

@Model
class UsersState(var users: List<UserModel> = listOf())

Затем, когда вы хотите его обновить, вам нужно назначить переменную users:

val usersState = UsersState()

// ...
fun addUsers(newUsers: List<UserModel>) {
    usersState.users = usersState.users + newUsers 
    // performance note: note this allocates a new list every time on the main thread
    // which may be OK if this is rarely called and lists are small
    // it's too expensive for large lists or if this is called often
}

Это всегда будет вызывать перекомпоновку каждый раз, когда новый List<UserModel назначается users, и поскольку нет возможности редактировать список после того, как он был назначен, пользовательский интерфейс всегда будет показывать текущее состояние.

В этом случае, поскольку структура данных представляет собой List, которое вы объединяете, производительность неизменяемых типов может быть неприемлемой. Однако, если у вас есть неизменяемый data class, этот вариант подойдет, поэтому я включил его для полноты картины.

Вариант исправления № 2: использование списка моделей

Compose имеет специальный тип списка наблюдаемый как раз для этого случая использования. Вы можете использовать вместо ArrayList, и любые изменения в списке будут заметны при создании.

@Model
class UsersState(val users: ModelList<UserModel> = ModelList())

Если вы используете ModelList, остальной код, который вы написали в Activity, будет работать правильно, и Compose сможет непосредственно наблюдать изменения в users.

Связанный: Вложенные классы @Model

Стоит отметить, что вы можете вкладывать классы @Model, именно так работает версия ModelList. Возвращаясь к примеру в начале, если вы аннотируете оба класса как @Model, тогда все свойства будут видны в Compose.

@Model
class ModelState(var username: String, var email: String)

@Model
class MyModel(var state: ModelState())

Примечание. Эта версия добавляет @Model в ModelState, а также позволяет переназначать состояние в MyModel

Поскольку @Model делает все свойства класса, который аннотируется, наблюдаемыми с помощью compose, state, username и email все будут наблюдаемыми.

TL; DR какой вариант выбрать

Полное исключение @Model (вариант № 0) в этом коде позволит избежать дублирования слоя модели только для Compose. Поскольку вы уже удерживаете состояние в ViewModel и выставляете его через LiveData, вы можете просто передать LiveData напрямую, чтобы составить и наблюдать его там. Это был бы мой первый выбор.

Если вы действительно хотите использовать @Model для представления изменяемого списка, используйте ModelList из Варианта №2.


Вероятно, вы захотите изменить ViewModel, чтобы он также содержал ссылку MutableLiveData. В настоящее время список, хранящийся в ViewModel, не является наблюдаемым. Чтобы получить представление о ViewModel и LiveData из компонентов архитектуры Android, посетите Курс по основам Android.

9
Sean McQuillan 11 Фев 2020 в 12:19