https://kotlinlang.org logo
Channels
100daysofcode
100daysofkotlin
100daysofkotlin-2021
advent-of-code
aem
ai
alexa
algeria
algolialibraries
amsterdam
android
android-architecture
android-databinding
android-studio
androidgithubprojects
androidthings
androidx
androidx-xprocessing
anime
anko
announcements
apollo-kotlin
appintro
arabic
argentina
arkenv
arksemdevteam
armenia
arrow
arrow-contributors
arrow-meta
ass
atlanta
atm17
atrium
austin
australia
austria
awesome-kotlin
ballast
bangladesh
barcelona
bayarea
bazel
beepiz-libraries
belgium
benchmarks
berlin
big-data
books
boston
brazil
brikk
budapest
build
build-tools
bulgaria
bydgoszcz
cambodia
canada
carrat
carrat-dev
carrat-feed
chicago
chile
china
chucker
cincinnati-user-group
cli
clikt
cloudfoundry
cn
cobalt
code-coverage
codeforces
codemash-precompiler
codereview
codingame
codingconventions
coimbatore
collaborations
colombia
colorado
communities
competitive-programming
competitivecoding
compiler
compose
compose-android
compose-desktop
compose-hiring
compose-ios
compose-mp
compose-ui-showcase
compose-wear
compose-web
confetti
connect-audit-events
corda
cork
coroutines
couchbase
coursera
croatia
cryptography
cscenter-course-2016
cucumber-bdd
cyprus
czech
dagger
data2viz
databinding
datascience
dckotlin
debugging
decompose
decouple
denmark
deprecated
detekt
detekt-hint
dev-core
dfw
docs-revamped
dokka
domain-driven-design
doodle
dsl
dublin
dutch
eap
eclipse
ecuador
edinburgh
education
effective-kotlin
effectivekotlin
emacs
embedded-kotlin
estatik
event21-community-content
events
exposed
failgood
fb-internal-demo
feed
firebase
flow
fluid-libraries
forkhandles
forum
fosdem
fp-in-kotlin
framework-elide
freenode
french
fritz2
fuchsia
functional
funktionale
gamedev
ge-kotlin
general-advice
georgia
geospatial
german-lang
getting-started
github-workflows-kt
glance
godot-kotlin
google-io
gradle
graphic
graphkool
graphql
graphql-kotlin
graviton-browser
greece
grpc
gsoc
gui
hackathons
hacktoberfest
hamburg
hamkrest
helios
helsinki
hexagon
hibernate
hikari-cp
hire-me
hiring
hiring-french
hongkong
hoplite
http4k
hungary
hyderabad
image-processing
india
indonesia
inkremental
intellij
intellij-plugins
intellij-tricks
internships
introduce-yourself
io
ios
iran
israel
istanbulcoders
italian
jackson-kotlin
jadx
japanese
jasync-sql
java-to-kotlin-refactoring
javadevelopers
javafx
javalin
javascript
jdbi
jhipster-kotlin
jobsworldwide
jpa
jshdq
juul-libraries
jvm-ir-backend-feedback
jxadapter
k2-early-adopters
kaal
kafka
kakao
kalasim
kapt
karachi
karg
karlsruhe
kash_shell
kaskade
kbuild
kdbc
kgen-doc-tools
kgraphql
kinta
klaxon
klock
kloudformation
kmdc
kmm-español
kmongo
knbt
knote
koalaql
koans
kobalt
kobweb
kodein
kodex
kohesive
koin
koin-dev
komapper
kondor-json
kong
kontent
kontributors
korau
korean
korge
korim
korio
korlibs
korte
kotest
kotest-contributors
kotless
kotlick
kotlin-asia
kotlin-beam
kotlin-by-example
kotlin-csv
kotlin-data-storage
kotlin-foundation
kotlin-fuel
kotlin-in-action
kotlin-inject
kotlin-latam
kotlin-logging
kotlin-multiplatform-contest
kotlin-mumbai
kotlin-native
kotlin-pakistan
kotlin-plugin
kotlin-pune
kotlin-roadmap
kotlin-samples
kotlin-sap
kotlin-serbia
kotlin-spark
kotlin-szeged
kotlin-website
kotlinacademy
kotlinbot
kotlinconf
kotlindl
kotlinforbeginners
kotlingforbeginners
kotlinlondon
kotlinmad
kotlinprogrammers
kotlinsu
kotlintest
kotlintest-devs
kotlintlv
kotlinultimatechallenge
kotlinx-datetime
kotlinx-files
kotlinx-html
kotrix
kotson
kovenant
kprompt
kraph
krawler
kroto-plus
ksp
ktcc
ktfmt
ktlint
ktor
ktp
kubed
kug-leads
kug-torino
kvision
kweb
lambdaworld_cadiz
lanark
language-evolution
language-proposals
latvia
leakcanary
leedskotlinusergroup
lets-have-fun
libgdx
libkgd
library-development
lincheck
linkeddata
lithuania
london
losangeles
lottie
love
lychee
macedonia
machinelearningbawas
madrid
malaysia
mathematics
meetkotlin
memes
meta
metro-detroit
mexico
miami
micronaut
minnesota
minutest
mirror
mockk
moko
moldova
monsterpuzzle
montreal
moonbean
morocco
motionlayout
mpapt
mu
multiplatform
mumbai
munich
mvikotlin
mvrx
myndocs-oauth2-server
naming
navigation-architecture-component
nepal
new-mexico
new-zealand
newname
nigeria
nodejs
norway
npm-publish
nyc
oceania
ohio-kotlin-users
oldenburg
oolong
opensource
orbit-mvi
osgi
otpisani
package-search
pakistan
panamá
pattern-matching
pbandk
pdx
peru
philippines
phoenix
pinoy
pocketgitclient
polish
popkorn
portugal
practical-functional-programming
proguard
prozis-android-backup
pyhsikal
python
python-contributors
quasar
random
re
react
reaktive
realm
realworldkotlin
reductor
reduks
redux
redux-kotlin
refactoring-to-kotlin
reflect
refreshversions
reports
result
rethink
revolver
rhein-main
rocksdb
romania
room
rpi-pico
rsocket
russian
russian_feed
russian-kotlinasfirst
rx
rxjava
san-diego
science
scotland
scrcast
scrimage
script
scripting
seattle
serialization
server
sg-user-group
singapore
skia-wasm-interop-temp
skrape-it
slovak
snake
sofl-user-group
southafrica
spacemacs
spain
spanish
speaking
spek
spin
splitties
spotify-mobius
spring
spring-security
squarelibraries
stackoverflow
stacks
stayhungrystayfoolish
stdlib
stlouis
strife-discord-lib
strikt
students
stuttgart
sudan
swagger-gradle-codegen
swarm
sweden
swing
swiss-user-group
switzerland
talking-kotlin
tallinn
tampa
teamcity
tegal
tempe
tensorflow
terminal
test
testing
testtestest
texas
tgbotapi
thailand
tornadofx
touchlab-tools
training
tricity-kotlin-user-group
trójmiasto
truth
tunisia
turkey
turkiye
twitter-feed
uae
udacityindia
uk
ukrainian
uniflow
unkonf
uruguay
utah
uuid
vancouver
vankotlin
vertx
videos
vienna
vietnam
vim
vkug
vuejs
web-mpp
webassembly
webrtc
wimix_sentry
wwdc
zircon
Powered by
Title
l

