Регистрация Обозревателя против установки обработчика - PullRequest
1 голос
/ 03 мая 2019

Я устанавливаю шаблон MVVM в своем приложении для Android, используя класс ViewModel из Android.

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

Ранее я и мои коллеги использовали rx Subject в ViewModel, чтобы Fragment мог наблюдать его и запускать навигацию или другие специфичные для Fragment вещи при получении нового элемента. Элементы обычно состоят из строк или перечислимых элементов. Обратите внимание, что мы обработали удаление Observer в методе onDestroy нашего фрагмента.

Теперь мы можем просто установить Observer для поля MutableLiveData поля ViewModel почти так же, как мы делаем с rx Observer. Кроме того, Android должен автоматически обрабатывать удаление / обновление Observer в течение жизненного цикла Fragment.

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

Мне на самом деле не очень нравится этот подход, так как наличие некоторых наблюдаемых строк или полей перечисления имеет, на мой взгляд, несколько проблем:

  1. Не совсем понятно, что должен вызывать каждый элемент strng / enum. Вы можете дать им читаемые подсказки значений / имен, но это не то же самое, что имя метода с правильными строками документов в любом случае
  2. Вы можете отправить дополнительные аргументы в Bundles, но это не похоже на правильную сигнатуру метода с типами и т. Д.

Таким образом, я бы предпочел использовать интерфейс и установить / сбросить обработчик, чем наблюдать за полем.

Итак, что у меня сейчас есть в моем фрагменте

viewModel.observe(this, Observer {
  when(it.event) {
    "I did This" -> handleViewModelDidThis(it.bundle),
    "I did That" -> handleViewModelDidThat(it.bundle)
  }
})

Хотелось бы, чтобы у меня был ViewModelEventsHandler интерфейс вроде:

interface ViewModelEventsHandler {
    /**
     * Handle the fact that the ViewModel did this.
     * @parameter how How the ViewModel did this
     * @parameter when When did the ViewModel do this
     */
    fun viewModelDidThis(String how, Date when)

    /**
     * Handle the fact that the ViewModel did that.
     * @parameter where Where did the ViewModel do that
     * @parameter withWho Who was with him when he did that
     */
    fun viewModelDidThis(Location where, List<Friends> withWho)
}

А потом в Fragment

viewModel.eventsHandler = ViewModelEventsHandler {

    override fun viewModelDidThis(String how, Date when) {
        handleViewModelDidThis(how, when)
    }

    override fun viewModelDidThis(Location where, List<Friends> withWho) {
        handleViewModelDidThat(where, withWho)
    }
}

Итак, мой вопрос таков: учитывая, что в обоих случаях я регистрирую анонимный класс, который ссылается на методы в моем Fragment (первый - Observer, а второй - мой ViewModelEventsHandler), есть ли какая-то разница? что делает Observer «более безопасным» с точки зрения удержания экземпляра Fragment или они оба ведут себя одинаково? Если есть какая-либо разница, как я мог бы настроить надлежащий обратный вызов / обработчик для конкретной адресации возможных ViewModel событий вместо прослушивания «небезопасных» значений в живом поле?

Спасибо

Ответы [ 2 ]

1 голос
/ 03 мая 2019

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

Также ваши две точки:

  1. Не совсем понятно, что должен вызывать каждый элемент strng / enum. Вы можете дать им читаемые подсказки значения / имена, но это не похоже на имя метода с правильными строками документации в любом случае
  2. Вы можете отправлять дополнительные аргументы в Bundles, но это не похоже на правильную сигнатуру метода с типами и т. Д.

Правильны, за исключением того, что и RxJava, и LiveData могут легко справиться с этими проблемами, потому что они являются проблемами проектирования, а не инструментами. Например, ваш Event класс может выглядеть так:

class Event(event: String, data: Bundle)

Не делай этого. Лучший способ сделать:

abstract class Event()

class DoneThisEvent(how:How, when:Time): Event()
class DoneThatEvent(location:Location, withWho:Person): Event()


Кроме того, вы должны думать о зависимости между событиями. Другими словами, могут ли события «Я сделал это» и «Я сделал То» сосуществовать одновременно? Сравнение данной реализации LiveData и ViewModelEventsHandler не является справедливым, поскольку в реализации LiveData предполагается, что эти два события не могут сосуществовать, а в реализации ViewModelEventsHandler они могут. Давайте сравним оба сценария:

Если два события не могут сосуществовать

Тогда ваш пример LiveData имеет смысл, но все же есть место для улучшения:

viewModel.iDidThisAndThat.observe(this, Observer { done ->
    when(done) {
        is DoneThisEvent -> handleViewModelDidThis(done.how, done.when) // Notice the automatic type casting
        is DoneThatEvent -> handleViewModelDidThat(done.location, done.withWho)
    }
})

Также проверьте ваш ViewModelEventsHandler аналог.

viewModel.eventsHandler = ViewModelEventsHandler {

    override fun viewModelDidThisAndThat(done: Event) {
        when(done) {
            is DoneThisEvent -> handleViewModelDidThis(done.how, done.when) // Notice the automatic type casting
            is DoneThatEvent -> handleViewModelDidThat(done.location, done.withWho)
        }
    }
}

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


Если два события могут сосуществовать

Если два события независимы, то они должны наблюдаться независимо.

// In activity onCreate
viewModel.iDidThis.observe(this, Observer { doneThis ->
    handleViewModelDidThis(doneThis.how, doneThis.when)
})

viewModel.iDidThat.observe(this, Observer { doneThat ->
    handleViewModelDidThat(doneThat.location, doneThat.withWho)
}



// In your ViewModel
val iDidThis: LiveData<DoneThisEvent>
val iDidThat: LiveData<DoneThatEvent>

Эта реализация LiveData не должна выглядеть уродливее или менее управляемой, чем ее ViewModelEventsHandler аналог:

viewModel.eventsHandler = ViewModelEventsHandler {
    override fun viewModelDidThis(String how, Date when) {
        handleViewModelDidThis(how, when)
    }

    override fun viewModelDidThis(Location where, List<Friends> withWho) {
        handleViewModelDidThat(where, withWho)
    }
}
1 голос
/ 03 мая 2019

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

developer.android.com / Jetpack / Docs / руководство # синфазного принципы

...