MVVM с модернизацией - как справиться с большим количеством LiveData в репозитории?

Я следую этому руководству по использованию MVVM с Retrofit

https://medium.com/@ronkan26/viewmodel-using-retrofit-mvvm-architecture-f759a0291b49

где пользователь помещает MutableLiveData внутри класса Repository:

public class MovieRepository {
    private static final ApiInterface myInterface;
    private final MutableLiveData<EntityMovieOutputs> listOfMovies = new MutableLiveData<>();

private static MovieRepository newsRepository;

    public static MovieRepository getInstance(){
        if (newsRepository == null){
            newsRepository = new NewsRepository();
        }
        return movieRepository;
    }

    public MovieRepository(){
        myInterface = RetrofitService.getInterface();
    }

Я создаю простое приложение и заметил, что мой класс репозитория быстро заполняется множеством объектов MutableLiveData. Действительно ли это правильный способ реализации MVVM, LiveData и шаблона репозитория?

введите описание изображения здесь

Edit1: ________________________________________________

Я создал объект AdminLiveData, который просто содержит LiveData и имеет геттеры.

введите описание изображения здесь

Но как мне получить ссылку на ViewModel внутри моего AdminRepo класса, чтобы я мог уведомить LiveData внутри ViewModel, когда вызов Retrofit Network завершен?

private AdminService  adminService;
    
public AdminRepo(Application application) {
        BaseApplication baseApplication = (BaseApplication) application;
        RetrofitClient client = baseApplication.getRetrofitClient();
        adminService = client.getRetrofit().create(AdminService.class);  

        //AdminViewModel viewModel = (AdminViewModel) .... 
        // Not sure how to get reference to the viewmodel here so I can get the 
        // LiveData object and call postValue after the retrofit calls
}

    public void getFirstPageMembers(int offset, int limit) {
        adminService.getUsersPaginitation(offset, limit).enqueue(new Callback<List<UserInfo>>() {
            @Override
            public void onResponse(@NonNull Call<List<UserInfo>> call, @NonNull Response<List<UserInfo>> response) {
                if (response.body() != null) {
                    //firstPageLiveData.postValue(response.body());
                    //Since I create the LiveData inside the ViewModel class 
                   //instead, how do I get reference to the ViewModel's LiveData?
                }
            }

            @Override
            public void onFailure(@NonNull Call<List<UserInfo>> call, @NonNull Throwable t) {
                //firstPageLiveData.postValue(null);
            }
        });
    }

AdminViewModel:

public class AdminActivityViewModel extends AndroidViewModel {

    private AdminRepo repo;

    private AdminLiveData adminLiveData = new AdminLiveData();
    
    public AdminActivityViewModel(@NonNull Application application) {
        super(application);

        repo = new AdminRepo(application);
    }

Как мне получить ссылку на AdminViewModel из моего класса AdminRepo?


person DIRTY DAVE    schedule 04.03.2021    source источник
comment
Если возможно, вы можете разбить репозиторий по ресурсу (сущности). т.е. один репозиторий должен обрабатывать только один ресурс. Вы также можете таким же образом разбить интерфейс API ретро-подгонки.   -  person ADM    schedule 04.03.2021
comment
извините, не могли бы вы привести мне быстрый пример? Я не понимаю, что вы имеете в виду под Entity. Вы имеете в виду отдельный класс, содержащий все объекты LiveData для репозитория? нравится? stackoverflow.com/q/45729606/11110509   -  person DIRTY DAVE    schedule 23.03.2021
comment
да более менее то же самое. Держите вещи отдельно. Entity / Resource - это отдельный объект, например, в вашем случае Movie - это ресурс, а Admin / User - другой ресурс. Вы можете разделить классы на основе этого. Будет MovieRepo и еще одно AdminRepo то же самое, что вы можете сделать с интерфейсом в стиле ретро.   -  person ADM    schedule 23.03.2021
comment
о, хорошо, спасибо, если ты хочешь быстро ответить, я могу дать тебе награду   -  person DIRTY DAVE    schedule 23.03.2021
comment
Спасибо, Дэйв, но осталось еще 7 дней. Мы можем поискать лучший ответ. Проблема с архитектурой в том, что вы не можете указать пальцем на то, что лучше. так что это не будет однозначный ответ. Я просто предложил, что, на мой взгляд, будет лучше. Давайте посмотрим .   -  person ADM    schedule 23.03.2021


Ответы (2)


Репозиторий и ViewModel

Объекты репозитория в проектах Android следует рассматривать как ворота во внешний мир. На этом уровне происходит взаимодействие со средствами сохранения состояния (Network, SQLite, Shared Pref). Из-за этого входящие данные не должны соответствовать среде Android. Например, строковое поле даты во входящем DTO должно быть преобразовано в объект даты с использованием локальной даты и времени, вам может потребоваться сохранить данные, поступающие с сервера в локальную БД. Кроме того, на этом уровне могут выполняться другие задачи, связанные с данными, например кэширование в памяти.

ViewModels представляют данные, которые отображаются в пользовательском интерфейсе. Данные в этом слое должны быть готовы к отображению на экране. Например, для представления могут потребоваться данные, поступающие из двух разных HTTP-запросов, вам следует объединить входящие данные на этом уровне. (Тем не менее, вы можете разделить обязанности и дальше. Если эти два запроса являются частью одной задачи или цели, вы можете выполнить эту операцию на уровне варианта использования.) С точки зрения Android, модели представления несут большую ответственность, например, предотвращение уничтожения данных. в изменениях конфигурации. В моделях представления рекомендуется, чтобы данные представлялись слою представления с помощью LiveData. Это связано с тем, что LiveData сохраняет последнее состояние данных и знает о жизненном цикле уровня представления (если он используется правильно).

Связь между репозиторием и ViewModel

Прежде всего, уровень репозитория не должен знать о существовании какой-либо модели представления, поэтому вы не должны хранить ссылку на модель представления на уровне репозитория. На это есть несколько причин,