Lokik Soni

07/26/2022, 6:00 PM
Hi all I am collecting flow in compose using collectAsState() But don't know how to make it lifecycle aware or collectAsState() is already lifecycle aware. In compose screen
val isAlarmOn by viewModel.alarmBtnState.collectAsState()
In ViewModel
val alarmBtnState = isAlarmActivated(Unit).map {
    it.successOr(DEF_VAL_ALARM_ACTIVATED)
}.stateIn(viewModelScope, WhileViewSubscribed, DEF_VAL_ALARM_ACTIVATED)
a

Alex Vanyo

07/26/2022, 6:18 PM
collectAsState
by itself will continue to collect even if the application is in the background. https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda#27a9 describes how to make a flow that is lifecycle aware, and
androidx.lifecycle
2.6.0-alpha01
added a
collectAsStateWithLifecycle
utility to accomplish this as well!
o

Oleksandr Balan

07/27/2022, 3:45 PM
But what if we do use
State
inside VM? How could we then make it lifecycle-aware?
a

Alex Vanyo

07/27/2022, 5:22 PM
If you have Compose
State
inside a
ViewModel
, then I’m assuming you have some other suspending method that is updating the
State
that you want to cancel when the lifecycle changes. The most straightforward way to do that would be to expose the
suspend fun
from the
ViewModel
directly, and use
repeatOnLifecycle
inside the
LaunchedEffect
. Something like:
val lifecycleOwner = LocalLifecycleOwner.current
    LaunchedEffect(viewModel, lifecycleOwner) {
        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.update()
        }
    }
