Я описал пример в утрированной форме. В последующих сообщениях уже вспомнил более правильную аналогию. Мне не важно откуда пришел приказ включить светодиод, с кнопки или по сети. Вот для этого и нужны абстракции, так появляется событие "Нужно зажечь светодиод", которое отлавливается сущностью, которая отвечает за светодиод. Разве не правильно, что класс, включающий светодиод абстрагирован от кнопок и сети?
Ну я не только про абстракции над железом. Я про абстракции на разных уровнях. Мне не важно откуда пришел сигнал, с кнопки или по сети. Код отвечающий за логику, занимаетсят только логикой.
Мне кажется, что код вида "Если пользователь нажал лампочку — зажечь светодиод", куда более понятен, чем, "Если на ноге 2 сигнал изменился с 1 на 0, то изменить уровень сигнала на ноге 8". К тому же, в моем коде, я могу нажатие кнопки заменить на получение запроса по сети, а логика работы не изменится, изменится только железная часть. Но это, наверное, субъективно, кому-то хочется быть ближе к реальности и железу, а кому-то хочется видеть бизнес задачу.
По поводу программирования железок. Решил я тут по стопам Алекса Гайвера сделать лампу на адресных светодиодах. Железо там банальное, а программная часть интереснее. Глянул в код у Гайвера, а там как раз близко к тому, как у копипасте сказано "всё это сводится к одной команде ассемблера: поднять один бит по одному адресу". Мне показалось это неправильным, потому как хочется писать переиспользуемый код, такой, что бы в случае изменения конфигурации железа, мне не пришлось бы все переписывать. К тому же накладывает отпечаток и то, что я Android разработчик, а у нас тут всякие SOLID, Clean Architecture. То есть абстракция на абстракции, и все для переиспользуемочти и универсальности кода. Мне не нравится, когда в файле, отвечающем за обработку входщих сетевых запросов, зажигаются светодиоды. В результате прошивка заняла несколько тысяч строк, и не столь оптимальна по памяти и производительности, как исходная. Но связность кода значительно меньше, а читаемость выше. Вот я и не знаю как быть, с одной стороны это здорово, а с другой, на си писать такой код не очень приятно.
Вот вопрос и есть про то, как аккуратно это делать. А не копировать логику по созданию и завершению скоупов в каждую кастомную вьюху. Кстати, а что за экстеншен? По какой логике он будет работать?
В статье про котлин нельзя обсуждаь андройд?
А по поводу lifecycle, я как раз и хочу получить lifecycle отдельной View, что бы сделать маленькую переиспользуемую часть UI. Что бы вокруг этой View не нужно было городить кучу дополнительного кода, который бы следил за жизненным циклом
View в любой момент может быть удалена с экрана тем, кто ей управляет. Может пользователь у ViewPager пролистнул страницу и открепленная View могла бы быть собрана сборщиком мусора. То есть мне нужно иметь самостоятельно внутри View понимать ее состояние.
Надо ещё обработать ситуацию, что View может быть откреплена от Activity, да хотя бы тот же фрагмент сменился. И нам надо отписаться от Flow, хотя activity.lifecycleScope ещё активен
Вроде да, так обычно и делается. Но мне очень нравится подход, что изображение на экране это функция от состояния. В этом подходе можно делать View без публичных методов, просто передать Flow состояния в конструкторе, а View уже сам его будет актуально отображать
ViewModel имеет метод onClear, то есть у ViewModel жизненный цикл, как и у Activity конечный. И в onClear можно завершить скоуп и память не утечет. А при работе с View видимо придется завершать его каждый раз, когда View скрывается, потому как не знаем, появится ли ещё она на экране или уже можно собирать ее сборщиком.
Не понял в чем проблема с onCompletion. listener же и должн быть подписанным на события в View до тех пор, пока isAttachedToWindowFlow слушается. subscriptionCount Подходит для данной задачи идеально, но проблема в том, что это Flow, а значит нужно в каком то скоупе его слушать, а в каком, непонятно
Да, в текущей реализации так и есть, но хотелось бы некую утилитку использовать, не в каждой же View прописывать эту логику.
Кстати идея с приостановкой корутины на время сокрытия View, наверное, не подойдет, т.к. В отличии от Activity, в View мы не можем знать завершилась ли работа с ней полностью, или может ее ещё раз пользователю покажут, так что мы не можем ни в какой момент времени завершить корутину, а только поставить ее на паузу. А это опасно утечками
val View.isAttachedToWindowFlow: Flow<Boolean>
get() = MutableStateFlow(
value = isAttachedToWindow
).let { stateFlow ->
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
stateFlow.value = true
}
override fun onViewDetachedFromWindow(v: View?) {
stateFlow.value = false
}
}
stateFlow
.onStart { addOnAttachStateChangeListener(listener) }
.onCompletion { removeOnAttachStateChangeListener(listener) }
}
Обидно только, что в результате получется Flow<Boolean>, а не StateFlow<Boolean>. Не получится текущее значение вытащить
Увидел в этом решении проблему в том, что на каждого подписчика listener будет добавляться в View. Работать вроде даже и должно, но не оптимально. Видимо придется ещё какую то проверку делать, что бы listener добавлялся только один раз. Как то это все некрасиво получается.
Да, спасибо, для данной задачи то что нужно. Не учел, что корутины можно не только отменять, но ещё и ставить на паузу. Правда постановка на паузу в данном решении прибита к Lifecycle, для более абстрактного решения придется либой свой велосипед пилить с оглядкой на lifecycleScope.launchWhenStarted, либо искать готовое.
Вообще изначально вопрос был задан для того, что бы понять как подобное сделать для обработки Flow внутри Custom View. Хотелось бы получать новые значения из Flow, только когда View видна пользователю. Думал решение для Activity подойдет и для View. Но View Lifecycle не соответствует жизненному циклу Activity
Я описал пример в утрированной форме. В последующих сообщениях уже вспомнил более правильную аналогию. Мне не важно откуда пришел приказ включить светодиод, с кнопки или по сети. Вот для этого и нужны абстракции, так появляется событие "Нужно зажечь светодиод", которое отлавливается сущностью, которая отвечает за светодиод. Разве не правильно, что класс, включающий светодиод абстрагирован от кнопок и сети?
Это пет-проект. Так что вряд ли стоит ориентироваться на других разработчиков
Ну я не только про абстракции над железом. Я про абстракции на разных уровнях. Мне не важно откуда пришел сигнал, с кнопки или по сети. Код отвечающий за логику, занимаетсят только логикой.
Мне кажется, что код вида "Если пользователь нажал лампочку — зажечь светодиод", куда более понятен, чем, "Если на ноге 2 сигнал изменился с 1 на 0, то изменить уровень сигнала на ноге 8". К тому же, в моем коде, я могу нажатие кнопки заменить на получение запроса по сети, а логика работы не изменится, изменится только железная часть. Но это, наверное, субъективно, кому-то хочется быть ближе к реальности и железу, а кому-то хочется видеть бизнес задачу.
По поводу программирования железок. Решил я тут по стопам Алекса Гайвера сделать лампу на адресных светодиодах. Железо там банальное, а программная часть интереснее. Глянул в код у Гайвера, а там как раз близко к тому, как у копипасте сказано "всё это сводится к одной команде ассемблера: поднять один бит по одному адресу". Мне показалось это неправильным, потому как хочется писать переиспользуемый код, такой, что бы в случае изменения конфигурации железа, мне не пришлось бы все переписывать. К тому же накладывает отпечаток и то, что я Android разработчик, а у нас тут всякие SOLID, Clean Architecture. То есть абстракция на абстракции, и все для переиспользуемочти и универсальности кода. Мне не нравится, когда в файле, отвечающем за обработку входщих сетевых запросов, зажигаются светодиоды. В результате прошивка заняла несколько тысяч строк, и не столь оптимальна по памяти и производительности, как исходная. Но связность кода значительно меньше, а читаемость выше. Вот я и не знаю как быть, с одной стороны это здорово, а с другой, на си писать такой код не очень приятно.
А разве есть возраст, в котором кто нибудь променяет часть зарплаты на кофемашину?
Ага, точно, гладко ложится на задачу. Спасибо
Вот вопрос и есть про то, как аккуратно это делать. А не копировать логику по созданию и завершению скоупов в каждую кастомную вьюху. Кстати, а что за экстеншен? По какой логике он будет работать?
В статье про котлин нельзя обсуждаь андройд?
А по поводу lifecycle, я как раз и хочу получить lifecycle отдельной View, что бы сделать маленькую переиспользуемую часть UI. Что бы вокруг этой View не нужно было городить кучу дополнительного кода, который бы следил за жизненным циклом
View в любой момент может быть удалена с экрана тем, кто ей управляет. Может пользователь у ViewPager пролистнул страницу и открепленная View могла бы быть собрана сборщиком мусора. То есть мне нужно иметь самостоятельно внутри View понимать ее состояние.
Надо ещё обработать ситуацию, что View может быть откреплена от Activity, да хотя бы тот же фрагмент сменился. И нам надо отписаться от
Flow, хотяactivity.lifecycleScopeещё активенА внутри View как ими пользоваться?
Да, но в каком CoroutineScope подписываться не понятно. В LiveData я просто обрабатываю события появления и исчезновения подписчиков
Вроде да, так обычно и делается. Но мне очень нравится подход, что изображение на экране это функция от состояния. В этом подходе можно делать View без публичных методов, просто передать Flow состояния в конструкторе, а View уже сам его будет актуально отображать
Пора мержить ветки :D
ViewModelимеет методonClear, то есть уViewModelжизненный цикл, как и уActivityконечный. И вonClearможно завершить скоуп и память не утечет. А при работе сViewвидимо придется завершать его каждый раз, когдаViewскрывается, потому как не знаем, появится ли ещё она на экране или уже можно собирать ее сборщиком.Не понял в чем проблема с
onCompletion.listenerже и должн быть подписанным на события в View до тех пор, покаisAttachedToWindowFlowслушается.subscriptionCountПодходит для данной задачи идеально, но проблема в том, что это Flow, а значит нужно в каком то скоупе его слушать, а в каком, непонятноДа, в текущей реализации так и есть, но хотелось бы некую утилитку использовать, не в каждой же View прописывать эту логику.
Кстати идея с приостановкой корутины на время сокрытия View, наверное, не подойдет, т.к. В отличии от Activity, в View мы не можем знать завершилась ли работа с ней полностью, или может ее ещё раз пользователю покажут, так что мы не можем ни в какой момент времени завершить корутину, а только поставить ее на паузу. А это опасно утечками
Вроде получилось. Думаете корректно?
Обидно только, что в результате получется
Flow<Boolean>, а неStateFlow<Boolean>. Не получится текущее значение вытащитьУвидел в этом решении проблему в том, что на каждого подписчика
listenerбудет добавляться вView. Работать вроде даже и должно, но не оптимально. Видимо придется ещё какую то проверку делать, что быlistenerдобавлялся только один раз. Как то это все некрасиво получается.Да, спасибо, для данной задачи то что нужно. Не учел, что корутины можно не только отменять, но ещё и ставить на паузу. Правда постановка на паузу в данном решении прибита к Lifecycle, для более абстрактного решения придется либой свой велосипед пилить с оглядкой на
lifecycleScope.launchWhenStarted, либо искать готовое.Вообще изначально вопрос был задан для того, что бы понять как подобное сделать для обработки Flow внутри Custom View. Хотелось бы получать новые значения из Flow, только когда View видна пользователю. Думал решение для Activity подойдет и для View. Но View Lifecycle не соответствует жизненному циклу Activity