[Разработка мобильных приложений, Разработка под Android, Kotlin] Android и привязка к жизненному циклу компонентов
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В данной статье хочу поделиться подходом, который использую для выполнения действий в определенные моменты жизненного цикла компонентов. Сам по себе подход не новый, но в такой реализации не встречал его на просторах интернета. Для тех, кто ценит код больше слов, вот ссылка на репозиторий с простеньким приложением, на примере которого можно увидеть применения подхода на практике.Гугл, несколько лет назад представив Lifecycle, сразу же начал пропагандировать построение компонентов, работа которых опирается на жизненный цикл (lifecycle-aware components). Нужно сразу отметить, что в android существует несколько компонентов, имеющих жизненный цикл, которых называют владельцами (LifecycleOwner): Acitvity, Fragment, Fragment view и другие. Диаграмма жизненного цикла представлена на следующей схеме.
И фреймворк позволяет нам подписываться на жизненный цикл компонентов для получения событий о переходе из одного состояния в другое. Для этого реализуется интерфейс LifecycleObserver и передается в метод Lifecycle#addObserver.Давайте посмотрим, как можем использовать данный механизм для выполнения типичных задач и без необходимости генерировать кучу наблюдателей.
Создадим класс Configurator, со следующим API:
class Configurator {
fun addOperation(triggerOnEvent: Lifecycle.Event, operation: (LifecycleOwner) -> Unit)
fun manageBy(lifecycleOwner: LifecycleOwner)
}
Реализация такого класса представляет собой набор массивов, соответствующий количеству событий жизненного цикла, в которые будем добавлять нужные нам действия. Метод addOperation() будем использовать для привязки какого-либо действия к событию жизненного цикла, а manageBy() поможет нам активировать подписку на жизненный цикл android компонента. Такой API удобен для настройки выполнения разовых действий, но часто приходится после выполнения действия отменять его потом. Например, если мы подписываемся на реактивный источник (Observable) в onStart(), то потом должны отменить подписку в onStop(). Давайте для подобного случая создадим функцию-расширение:
fun <T, R: Any?> Configurator.bind(
target: T,
bindAction: (LifecycleOwner, T) -> R,
bindOnEvent: Lifecycle.Event,
unbindAction: (LifecycleOwner, T, R) -> Unit,
unbindOnEvent: Lifecycle.Event = bindOnEvent.oppositeEvent()
) {
var result: R? = null
addOperation(bindOnEvent) { result = bindAction(it, target) }
addOperation(unbindOnEvent) { unbindAction(it, target, result!!) }
}
Первым параметром передается целевой элемент, над которым требуется выполнить действие. Событие жизненного цикла, тригерящее отмену ранее выполненного действия, по умолчанию берется противоположным, но может быть и передано явно. Важным моментом является возможность обратиться к результату выполненного начального действия при выполнении отмены (третий аргумент лямбды unbindAction). Для удобства использования с Observable создадим отдельную функцию:
fun <T> Configurator.Builder.bindObservable(
observable: Observable<T>,
action: (Observable<T>) -> Disposable,
bindOnEvent: Lifecycle.Event = Lifecycle.Event.ON_CREATE,
unbindOnEvent: Lifecycle.Event = bindOnEvent.oppositeEvent()
) {
bind(
target = observable to action,
bindAction = { _, (stream, act) ->
act(stream)
},
bindOnEvent = bindOnEvent,
unbindAction = { _, _, subscription ->
subscription.dispose()
},
unbindOnEvent
)
}
В данном примере видно, как можно в качестве целевого элемента передавать больше одного объекта.На текущий момент получившийся API класса Configurator позволяет добавлять действия в любой момент времени, но этого хотелось избежать, чтобы сосредоточить (искуственно навязать) конфигурирование действий в одном месте, а не разбрасывать их по коду. Для решения подобной задачи введем промежуточное звено - Builder, который будет возможность сконфигурировать, но после создания объект Configurator будет неизменяемым. В итоге получим:
class Configurator {
fun manageBy(lifecycleOwner: LifecycleOwner)
class Builder {
fun addOperation(triggerOnEvent: Lifecycle.Event, operation: Operation)
fun build(): Configurator
}
}
Соответственно наши функции-расширения будут определены для Configurator.Builder. Добавим еще синтаксического сахара для удобства работы с LifecycleOwner:
fun LifecycleOwner.configure(block: Configurator.Builder.() -> Unit) {
Configurator.Builder()
.apply(block)
.build()
.manageBy(this)
}
После того как мы проделали подготовительную работу, давайте рассмотрим применение на примере фрагмента и для демонстрации всех возможностей подхода задействуем все переходы между состояниями. Итак, код в студию:
class TimeWastingFragment : Fragment() {
private val timeSpent = MutableLiveData<Long>()
private val color = MutableLiveData<Int>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentTimeWastingBinding.inflate(inflater, container, false)
viewLifecycleOwner.configure {
bindState(timeSpent, binding.txtTime) { txtView, time ->
txtView.text = "$time"
}
bindState(color, binding.root) { view, color ->
view.setBackgroundColor(color)
}
bindObservable(
observable = Observable.interval(1, TimeUnit.SECONDS),
action = { source ->
source
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
timeSpent.value = timeSpent.value!! + 1
},
Throwable::printStackTrace
)
},
bindOnEvent = Lifecycle.Event.ON_START
)
val colorChangeStream = PublishSubject.create<Unit>()
bindClicks(
view = binding.root,
clickListener = {
color.value = generateRandomColor()
colorChangeStream.onNext(Unit)
}
)
bindObservable(
observable = colorChangeStream
.startWith(Unit)
.switchMap { Observable.interval(5,5, TimeUnit.SECONDS) },
action = { source ->
source
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
color.value = generateRandomColor()
},
Throwable::printStackTrace
)
},
bindOnEvent = Lifecycle.Event.ON_RESUME
)
}
return binding.root
}
}
В данном случае при конфигурировании мы cлушаем один реактивный поток только в состоянии Started, другой - только в Resumed. Функция-расширение bindState() добавлена для консистентности подхода, bindClicks() - в качестве примера установки/сброса слушателя.Отмечу, что подход можно также применять и для других LifecycleOwner'ов с учетом особенностей их жизненного цикла. Важным моментом является когда нужно выполнять конфигурирование компонента (непосредственно вызов configure()). Так для элементов отображения я придерживаюсь следующего правила:
- Для Activity в методе onCreate(), используя активити как LifecycleOwner
- Для Fragment в методе onCreate(), используя фрагмент как LifecycleOwner
- Для Fragment View в методе onCreateView(), используя viewLifecycleOwner
На мой взгляд, подход позволяет добиться следующих преимуществ:
- в связке с использованием LivedData / Flow позволяет сосредоточиться на хранении в Activity/Fragment объектов состояния и избавиться от хранения ссылок на View-элементы и других промежутоных переменных (например, подписки на Rx-потоки);
- код лучше сгруппирован, большая часть действий по конфигурированию сосредоточена в одном месте (останется еще обработка onActivityResult/onPermissionResult/onSaveInstanceState, но для этого уже есть соответствующие инструменты).
Из недостатков стоит отметить:
- Отсутствие возможности дополнить список выполняемых действий после формирования объекта Configurator. (Но это сделано умышленно при формировании API. На мой взгяд, это может создать больше проблем, чем принесет пользы).
Надеюсь статья оказалась полезной. Все исходники лежат в репозитории.
Спасибо, что дочитали до конца. Всем добра!
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка мобильных приложений, Машинное обучение, Искусственный интеллект, Natural Language Processing] OpenAI: более 300 сторонних приложений работают на GPT-3
- [Разработка под Android, Гаджеты, Смартфоны, Старое железо] Fairphone обновила операционку на 5-летнем смартфоне на Snapdragon 801 до Android 9
- [Разработка мобильных приложений, Git, Big Data, Машинное обучение] DVC — Git для данных на примере ML-проекта
- [Разработка мобильных приложений, Разработка под Android, Kotlin] Android + Redux = <3
- [Open source, Виртуализация, Разработка под Linux, Разработка на Raspberry Pi] Шпаргалка по Linux grep, домашний термостат на Raspberry Pi, эл. книга «Ansible for DevOps» и PostgreSQL на Linux
- [Разработка мобильных приложений, Разработка под Android, DevOps, Gradle] Советы по работе с Gradle для Android-разработчиков
- [Java, Разработка под Android, Kotlin] Уже сегодня Android MeetUp: VK, Leroy Merlin, FindMyKids, Кухня на районе
- [IT-инфраструктура, IT-эмиграция, Гаджеты, Софт] Как закон о предустановке российского софта изменит отечественный IT-рынок
- [Разработка под Android, Kotlin] Работа с библиотеками KTX (перевод)
- [Программирование, Разработка мобильных приложений, Dart, Flutter] Dart 2.12: Sound null safety и Dart FFI отправлены на стабильный канал (перевод)
Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_kotlin, #_android, #_android_developement, #_lifecycleaware_components, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_razrabotka_pod_android (
Разработка под Android
), #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:26
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В данной статье хочу поделиться подходом, который использую для выполнения действий в определенные моменты жизненного цикла компонентов. Сам по себе подход не новый, но в такой реализации не встречал его на просторах интернета. Для тех, кто ценит код больше слов, вот ссылка на репозиторий с простеньким приложением, на примере которого можно увидеть применения подхода на практике.Гугл, несколько лет назад представив Lifecycle, сразу же начал пропагандировать построение компонентов, работа которых опирается на жизненный цикл (lifecycle-aware components). Нужно сразу отметить, что в android существует несколько компонентов, имеющих жизненный цикл, которых называют владельцами (LifecycleOwner): Acitvity, Fragment, Fragment view и другие. Диаграмма жизненного цикла представлена на следующей схеме. И фреймворк позволяет нам подписываться на жизненный цикл компонентов для получения событий о переходе из одного состояния в другое. Для этого реализуется интерфейс LifecycleObserver и передается в метод Lifecycle#addObserver.Давайте посмотрим, как можем использовать данный механизм для выполнения типичных задач и без необходимости генерировать кучу наблюдателей. Создадим класс Configurator, со следующим API: class Configurator {
fun addOperation(triggerOnEvent: Lifecycle.Event, operation: (LifecycleOwner) -> Unit) fun manageBy(lifecycleOwner: LifecycleOwner) } fun <T, R: Any?> Configurator.bind(
target: T, bindAction: (LifecycleOwner, T) -> R, bindOnEvent: Lifecycle.Event, unbindAction: (LifecycleOwner, T, R) -> Unit, unbindOnEvent: Lifecycle.Event = bindOnEvent.oppositeEvent() ) { var result: R? = null addOperation(bindOnEvent) { result = bindAction(it, target) } addOperation(unbindOnEvent) { unbindAction(it, target, result!!) } } fun <T> Configurator.Builder.bindObservable(
observable: Observable<T>, action: (Observable<T>) -> Disposable, bindOnEvent: Lifecycle.Event = Lifecycle.Event.ON_CREATE, unbindOnEvent: Lifecycle.Event = bindOnEvent.oppositeEvent() ) { bind( target = observable to action, bindAction = { _, (stream, act) -> act(stream) }, bindOnEvent = bindOnEvent, unbindAction = { _, _, subscription -> subscription.dispose() }, unbindOnEvent ) } class Configurator {
fun manageBy(lifecycleOwner: LifecycleOwner) class Builder { fun addOperation(triggerOnEvent: Lifecycle.Event, operation: Operation) fun build(): Configurator } } fun LifecycleOwner.configure(block: Configurator.Builder.() -> Unit) {
Configurator.Builder() .apply(block) .build() .manageBy(this) } class TimeWastingFragment : Fragment() {
private val timeSpent = MutableLiveData<Long>() private val color = MutableLiveData<Int>() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val binding = FragmentTimeWastingBinding.inflate(inflater, container, false) viewLifecycleOwner.configure { bindState(timeSpent, binding.txtTime) { txtView, time -> txtView.text = "$time" } bindState(color, binding.root) { view, color -> view.setBackgroundColor(color) } bindObservable( observable = Observable.interval(1, TimeUnit.SECONDS), action = { source -> source .observeOn(AndroidSchedulers.mainThread()) .subscribe( { timeSpent.value = timeSpent.value!! + 1 }, Throwable::printStackTrace ) }, bindOnEvent = Lifecycle.Event.ON_START ) val colorChangeStream = PublishSubject.create<Unit>() bindClicks( view = binding.root, clickListener = { color.value = generateRandomColor() colorChangeStream.onNext(Unit) } ) bindObservable( observable = colorChangeStream .startWith(Unit) .switchMap { Observable.interval(5,5, TimeUnit.SECONDS) }, action = { source -> source .observeOn(AndroidSchedulers.mainThread()) .subscribe( { color.value = generateRandomColor() }, Throwable::printStackTrace ) }, bindOnEvent = Lifecycle.Event.ON_RESUME ) } return binding.root } }
Спасибо, что дочитали до конца. Всем добра! =========== Источник: habr.com =========== Похожие новости:
Разработка мобильных приложений ), #_razrabotka_pod_android ( Разработка под Android ), #_kotlin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:26
Часовой пояс: UTC + 5