This fundamentally does the same thing as the
StateFlow
approach. The
ViewModel
is exposing 2 things: a state, and a way to initiate and cancel updating that state, based on the
Lifecycle
. The difference, is that for the
StateFlow
, the state and the method to initiate and cancel updating are tied together in the
StateFlow
object. For the Compose
State
, the
State
object exists separately, and there is a different
suspend fun
that will update the
State
.
o

Oleksandr Balan

07/27/2022, 5:26 PM
Got it, thx Actually we do use VM’s scope to launch coroutines and update
State
. So I guess this part should be changed if we want to make
State
aware of different lifecycle 🤔
a

Alex Vanyo

07/27/2022, 6:02 PM
One note:
State
itself is completely unaware of lifecycle. It just exists. You can always read the current value, transform it, or pass it around elsewhere in your state production pipeline. What is modifying the
State
can be lifecycle aware, and when you want it active depends on your use case. You might want to have that be a
suspend fun
launched in the
ViewModel
scope, if you have a request that you want to continue executing while an Activity recreation is happening. Or you might want to cancel long running database connections if the application goes to the background.
l

Lokik Soni

07/27/2022, 8:20 PM
Okay so what is the use of WhileViewSubscribed in stateIn operator?
The above shared link says:
The MutableStateFlow and MutableSharedFlow APIs expose a subscriptionCount field that you can use to stop the underlying producer when subscriptionCount is zero. By default, they will keep the producer active as long as the object that holds the flow instance is in memory. There are some valid use cases for this though, for example, a UiState exposed from the ViewModel to the UI using StateFlow. That's ok! This use case demands the ViewModel to always provide the latest UI state to the View.

Similarly, the Flow.stateIn and Flow.shareIn operators can be configured with the sharing started policy for this. WhileSubscribed() will stop the underlying producer when there are no active observers! On the contrary, Eagerly or Lazily will keep the underlying producer active as long as the CoroutineScope they use is active.
a

Alex Vanyo

07/27/2022, 11:24 PM
The
WhileViewSubscribed
is custom, is it probably something like
WhileSubscribed(5000)
? That will keep collecting alive for an additional 5 seconds after being cancelled. So the upstream flow won’t be cancelled if the UI goes away and comes back quickly (say, during activity recreation), but it will eventually be canceled if the application goes into the background for a while. https://medium.com/androiddevelopers/things-to-know-about-flows-sharein-and-statein-operators-20e6ccb2bc74 to go in more detail about those APIs and explains the
WhileSubscribed(5000)
option.
l

Lokik Soni

07/28/2022, 12:47 PM
Okay so it means we should collect flow as a lifecycle aware whether it is cold or hot flow doesn't matter?
a

Alex Vanyo

08/10/2022, 5:23 PM
Shamelessly plugging Manuel’s new article which goes into more detail about
collectAsStateWithLifecycle
, and why you’d want to use it over `collectAsState`: https://medium.com/androiddevelopers/consuming-flows-safely-in-jetpack-compose-cde014d0d5a3 Hopefully that helps give some more context!
m

Michal

08/19/2022, 8:05 AM
@Alex Vanyo if we have screens that are restricted to login, we use on them something like
...
val state by viewModel.state.collectAsStateWithLifecycle()
LaunchedEffect(key1 = state.isAuthenticated) {
   if (!state.isAuthenticated) {
      showLogin()
   }
}
...
With collectAsState the viewmodel gets updated in the background and when the UI is shown, it will have the correct state. However, when using collectAsStateWithLifecycle, it won't update and if UI is shown, it will still forward with the LaunchedEffect as state.isAuthenticated is false (even though in next ms it would be true, but at that time it is already paused again and we are moved to login screen). How would you prevent this from happening? I suspect this will be pretty common, because this kind of "login" is suggested by google and very common. Thank you.
a

