Общая ViewModel для облегчения связи между фрагментами и родительской активностью

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

Давайте посмотрим на пример экрана приложения:

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

<сильный>1. Мне нужно реализовать транзакции фрагментов, чтобы разрешить замену фрагмента на экране в зависимости от взаимодействия с пользователем. Есть три способа, которые я могу придумать и реализовать:

  • путь обратных вызовов. Наличие обратного вызова интерфейса onFragmentAction во фрагменте и реализация его действия. Таким образом, в основном, когда пользователь нажимает кнопку в FragmentA, я могу вызвать onFragmentAction с параметрами, чтобы действие запускалось и запускало, например, транзакцию, чтобы заменить ее на FragmentB
  • внедрить компонент Navigation из JetPack. Хотя я попробовал это и кажется довольно простым, у меня возникла проблема, поскольку я не смог получить текущий фрагмент.
  • Используйте общий ViewModel между фрагментом и действием, обновите его из фрагмента и наблюдайте за ним в действии. Это будет «замена» обратных вызовов

<сильный>2. Поскольку FAB находится в родительской активности, при нажатии мне нужно иметь возможность взаимодействовать с текущим видимым фрагментом и выполнять действие. Например, добавьте новый элемент в recyclerview внутри фрагмента. Таким образом, в основном способ связи между действием и фрагментом Есть два способа, которыми я могу думать о том, как сделать это

  • Если не использовать Navigation, я могу использовать findFragmentById и получить текущий фрагмент и запустить общедоступный метод для запуска действия.
  • Используя общий «ViewMode» между фрагментом и действием, обновите его из действия и наблюдайте за ним во фрагменте.

Итак, как вы можете видеть, рекомендуемым способом навигации будет использование нового архитектурного компонента «Навигация», однако на данный момент ему не хватает способа получить текущий экземпляр фрагмента, поэтому я не знаю, как общаться между деятельность и фрагмент. Этого можно было бы достичь с помощью shared ViewModel, но здесь у меня есть недостающая часть: я понимаю, что связь между фрагментами может осуществляться с помощью общей ViewModel. Я думаю, что это имеет смысл, когда у фрагментов есть что-то общее для этого, например сценарий Master/Detail, и совместное использование одной и той же модели представления очень полезно.

Но если говорить между действием и ВСЕМИ фрагментами, как можно использовать общий ViewModel? Для каждого фрагмента нужна своя сложная ViewModel. Может ли это быть GeneralViewModel, который создается в действии и во всех фрагментах вместе с обычной моделью представления фрагмента, поэтому в каждом фрагменте должно быть 2 модели представления.

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

Любая информация с радостью принимается.

Позже редактировать. Вот пример кода, основанный на комментарии ниже. Это решение моего вопроса? Может ли это обрабатывать как изменения между фрагментами, так и родительской активностью, и это на рекомендуемой стороне.

 private class GlobalViewModel ():ViewModel(){

      var eventFromActivity:MutableLiveData<Event>
      var eventFromFragment:MutableLiveData<Event>


      fun setEventFromActivity(event:Event){
          eventFromActivity.value = event
      }

      fun setEventFromFragment(event:Event){
          eventFromFragment.value = event
      }

 } 

Тогда в моей деятельности

class HomeActivity: AppCompatActivity(){

   onCreate{
       viewModel = ViewModelProviders.of(this, factory)
                .get(GlobalViewModel::class.java)

        viewModel.eventsFromFragment.observe(){
           //based on the Event values, could update toolbar title, could start
           // new fragment, could show a dialog or snackbar
        ....
        }   

     //when need to update the fragment do 
     viewModel.setEventFromActivity(event)
   }
}

Тогда во всех фрагментах есть что-то вроде этого

class FragmentA:Fragment(){

  onViewCreated(){

       viewModel = ViewModelProviders.of(this, factory)
                .get(GlobalViewModel::class.java)

        viewModel.eventsFromActivity.observe(){
           // based on Event value, trigger a fun from the fragment 
        ....
        }

        viewModelFragment = ViewModelProviders.of(this, factory)
            .get(FragmentAViewModel::class.java)

        viewModelFragment.some.observe(){
        ....
        }   

  //when need to update the activity do 
     viewModel.setEventFromFragment(event)      
  }

}

comment
Но, если говорить между активностью и ВСЕМИ фрагментами, как можно использовать общую ViewModel? -- действия и фрагменты могут иметь более одного ViewModel за штуку, если каждый ViewModel в действии/фрагменте относится к другому классу. Может ли это быть GeneralViewModel, который создается в действии и во всех фрагментах вместе с обычной моделью представления фрагмента, поэтому в каждом фрагменте должно быть 2 модели представления. -- да. Однако это очень длинный вопрос, и поэтому я предполагаю, что да - это не тот ответ, который вы ищете, или, возможно, я что-то упускаю в вопросе.   -  person CommonsWare    schedule 25.07.2018
comment
@CommonsWare большое спасибо за ваш ценный вклад. Я добавил пример кода, чтобы быть уверенным, что правильно понял даже то, что написал. Код выше имеет смысл?   -  person Alin    schedule 25.07.2018
comment
Предполагая, что ваши имена являются заполнителями, я не знаю ничего плохого в этом подходе, и, насколько мне известно, это более или менее то, о чем здесь думает Google. Однако требования здесь будут различаться в зависимости от архитектуры графического интерфейса. Например, в структуре Model-View-Intent (MVI) будет меньше необходимости в том, чтобы фрагменты и действия взаимодействовали друг с другом, поскольку они оба в основном инициируют действия, которые запускают новое состояние представления, которое будет доставлено всем сторонам.   -  person CommonsWare    schedule 25.07.2018
comment
Не уверен, что вы подразумеваете под именами заполнители. Я пытаюсь реализовать предложенный шаблон MVVM, который рекомендует Google. Идея состоит в том, чтобы использовать как можно больше компонентов архитектуры: ViewModel, LiveData, Room, Navigation и смешивать их наилучшим образом.   -  person Alin    schedule 25.07.2018
comment
Я бы не назвал вещи GlobalViewModel, eventsFromActivity(), FragmentA, FragmentAViewModel и т. д. Именно это я имел в виду, когда предположил, что ваши имена являются заполнителями.   -  person CommonsWare    schedule 26.07.2018
comment
О да, теперь я понимаю. Мне нравится GlobalViewModel, так как он будет использоваться родительской активностью и всеми остальными фрагментами, поэтому он является глобальным. eventsFromActivity и eventsFromFragment также могут работать, поскольку единственная цель этой ViewModel - передавать события такого рода. Еще лучше реализовать способ обработки Event только один раз, чтобы избежать повторного запуска при изменении ориентации, и все будет выглядеть хорошо. Я на самом деле попробовал и работает так, как мне нужно, активность может общаться с фрагментом без каких-либо обратных вызовов и интерфейсов. Потрясающий. Спасибо.   -  person Alin    schedule 26.07.2018
comment
Просто немного измените свой код, передайте активность при создании модели представления наилучшим образом.... viewModel = ViewModelProviders.of(activity!!, factory) .get(GlobalViewModel::class.java)   -  person Saurabh Khare    schedule 30.11.2018