Как связать View и ViewModel относительно поворота экрана?

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

При определенных событиях модели представление должно обновляться. Есть два основных варианта:

  1. ViewModel обновляет View.
  2. Представление наблюдает за ViewModel и обновляет себя.

В первом случае ViewModel нужна ссылка на представление. Я мог бы внедрить View в ViewModel, но мне кажется, что было бы лучше внедрить VieModel в View.

Каков лучший стиль, чтобы присоединиться к ним?

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

Эта проверка кажется какой-то нечистой. Я бы ожидал, что в ViewModel будет специальный API для этого, если это будет стандартной практикой. Без меня есть ощущение, что я на ложном пути.

Каковы хорошие шаблоны, чтобы справиться с этим чистым стандартным способом?


person Blcknx    schedule 26.03.2018    source источник


Ответы (1)


Итак... Вам действительно не нужно подключать ViewModel и Activity/Fragment "относительно поворота экрана", вы получаете это бесплатно - это одно из преимуществ.

Официальная документация действительно хороша.

Вы подключаете ViewModel к своему представлению в onCreate() чем-то вроде

public class MyActivity extends AppCompatActivity {

    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            updateUI()
        });
    }
}

И хотя верно то, что вы говорите, что изменение ориентации снова вызовет onCreate(), неверно, что это создаст новую ViewModel. MyViewModel создается только в первый раз в onCreate. Повторно созданные действия получают тот же MyViewModel экземпляр, созданный первым действием. Это верно даже для разных фрагментов/активностей, ссылающихся на одну и ту же ViewModel.

Вы никогда не должны внедрять представление в ViewModel. Это равносильно утопающим щенкам. Если вам нужен контекст в ViewModel, вместо этого расширьте AndroidViewModel (и передайте ему Application).

Что вы делаете, так это создаете ViewModel, которая содержит все состояния. И обрабатывает выборку данных из сети или с диска или что-то еще. Все, что не связано с пользовательским интерфейсом, входит в ViewModel (как правило). Все материалы, обновляющие представление, входят в активность/фрагмент.

ViewModel для приведенного выше примера может выглядеть так:

public class MyViewModel extends ViewModel {

    private MutableLiveData<List<User>> users;

    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Это часто означает, что события кликов, вероятно, должны передаваться в ViewModel, чтобы она могла массировать данные. И представление будет просто реагировать на обновленные (массированные) данные.

person Algar    schedule 26.03.2018
comment
Привет! Второй раз не создается. Наблюдатель регистрируется второй раз. В зависимости от реализации getUsers().observe() обратный вызов updateUI() может быть вызван дважды после первого вращения и трижды после второго вращения. - person Blcknx; 26.03.2018
comment
Затем вызов .getUsers() рисует логику модели в представлении. Это вторая причина, почему я считаю, что это довольно плохой пример официальной документации. На мой взгляд, они учат плохой практике. Теперь нам нужно найти лучшую практику, по крайней мере, лучшую практику. - person Blcknx; 26.03.2018
comment
О, извините, если я неправильно понял. Но я не вижу проблемы. Если вы поворачиваете экран, ваша активность проходит через его onDestroy. Поэтому, конечно, вы хотите, чтобы наблюдатель был зарегистрирован снова, чтобы вы могли снова получить данные для обновления пользовательского интерфейса — поскольку пользовательского интерфейса больше нет. Или вы говорите, что получаете, например. 3 обновления подряд? - person Algar; 27.03.2018
comment
И я не понимаю, что вы имеете в виду под .getUsers() рисует логику модели в представлении. Наблюдатели должны рисовать (наблюдать) только те части модели, которые должны быть представлены пользователю. Я никоим образом не считаю это чем-то плохим или плохой практикой. Или я просто что-то не понимаю, потому что вы явно видите проблему, которую я не вижу :) - person Algar; 27.03.2018
comment
Да, это то, что я говорю. В зависимости от реализации observe() я получаю вызов updateUI() столько раз подряд, сколько было зарегистрировано. Нет призыва удалить его раньше. Вы можете проверить несколько вызовов с помощью оператора журнала или с помощью отладчика. В примерах, которые я видел в stackoverflow, люди реализуют наблюдаемые объекты внутри с помощью List, а не с Set. - person Blcknx; 27.03.2018
comment
Просто небольшой запах. 'model.getUsers().observe()` можно заменить на model.observeUsers(). - person Blcknx; 27.03.2018
comment
Как тогда будет выглядеть observeUsers()? - person Algar; 27.03.2018
comment
Не будем углубляться в это направление. Это касается разделения и общих принципов хорошей архитектуры. Ключевым словом может быть Закон Деметры. Это не относится к ViewModels. - person Blcknx; 27.03.2018
comment
Я думаю, что на первый вопрос можно ответить так. Вызов .observe() обычно устанавливает наблюдателя ViewModel. Если у каждого представления есть собственная модель представления, observe() может быть регистрацией set() вместо регистрации add(), простым свойством вместо списка наблюдателей. В результате вызов updateUI() выполняется только один раз, поскольку списка наблюдателей нет. - person Blcknx; 27.03.2018
comment
Пример, который вы привели из официального учебника, является частным случаем. Метод .observe() заимствован из LiveData. Я не могу предсказать, как он будет вести себя при многократной регистрации одного и того же действия во время вращения. Также мне несколько спорным выглядит это заимствование метода наблюдения. Они рекомендуют это как стандартную практику? Тогда они должны дать ему имя. Иначе они не должны подавать такой странный пример. - person Blcknx; 27.03.2018
comment
A дал ответ на вопрос, как тогда выглядело быObservUsers()? здесь: stackoverflow.com/questions/49511750/ - person Blcknx; 28.03.2018