  • В большинстве случаев одного объекта репозитория для набора взаимодействий достаточно для всего приложения. Если ссылка на модель представления сохраняется, это вызывает утечку памяти.
  • Если серверный API, локальное хранилище и другие компоненты, которые используются на уровне репо, хорошо спроектированы, уровень репозитория менее подвержен изменениям по сравнению с моделью представления.
  • Ответственность за уровень репозитория заключается в извлечении данных откуда-то, поэтому это означает, что он не имеет ничего общего со слоями, связанными с представлением.

Конечно, нам нужна какая-то ссылка на модель представления, чтобы уведомить ее о завершении запроса, но мы должны делать это систематически неявно, а не напрямую.

Обратный вызов. Это устаревший способ отправки данных обратно в модель представления. Широкое использование обратных вызовов в вашей кодовой базе вызывает нежелательный ад обратных вызовов. Кроме того, механизм структурированной отмены сложно реализовать с помощью обратных вызовов.

LiveData: на первый взгляд кажется, что он отлично подходит для этой цели, но это не так. Причины могут быть указаны как

  • LiveData разработан как хранилище данных с учетом жизненного цикла. Это накладные расходы для вашей цели, потому что вы не имеете никакого отношения к жизненным циклам представления в этом слое.
  • В большинстве случаев операции выборки данных являются одноразовыми (как в вашем случае), но LiveData предназначена для потоков.
  • У него нет встроенной поддержки для структурированной отмены.
  • С архитектурной точки зрения классы, связанные с Android, такие как живые данные, не должны импортироваться на уровень репозитория. Классы репозитория должны быть реализованы как простой класс Kotlin или java.

В вашем случае это особенно плохая практика, потому что HTTP-запросы не должны изменять состояние вашего объекта репозитория. Вы используете LiveData как своего рода кеш, но для этого нет такого требования, поэтому вам следует избегать этого. Тем не менее, если вам нужно использовать LiveData в репо, вы должны либо передать MutableLiveData в метод запроса в качестве параметра, чтобы вы могли отправить ответ через эти LiveData, либо вернуть LiveData в методе запроса.

RxJava: это один из вариантов. Он поддерживает одноразовые запросы (Single), горячие потоки (Subject) и холодные потоки (Observable). Он каким-то образом поддерживает структурированную отмену (CompositeDisposable). Он имеет стабильный API и широко используется в течение многих лет. Он также упрощает множество различных сетевых операций, таких как параллельные запросы, последовательные запросы, манипулирование данными, переключение потоков и многое другое с помощью своих операторов.

Сопрограммы: это еще один вариант и, на мой взгляд, лучший. Хотя его API не полностью стабилен, я использовал его во многих разных проектах и ​​не видел никаких проблем. Он поддерживает одноразовые запросы (функции приостановки), горячие потоки (каналы и поток состояний) и холодные потоки (поток). Это действительно полезно в проектах, требующих сложных потоков данных. Это встроенная функция в Kotlin со всеми операторами. Он действительно элегантно поддерживает структурированный параллелизм. Он имеет множество различных операторных функций, и новые операторные функции легче реализовать по сравнению с RxJava. Он также имеет полезные функции расширения для использования с ViewModel.

Подводя итог, уровень репозитория - это шлюз для входящих данных, это первое место, где вы манипулируете данными, чтобы соответствовать требованиям вашего приложения, как я упоминал выше. Операции, которые могут быть выполнены на этом уровне, могут быть кратко перечислены как сопоставление входящих данных, выбор места для извлечения данных (локальный или удаленный источник) и кэширование. Есть много вариантов передачи данных обратно в класс, запрашивающий данные, но RxJava и Coroutines лучше других, как я объяснил выше. На мой взгляд, если вы не знакомы с ними обоими, приложите все усилия к Coroutines.

person M.ekici    schedule 25.03.2021
comment
From an architectural perspective, android related classes like live data should not be imported into the repository layer - почему бы и нет, особенно если это сделано для Android-приложения. - person Anatolii; 27.03.2021
comment
Это потому, что это последний уровень перед операциями, связанными с конкретным устройством или средой, с сетью, базой данных, файлом и т. Д. Эти операции должны выполняться через интерфейсы, чтобы репозиторий ничего не знал о базовых операциях. Я имею в виду, что репозиторий не должен знать об окружении и компилироваться на чистом Kotlin или Java. Если вы используете классы, связанные с Android, такие как LiveData, вы не можете скомпилировать его, как я сказал. Это становится более важным, если вы разрабатываете мультиплатформенный проект. - person M.ekici; 28.03.2021
comment
Неплохо подмечено. Тогда, возможно, вам следует пояснить это в своем ответе - сказать, что Repository в идеале должен быть независимым от платформы, а LiveData делает его зависимым от Android. - person Anatolii; 28.03.2021
comment
@DIRTY DAVE ответ на ваш вопрос? Если нет, то какой у вас вопрос? - person Anatolii; 29.03.2021

Решение, которое вы ищете, зависит от того, как разработано ваше приложение. Вы можете попробовать следующее:

  • Держите свое приложение модульным - как упоминалось в @ADM, разделите ваш репозиторий на меньшие
  • Переместите живые данные из репозитория - нет необходимости хранить живые данные в репозитории (в вашем случае синглтон) на протяжении всего жизненного цикла приложения, в то время как может быть только несколько экранов, на которых нужны другие данные.
  • При этом - держите ваши живые данные в моделях просмотра - это самый стандартный способ сделать. Вы можете ознакомиться с этой статьей, в которой описывается Retrofit-ViewModel- Шаблон репозитория LiveData
  • Если у вас сложный экран и много живых объектов данных, вы все равно можете отображать сущности в экранное представление данных с помощью событий / состояний / команд (назовите это как хотите), которые довольно хорошо описаны здесь. Таким образом, у вас есть один LiveData<ScreenState>, и вам просто нужно сопоставить свои объекты.

Кроме того, вы можете использовать сопрограммы с модификацией, поскольку сопрограммы рекомендуются теперь для обработки фоновых операций и поддержки Kotlin, если вы хотите попробовать.

Также эти ссылки могут помешать вам при изучении различных архитектур или решений для решения вашей проблемы architecture-components-samples или образцы архитектуры (хотя в основном с использованием котлина).

person Luke    schedule 23.03.2021
comment
Я создал отдельный класс AdminLiveData для хранения всех различных LiveData объектов. Затем я создаю экземпляр этого класса внутри своего AdminViewModel. Но как мне получить ссылку на ViewModel и вызвать метод .getAdminLiveData() из моего класса AdminRepo? - person DIRTY DAVE; 24.03.2021
comment
Если вы используете модернизацию с обратными вызовами, вам необходимо определить слушателя, который реализует ViewModel, и передать его конструктору вашего репозитория. Однако я настоятельно рекомендую использовать сопрограммы или rxJava, поскольку это немного облегчает жизнь, когда вы их изучите. Я считаю, что вам также не нужно столько живых данных, особенно когда они одного типа. Если вам нужны все эти живые данные на одном экране (модель просмотра), возможно, вы могли бы попробовать подход с состояниями / командами. Кроме того, у вас может быть несколько моделей просмотра, если они независимы, но вы редко используете один и тот же экран. - person Luke; 24.03.2021