Как реализовать ViewModel для активности со многими полями

Проблема

Имеется экран настроек (SettingsActivity) с примерно 10 текстовыми полями и 3 кнопками. Содержимое текстовых полей, которые onClick открывают диалоговое окно для вставки/редактирования текста, сохраняется в файле SharedPreferences. Кнопки выполняют асинхронные запросы для извлечения содержимого и сохранения в другом месте. Во время запросов отображается диалоговое окно для уведомления о ходе выполнения.

Исходное решение

Репозиторий данных

В основном оболочка для SharedPreferences, которая имеет 10 геттеров и 10 сеттеров, по одному для каждого поля. В get[field_name] DataRepository получает значение из SharedPreferences, а в set[field_name] оно фиксируется в SharedPreferences.

ViewModel

ViewModel, который имеет 10 объектов MutableLiveData, по одному для каждого поля. Этот класс реализует LifecycleObserver, чтобы знать о жизненном цикле SettingsActivity, чтобы он мог загружать поля из репозитория в onCreate и сохранять поля в репозиторий в onDestroy.

Есть также 3 метода для выполнения 3 асинхронных запросов, которые запускаются 3 упомянутыми кнопками. Каждый метод получает экземпляр OnRequestProgressListener, который передается классу, который делает асинхронный запрос, который будет использоваться для уведомления представления о ходе выполнения.

Вид

Активность с 10 полями, которая наблюдает за 10 MutableLiveData из ViewModel. На onClick каждого поля открывается диалоговое окно для редактирования/вставки текста. На onPositiveButton диалога вызывается наблюдатель соответствующего поля.

Активность реализует OnRequestProgressListener для отображения и скрытия диалогов в соответствии с ходом выполнения асинхронных запросов.

Начальная задача решения

Описанная выше конструкция не кажется правильной. Могу отметить некоторые:

  • 10 MutableLiveData в ViewModel;
  • 10 геттеров и 10 сеттеров в DataRepository;
  • Репозиторий для SharedPreferences.
  • ViewModel получает слушателей для передачи классам, выполняющим асинхронные запросы, которые используют этих слушателей для уведомления представления. Все с ViewModel посередине.

Правильное решение

Это правильное решение? Если нет, а я считаю, что это не так, как следует разработать правильное решение?


person Mateus Pires    schedule 12.07.2018    source источник


Ответы (2)


  • 10 MutableLiveData во ViewModel;

Это совершенно нормально, если у вас есть 10 независимых фрагментов данных, вы можете иметь LiveData для каждого из них.

  • Репозиторий для SharedPreferences.

Репозиторий должен быть абстракцией над вашим уровнем данных, позволяя вам легко переключаться между реализациями. Так что иметь репозиторий с общими настройками теоретически нормально.

Но в вашем случае, если единственное, что делает репозиторий, — это переадресация вызовов на SharedPreferences, поскольку вероятность переключения решения хранилища с общих префов на что-то другое очень мала, я бы избавился от репозитория и напрямую использовал SharedPreferences для упрощения кода.

  • 10 геттеров и 10 сеттеров в DataRepository;

То же самое здесь, если у вас есть 10 фрагментов данных, хранящихся в вашем классе, и вы хотите, чтобы они были доступны извне, вы должны использовать шаблон свойства, который приводит к получению и установке в Java. Однако в Kotlin вам не нужно явно писать геттеры и сеттеры. Кроме того, если вы решите удалить DataRepository, вам не понадобится этот код.

  • ViewModel получает прослушиватели для передачи классам, которые выполняют асинхронные запросы, которые используют эти прослушиватели для уведомления представления. Все с ViewModel посередине.

Это звучит немного неправильно, если вы создадите прослушиватель в своей активности, есть вероятность, что вы случайно используете анонимный класс, у которого есть ссылка на активность, передадите его ViewModel и получите утечку памяти. Вы не должны передавать ссылку на активность в файл ViewModel. Правильный способ связи — через LiveData. Вам нужно создать LiveData, который будет публиковать прогресс, использовать его внутри ViewModel, предоставляя ему прогресс, и ваша активность должна подписаться на него, чтобы получать информацию о прогрессе. При таком подходе вы будете в безопасности от утечек памяти.

person TpoM6oH    schedule 18.07.2018
comment
О LiveData для публикации прогресса: я создал метод в ViewModel для получения некоторых данных. Метод возвращает LiveData, поэтому, когда Activity нужны эти данные, он вызывает метод и подписывается. Когда значение LiveData равно null, отображается Dialog; когда значение истинно или ложно, выборка выполнена успешно или неудачно, поэтому новый Dialog уведомляет об этом состоянии. Что вы думаете о необходимости Activity отписаться от LiveData? Мне действительно нужно сохранить ссылку на LiveData, возвращаемый методом, чтобы я мог отказаться от подписки позже, когда запрос будет успешным или неудачным? - person Mateus Pires; 20.07.2018
comment
Вам не нужно отписываться от LiveData, он предназначен для обработки отказа от подписки сам по себе. При вызове .observe вы указываете владельца жизненного цикла, на основании которого он отпишется. Поэтому, если вы предоставите свою активность в качестве владельца жизненного цикла, LiveData автоматически отменит подписку, когда ваша активность будет уничтожена. - person TpoM6oH; 21.07.2018
comment
Но когда пользователь снова нажимает кнопку, для нового запроса возвращается другой экземпляр LiveData. Не будет ли N LiveData экземпляров при N-м нажатии кнопки? Возможно, если действие отменит подписку на LiveData, экземпляр больше не будет удерживаться, поэтому он может быть уничтожен сборщиком мусора. Я считаю, что N › 3 не будет, если пользователь нормальный человек, но я боюсь, что это может быть плохой дизайн. (Спасибо за вашу помощь!) - person Mateus Pires; 22.07.2018
comment
О, это не совсем правильный способ работы с LiveData. Вам нужны одни оперативные данные для вашего источника данных - диалоговая информация. Вы создаете оперативные данные в ViewModel один раз, а затем вызываете setValue при изменении данных. В действии вы также вызываете .observe(this, observer) один раз и обрабатываете результаты ViewModel в наблюдателе. В вашем случае, когда кнопка нажата, вы вызовете метод в ViewModel, ViewModel что-то обработает и вызовет .setValue для LiveData, и в вашей деятельности наблюдатель получит новые данные, на основе которых вы покажете или скроете свой диалог. Пожалуйста, прочитайте официальные документы для получения дополнительной информации - person TpoM6oH; 22.07.2018

Вы можете использовать один класс данных Settings, содержащий строки, целые числа и геттеры, которые инкапсулируют ваши данные.

Одного MutableLiveData<Settings> достаточно, чтобы отслеживать изменения и передавать изменения в пользовательский интерфейс. А с привязкой данных вам также не понадобится шаблонный код, если вы используете TextViews и EditTexts. Чтобы привязка данных работала с LiveData, вам нужно вызвать setValue() из LiveData, используя метод, определенный в ViewModel. Я сделал пример, как работает привязка данных и не работает с LiveData.

Вы можете увидеть мой ответ о том, как вы можете работать с LiveData с изменяемыми объектами и обновлять пользовательский интерфейс без кода котла и ненужных MutableLiveDatas или преобразование MutableLiveData<Object> в MutableLiveData<String> или MutableLiveData<Integer>.

Если вы можете поделиться своим файлом макета, я могу опубликовать более конкретный ответ.

person Thracian    schedule 20.07.2018