Как создавать составные модели представлений с временными вложенными представлениями?

Сценарий

У меня есть генератор викторин, который генерирует последовательность викторин разных классов. Последовательность имеет неограниченную длину.

Существует модель представления для генератора викторин. Для каждого типа викторины существует модель просмотра. Модель представления генератора викторин должна создавать модели представлений викторин в зависимости от их классов.

Проблема

Модель представления не должна содержать ссылку на жизненный цикл, но мне нужен жизненный цикл для создания моделей представлений.

ViewModelProviders.of(lifecycle).get(classForQuizzType);

Вопросы

Где я могу создать модели подвидов викторин?

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

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

Если нет чистого решения, что не так с моим подходом к архитектуре? Должен ли я использовать фрагменты для такого сценария?


person Blcknx    schedule 18.04.2018    source источник
comment
вы не можете создать new ViewModelProvider?   -  person pskink    schedule 18.04.2018
comment
Модели просмотра должны быть привязаны к жизненному циклу.   -  person Blcknx    schedule 18.04.2018
comment
но вы хотите, чтобы ваша новая викторина ViewModel была привязана к вашему мастеру ViewModel, верно?   -  person pskink    schedule 18.04.2018
comment
Не обязательно, но я понимаю вашу точку зрения. Для привязки композиции к жизненному циклу требуется только один дескриптор. Компоненты даже не должны быть ViewModel.   -  person Blcknx    schedule 18.04.2018
comment
заголовок говорит: How to generate view models from inside a view model? так что я не понимаю, что вам действительно нужно   -  person pskink    schedule 18.04.2018
comment
Мне нужно пересмотреть название сейчас.   -  person Blcknx    schedule 18.04.2018


Ответы (2)


Я даю первый ответ сам, вдохновленный pskink. Возможно, я обновлю ответ после некоторого опыта с предложенным подходом.

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

Для данного примера это означает, что хорошим местом для создания главного объекта является позиция верхнего уровня действия, где lifecycle доступен напрямую. На объекты опроса ссылаются главные объекты. Им не нужен прямой доступ к жизненному циклу, и их можно создавать где угодно, например внутри главного объекта. Это позволяет создавать их по запросу.

Компоненты могут быть или не быть подклассом ViewModel. Я считаю хорошей практикой расширить ViewModel. Этот родительский класс вводит метод onCleared. Это место для удаления наблюдателей из базовой модели. Без этого вы, вероятно, создадите утечку памяти.

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

Модели представления компонентов можно просто создать с помощью ключевого слова new. Нет необходимости использовать фабрику или провайдера.

person Blcknx    schedule 18.04.2018
comment
именно, ваши компоненты, скорее всего, являются контейнерами для LiveData объектов, и они не обязательно должны быть ViewModel (если для этого нет каких-либо требований) - person pskink; 18.04.2018
comment
Компоненты как контейнеры для объектов LiveData нуждаются в доступе к жизненному циклу объектов LiveData. Откуда взять жизненный цикл по требованию, если мастеру нельзя ссылаться на него? - person Blcknx; 20.04.2018
comment
liveData.observe(lifecycle, observer); Как и в исходном вопросе, жизненный цикл доступен только на верхнем уровне, но ни внутри представлений, ни внутри моделей представлений. Поэтому я не могу создавать компоненты в тех местах, которые являются контейнерами для LiveData. Мне нужно идти без LiveData. - person Blcknx; 20.04.2018
comment
Если функция LiveData заключается в автоматической отмене регистрации наблюдателей, мне, наверное, нужно вручную отменить регистрацию наблюдателей. - person Blcknx; 20.04.2018

Если нет чистого решения, что не так с моим подходом к архитектуре? Должен ли я использовать фрагменты для такого сценария?

Да, фрагменты - правильный выбор

Резюме:

  1. Нет реальной альтернативы связыванию Views с LiveData.
  2. При использовании LiveData требуется LifeCycle.
  3. Если жизненный цикл подпредставлений в последовательности должен быть короче, чем жизненный цикл активности, тогда лучше использовать фрагменты.

Подробности

Нет реальной альтернативы связыванию Views с LiveData.

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

а.) Изменяемые живые данные

Они требуют жизненного цикла. Ссылки очищаются автоматически, когда заканчивается жизненный цикл. Это рекомендуемое решение.

б.) Слабые ссылки

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

c.) Самодельный наблюдатель

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

В результате а.) является единственным возможным решением согласно моему опыту.

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

Упомянутые здесь подвиды создаются последовательно. Если мы привяжем их к активности, они будут накапливаться до тех пор, пока активность не исчезнет, ​​хотя они нужны только в течение небольшого промежутка времени в последовательности.

Фрагменты могут существовать в течение части времени действия. Это правильное решение для привязки к ним подвидов последовательности.

Пример кода

Викторины здесь называются вызовами. FragmentManger всегда относится к действию, а LifecycleOwner — либо к действию, либо к фрагменту.

# A view model acceptor interface for views

public interface ViewModelAcceptor<T extends  ViewModel> {
    void plugViewModel(
        T viewModel,
        LifecycleOwner lifecycleOwner,
        FragmentManager fragmentManager
    );
}

# In the parent view class of the challenges new challenges are created
# in sequence

ChallengeFragment challengeFragment = new ChallengeFragment();
challengeFragment.setChallengeViewModel(challengeViewModel);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(this.getId(), challengeFragment);
fragmentTransaction.commit();

# ChallengeFragment

public class ChallengeFragment extends Fragment {

    private ChallengeViewModel challengeViewModel;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        return new ChallengeView(getActivity(), null);
    }

    public void setChallengeViewModel(ChallengeViewModel challengeViewModel) {
        this.challengeViewModel = challengeViewModel;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ChallengeView challengeView = (ChallengeView) getView();
        Objects.requireNonNull(challengeView)
                .plugViewModel(challengeViewModel, this, getFragmentManager());
    }

}
# Challenge views are the child views of the sequence

public class ChallengeView extends ConstraintLayout implements ViewModelAcceptor<ChallengeViewModel> {
  [...]
}
person Blcknx    schedule 26.04.2018