Pull to refresh

Comments 40

Подскажите пожалуйста, как можно с помощью SharedFlow реализовать подписку на события в STARTED состоянии Activity? То есть обычно во всех примерах описывается ActivityScope, в котором мы подписываемся на Flow, это просто и удобно. Но мы остаемся подписанными на Flow в течении всего времени жизни Activity, даже когда приложение свернуто. А мне бы хотелось обрабатывать события только когда приложение видно пользователю.
Можно конечно подписаться на Flow в ActivityScope и при получении событий проверять находится ли Activity в состоянии STARTED, но это во-первых костыль, а во-вторых неуниверсальное решение.
То есть хотелось бы иметь возможность подписываться на Flow не в CoroutineScope, а в Flow<CoroutineScope?> или в Flow<Boolean>. То есть что бы была функция Flow<T>.launchIn(scopes: Flow<CoroutineScope>)

lifecycleScope.launchWhenStarted может вам помочь

Да, спасибо, для данной задачи то что нужно. Не учел, что корутины можно не только отменять, но ещё и ставить на паузу. Правда постановка на паузу в данном решении прибита к Lifecycle, для более абстрактного решения придется либой свой велосипед пилить с оглядкой на lifecycleScope.launchWhenStarted, либо искать готовое.
Вообще изначально вопрос был задан для того, что бы понять как подобное сделать для обработки Flow внутри Custom View. Хотелось бы получать новые значения из Flow, только когда View видна пользователю. Думал решение для Activity подойдет и для View. Но View Lifecycle не соответствует жизненному циклу Activity

Самое примитивное внутри view — сохранять джобу после collect, и отменять её, когда вью детачится от window

Да, в текущей реализации так и есть, но хотелось бы некую утилитку использовать, не в каждой же View прописывать эту логику.
Кстати идея с приостановкой корутины на время сокрытия View, наверное, не подойдет, т.к. В отличии от Activity, в View мы не можем знать завершилась ли работа с ней полностью, или может ее ещё раз пользователю покажут, так что мы не можем ни в какой момент времени завершить корутину, а только поставить ее на паузу. А это опасно утечками

Посмотрите как работает ViewModel внутри. Там похожая идея внутри. Может вам внутри вью создавать собственный скоуп, который вы будет использовать, и который будете закрываться когда вью детачится.

ViewModel имеет метод onClear, то есть у ViewModel жизненный цикл, как и у Activity конечный. И в onClear можно завершить скоуп и память не утечет. А при работе с View видимо придется завершать его каждый раз, когда View скрывается, потому как не знаем, появится ли ещё она на экране или уже можно собирать ее сборщиком.

Ну либо надо менять подход. Не делать умную View, а передавать данные в неё из вне..

Вроде да, так обычно и делается. Но мне очень нравится подход, что изображение на экране это функция от состояния. В этом подходе можно делать View без публичных методов, просто передать Flow состояния в конструкторе, а View уже сам его будет актуально отображать

launchWhenStarted — не наглядно? :)

А внутри View как ими пользоваться?

activity.lifecycleScope.launchWhenStarted.
У вьюх в следующей версии lifecycle такая фича появится

Надо ещё обработать ситуацию, что View может быть откреплена от Activity, да хотя бы тот же фрагмент сменился. И нам надо отписаться от Flow, хотя activity.lifecycleScope ещё активен

Надо, но это не проблема flow)
Сейчас разрабатывается lifecycle экстеншн для этого. но пока надо делать это вручную. В местах удаления с экрана, убивать и скоуп.

Вот вопрос и есть про то, как аккуратно это делать. А не копировать логику по созданию и завершению скоупов в каждую кастомную вьюху. Кстати, а что за экстеншен? По какой логике он будет работать?

И ещё есть вопрос. Есть ли какой нибудь аналог e Flow методов LiveData.onActive и LiveData.onInactive? Например я хочу сделать экстеншен для View val View.isAttachedToWindowFlow: Flow<Boolean>. И хотелось бы, что бы в момент подписки на этот Flow я мог подписаться на IsAttachedToWindow события View, а при отписке слушателей от Flow, отписаться от IsAttachedToWindow событий View. Или тут надо как то мышление менять?

У Flow есть onStart/OnComplete события. Не то?

Вроде получилось. Думаете корректно?


Заголовок спойлера
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 добавлялся только один раз. Как то это все некрасиво получается.

Только onCompletion вроде будет вызываться только если скоуп закроется…
Также ещё у SharedFlow есть onSubscription + subscriptionCount свойство. Но я их не смотерл сам ещё.

Не понял в чем проблема с onCompletion. listener же и должн быть подписанным на события в View до тех пор, пока isAttachedToWindowFlow слушается.
subscriptionCount Подходит для данной задачи идеально, но проблема в том, что это Flow, а значит нужно в каком то скоупе его слушать, а в каком, непонятно

См.выше мой коммент про скоупы как раз)

У SharedFlow можно подписаться на subscriptionCount, и рулить состоянием через кол-во подписчиков

Да, но в каком CoroutineScope подписываться не понятно. В LiveData я просто обрабатываю события появления и исчезновения подписчиков

в том бизнес юните, в котором определен flow. Если он глобальный — ну, значит глобально

View в любой момент может быть удалена с экрана тем, кто ей управляет. Может пользователь у ViewPager пролистнул страницу и открепленная View могла бы быть собрана сборщиком мусора. То есть мне нужно иметь самостоятельно внутри View понимать ее состояние.

Повторюсь — это задача lifecycle расширений конкретно UI слоя конкретно Android. Статья дает общее решение на уровне языка.

В статье про котлин нельзя обсуждаь андройд?
А по поводу lifecycle, я как раз и хочу получить lifecycle отдельной View, что бы сделать маленькую переиспользуемую часть UI. Что бы вокруг этой View не нужно было городить кучу дополнительного кода, который бы следил за жизненным циклом

Можно конечно, но вопрос адресности. Я думаю всякие Spring разработчики даже не в курсе, чегой-то вам понадобилось (исходя из первого сообщения в этой ветке).

Так чем вас не устраивает текущий ЖЦ у view? Да может он не оптимальный для задачи. Но в среднем по больнице работать всё будет.
Есть вью на экране — подписались, нет — отписались..

А что не так? Паттерн-то, конечно же, был известен давно, а вот конкретная реализация была именно что придумана.

Это перевод, в оригинале
Thus Channel was added as an inter-coroutine communication primitive.

Концептуально получился Rx, с холодными/горячими источниками данных, с PublishSubscriber (и другими видами).

Некорректное сравнение. RX, как и Flow, происходит от Observable шаблона + ФП программирования.
— RX пошел в сторону фп, получив сотни функций преобразования
— Coroutines Flow расширяет контроль состояния, продолжая идею самих корутин

Это два подхода, порожденных observable шаблоном, но ориентированных на разный результат.

П.С. Имхо, в реальных системах подход Flow более жизнеспособен, т.к. в конечном итоге все упирается именно в контроль состояния. Абстрактно чистое фп возможно чисто теоретически — на деле же мы просто отдаем контроль состояния на сторону. Flows дают более прозрачную картину
Все-таки есть какое-то послевкусие, что Channel можно было бы и закопать. Хотели как проще, а сущности плодятся, как всегда.
Sign up to leave a comment.

Articles