#android #kotlin #android-livedata #kotlin-coroutines
#Android #kotlin #android-livedata #kotlin-сопрограммы
Вопрос:
Компания, в которой я только начал работать, использует так называемый Navigator, который я пока интерпретирую как модель просмотра без состояния. Мой навигатор получает несколько вариантов использования, каждый из которых содержит 1 функцию приостановки. Результат любого из этих вариантов использования может оказаться в одном LiveData. У навигатора нет области действия сопрограммы, поэтому я передаю ответственность за приостановку использования фрагмента fetchValue()
.
Самый текущий код в project имеет LiveData на уровне данных, чего я старался не делать. Из-за этого их livedata связана из view с dao.
Мои упрощенные классы:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
val url = MediatorLiveData<String>()
fun goToUrl1() {
url.fetchValue { getUrl1() }
}
fun goToUrl2() {
url.fetchValue { getUrl2() }
}
fun <T> MediatorLiveData<T>.fetchValue(provideValue: suspend () -> T) {
val liveData = liveData { emit(provideValue()) }
addSource(liveData) {
removeSource(liveData)
value = it
}
}
}
class MyFeatureFragment : Fragment {
val viewModel: MyFeatureViewModel by viewModel()
val navigator: MyFeatureNavigator by inject()
fun onViewCreated() {
button.setOnClickListener { navigator.goToUrl1() }
navigator.url.observe(viewLifecycleOwner, Observer { url ->
openUrl(url)
})
}
}
Мои два вопроса:
fetchValue()
Хороший ли способ связать функцию приостановки с LiveData? Может ли это произойти утечка? Есть другие проблемы?- Моя основная причина использовать только сопрограммы (и поток) на уровне данных — «потому что так сказал Google». Какая лучшая причина для этого? И: каков наилучший компромисс в соответствии с проектом и текущими передовыми методами кодирования?
Ответ №1:
- Является ли fetchValue() хорошим способом связать функцию приостановки с LiveData? Может ли это произойти утечка? Есть другие проблемы?
Как правило, это должно работать. Вероятно, вам следует удалить предыдущий источник MediatorLiveData
перед добавлением нового, в противном случае, если вы получите два вызова fetchValue
подряд, первый URL-адрес может быть извлечен медленнее, поэтому он придет позже и выиграет. Я не вижу никаких других проблем с корректностью, но этот код довольно сложный, создает пару промежуточных объектов и, как правило, его трудно читать.
- Моя основная причина использовать только сопрограммы (и поток) на уровне данных — «потому что так сказал Google». Какая лучшая причина для этого?
Google предоставил множество полезных расширений для использования сопрограмм на уровне пользовательского интерфейса, например, взгляните на эту страницу . Очевидно, что они поощряют людей использовать его.
Вероятно, вы имеете в виду рекомендацию использовать LiveData
вместо уровня Flow
пользовательского интерфейса. Это не строгое правило, и у него есть одна причина: LiveData
является владельцем значения, он сохраняет свою ценность и немедленно предоставляет ее новым подписчикам, не выполняя никакой работы. Это особенно полезно на уровне пользовательского интерфейса / ViewModel — когда происходит изменение конфигурации и действие / фрагмент воссоздается, вновь созданный activity / fragment использует ту же модель представления, подписывается на LiveData
нее и получает значение бесплатно.
В то же время Flow
«холодно», и если вы предоставляете поток из своей модели представления, каждая реконфигурация будет запускать новую коллекцию потоков, и поток будет выполняться с нуля.
Так, например, если вы извлекаете данные из базы данных или сети, LiveData
просто предоставите последнее значение новому подписчику и Flow
снова выполните дорогостоящую операцию db / network.
Итак, как я уже сказал, строгого правила не существует, это зависит от конкретного варианта использования. Также я нахожу его очень полезным для использования Flow
в моделях просмотра — он предоставляет множество операторов и делает код чистым и кратким. Но затем я преобразовываю его в a LiveData
с помощью расширений, подобных asLiveData()
и предоставляю это LiveData
пользовательскому интерфейсу. Таким образом, я получаю лучшее от обоих слов — LiveData
улавливает значение между реконфигурациями и Flow
делает код моделей представления красивым и чистым. Также вы можете использовать последние StateFlow
версии, и SharedFlow
часто они также могут помочь преодолеть упомянутую Flow
проблему на уровне пользовательского интерфейса.
Возвращаясь к вашему коду, я бы реализовал его следующим образом:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
private val currentUseCase = MutableStateFlow<UseCase?>(null)
val url = currentUseCase.filterNotNull().mapLatest { source -> source.getData()}.asLiveData()
fun goToUrl1() {
currentUseCase.value = getUrl1
}
fun goToUrl2() {
currentUseCase.value = getUrl2
}
}
Таким образом, нет никаких условий гонки, о которых нужно заботиться, и код чистый.
- И: каков наилучший компромисс в соответствии с проектом и текущими передовыми методами кодирования?
Это спорный вопрос, и это должно быть в первую очередь командное решение. В большинстве проектов, в которых я участвовал, мы приняли это правило: при исправлении ошибок, выполнении обслуживания существующего кода следует придерживаться того же стиля. При выполнении большого рефакторинга / внедрении новых функций следует использовать новейшие методы, принятые командой.