Alex Vanyo

08/19/2022, 4:36 PM
I think you’d have the same problem the very first time you’re loading that screen with
collectAsState
or
collectAsStateWithLifecycle
I’d take the approach of having some sort of intermediate loading state, which you’d probably want anyway if knowing whether you are authenticated or not is asynchronous due to disk I/O (if for example it’s coming from
datastore
) Right now, I’m assuming that the default is
isAuthenticated
is
false
, but you could have something like
loginState
that is
Unknown
,
Authenticated
,
Unauthenticated
, and you’d only
showLogin()
if
Unauthenticated
.
m

Michal

08/19/2022, 6:21 PM
When loading for the first time, it isn't a problem, because we initialize the state to the correct state and it is known synchronously, because it depends on async loading that has its own loading screen in the login screen. So we have that loading state not on every screen, but only on the login screen. A similar problem would happen if we do any LaunchedEffect on a state, which isn't updated in the background, and then when reopening, we get the stale data. If we have loginState, it is Unknown or Unauthenticated and the user is moved to login screen, because of the collectAsStateWithLifecycle it will not get updated on the previous screen in background and when user is moved back there, it will again send Unknown or Unauthenticated which triggers the LaunchedEffect again. The next ms would send state Authenticated, but that is too late, because the LaunchedEffect opens the login again.
a

Alex Vanyo

08/19/2022, 6:35 PM
I was thinking of
Unknown
as a no-op. So you’d only redirect to the login screen if
Unauthenticated
. If
Unknown
, you’d keep waiting (showing a loading state of some sort) and then redirecting if it becomes
Unauthenticated
and showing the screen as normal if
Authenticated
So we have that loading state not on every screen, but only on the login screen.
What happens if you’re further in the app, and then your app is backgrounded and killed due to process death? When the user returns to the app, they’ll still be further in the app, but the current log in state won’t be available synchronously there. At any destination, the app could be recreated with a fresh process, so you can’t assume you’ve been to any other destination to already have handled some form of asynchronous loading.
m

Michal

08/19/2022, 6:47 PM
Even after process death we will have a correct state, because it won't be authenticated, will go to the login screen and there it will first try to refresh everything by showing a loading indicator. If failed, it will show login screen, if successful it will go back to the previous screen. In your example, I see the same problem: Screen A = current screen, B = login screen A - is Unknown, updates to Unauthenticated LaunchedEffect is triggered, B is shown, A stops getting updates user authenticates, B sets something to authenticated and clears the screen A is shown, but gets state Unauthenticated (it couldn't be updated because of lifecycle) - in the case of collectAsState this would already have the correct Authenticated state when the UI is shown. LaunchedEffect is triggered, B is shown, A stops getting updates (repeating)
a

Alex Vanyo

08/19/2022, 9:56 PM
Thanks for the detailed scenario!
A is shown, but gets state Unauthenticated (it couldn’t be updated because of lifecycle)
Ah I think I understand the issue: the state flow for screen A is using a stale value for authentication, which will be reused (and trigger the effect again) before it has a chance to see the updated value. I think this is a use case for
replayExpirationMillis = 0
as part of
SharingStrategy.WhileSubscribed
or
onSubscription
. Essentially, you want to avoid using that stale value in the case of authentication, so that it instead goes back to unknown when not subscribed, until it gets a fresh, updated value.
Alternatively, not use a state flow at all for authentication state. To ensure that you’re always getting the most up to date value (with a potentially initial delay while waiting for it)
m

Michal

08/20/2022, 6:11 AM
Thank you! replayExpirationMillis makes sense, but I would have to split the flow to multiple flows for the UI, because some other values are good to be cached for better UX (not the best to split it). It is a little drawback of collectAsStateWithLifecycle. Normally in fragment/activity onStop and onStart I would update the values before they are visible. I wonder if this won't be possible in the viewmodel - based on compose lifecycle it would updates something with onStart before it is send. That would resolve this and there wouldn't be possibility to run into weird issues with clearing the cache or keeping multiple streams that might have race conditions.