Навигация с помощью MVVM android

У меня есть приложение, использующее класс Androids ViewModel и компонент навигации для навигации между фрагментами. Как бы я справился с навигацией из ViewModel? Я использую RxJava, и я думал о том, чтобы фрагменты прослушивали события навигации, а затем запускали навигацию таким образом. Каков нормальный способ справиться с этим? Я также использую Dagger для внедрения зависимостей, если это помогает.

2 ответа

Решение

Если бы вы использовали MVP, P просто вызвал бы метод на V, который запускает навигацию.

Эквивалентный подход в MVVM заключается в том, чтобы иметь выделенный наблюдаемый / слушатель / обратный вызов, если с помощью RxJava он может управляться PublishSubject, Это удовлетворяет единовременному требованию. Если вместо этого вам нужно ответить на события, которые могут быть отправлены до подписки, вы можете использовать BehaviorSubject<Optional<T>> и создать его с createDefault(absent<T>()), запустить его с onNext(Optional.of(navigationObject)) и затем сообщите ВМ, когда происходит навигация, и затем ВМ может очистить его с помощью onNext(absent())

В качестве альтернативы, если вы хотите перевести его в какое-либо всеобъемлющее состояние, подобное избыточному / mvi, вы можете иметь некоторый класс состояний со всем состоянием, включая некоторое свойство, которое указывает представлению на навигацию куда-либо, при получении / действии по нему представление будет сообщите виртуальной машине, что она это сделала, и виртуальная машина установит состояние, совпадающее с текущим, но без навигации. например (в Котлине) state = state.copy(navigateToX = false)

В соответствии с LiveData с SnackBar, Navigation и другими сообщениями в блоге:

Некоторые данные должны использоваться только один раз, например, сообщение Snackbar, событие навигации или триггер диалога.

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

Они подробно описывают использование класса SingleLiveEvent, который гарантирует, что каждое событие навигации принимается только один раз наблюдателем (т. Е. Вашим фрагментом, который имеет доступ к вашему NavController).

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

Для ваших событий вы можете использовать что-то вроде этого:

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    @Suppress("MemberVisibilityCanBePrivate")
    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

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

viewModel.getShowAlertFragment().observe(viewLifecycleOwner, Event{
        event?.getContentIfNotHandled()?.let {
            // your code here
    }
})

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

/**
 * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
 * already been handled.
 *
 * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
 */
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
    override fun onChanged(event: Event<T>?) {
        event?.getContentIfNotHandled()?.let {
            onEventUnhandledContent(it)
        }
    }
}

теперь с этим вы можете упростить свой код примерно так:

viewModel.getShowAlertFragment().observe(viewLifecycleOwner, EventObserver {
        // do you stuff here and you know it will only be called once
    }
})

Идея взята из этого файла в примерах Android Здесь

Другие вопросы по тегам