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
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
f

Florian

08/05/2021, 6:05 PM
Let's say you have some logic in your ViewModel that should trigger a snackbar in your UI/Composable. How do you send that event? In XML times I used an event channel but this doesn't seem to work well with Compose since 2 times the same snackbar text in a row gets ignored by the
mutableStateOf
holder.
c

Colton Idle

08/05/2021, 6:22 PM
I've had this question and conversation way more times on this channel than I care to admit. And probably like 2-4 times in the past ~3 week. If you want to look through adam powells history on slack you can probably find some good conversations that way, but I also asked for more documentation and guidance surrounding this here if you want to star. https://issuetracker.google.com/issues/194911952
f

Florian

08/05/2021, 6:25 PM
Thanks. Yea I read one of those threads but it was quite messy to read through
☝️ 1
c

Colton Idle

08/05/2021, 6:28 PM
Yeah. Messy and informative, but to the pragmatic person of "Hi. I'm a long time developer and now I am using compose. How do I do this?" it's not really reasonable. After all of those conversations I still don't really have a good answer. I suppose I have three approaches: 1. I try to make things state when I can, and following off of that, I just have been acknowleding that a snackbar was shown for example (just by resetting the state) 2. I use state to launch a LaunchedEffect or DisposableEffect (@zhuinden showed me some stuff around that here https://github.com/ColtonIdle/ComposeSignInSample/pull/1) 3. For navigation events, I basically call a viewmodel.doNetworkCall(success = mySuccessEvent, failure = myFailureEvent)
a

Alexey Glushkov

08/05/2021, 6:34 PM
I’ve come across this article: https://codingtroops.com/android/compose-architecture-part-1-mvvm-or-mvi-architecture-with-flow/?s=09 haven’t tried but definitely going to :)
f

Florian

08/05/2021, 6:41 PM
thanks, I'll look through that stuff in a bit!
nevermind that was for something else
@Colton Idle what problems did you run into using a normal Channel or SharedFlow for events?
c

Colton Idle

08/05/2021, 7:23 PM
Nothing. Except for the fact that it seemed like I needed a phd to understand anything related to flows and channels and such. I just need something that works. Emitting state, and then acking that a snackbar was shown was much easier, less error prone, and made sense to my teammates too. /shrug
f

Florian

08/05/2021, 7:32 PM
and how do you reset that state so the snackbar is not shown again on screen rotation?
c

Colton Idle

08/05/2021, 7:37 PM
I just have something like If (state.showSnackBar){ showIt() State.showSnackBar = false } It's... Primitive. Maybe not the best. But it works.
f

Florian

08/05/2021, 7:43 PM
yea it's an option
but resetting it manually sucks
m

muthuraj

08/05/2021, 8:07 PM
I'm using Channel to show snackbar, refresh indicators, and such just like from the article Alexey shared (I'm actually following the exact pattern from the article) and I face no issue in that. What problem do you face by using Channel?
f

Florian

08/05/2021, 8:08 PM
I also used to use channels in XML times
the problem in Compose is that the same value sent multiple times in a row gets swallowed by mutableStateOf
for example 2 times the same snackbar error message
m

muthuraj

08/05/2021, 8:12 PM
ok, for this specific case, I'm not transforming the
Channel
into
mutableStateOf
. Like from the article, I expose the Channel as
Flow
to the composable. And in the composable, I use
LaunchedEffect(Unit)
to collect the flow and respond to that. This way I can consume all events emitted to the channel since the flow actually suspends until my task from
onEach/collect
is completed before emitting next item. Roughly like this,
fun MyComposable(effectFlow: Flow<Effects>){
  LaunchedEffect(Unit){
    effectFlow.onEach{
        //do thing with it
    }.launchIn(this)
  }
}
c

Colton Idle

08/05/2021, 8:19 PM
At that point, couldn't you just use state and based on the state you just have a LaunchedEffect?
m

muthuraj

08/05/2021, 8:21 PM
In this way, I can queue the event handling. If a snackbar message is emitted while another one is showing, the new one will be handled only after the previous task is finished since the flow operators are suspend functions.
f

Florian

08/05/2021, 8:51 PM
@muthuraj but what do you do with the snackbar text you receive through the channel? You have to get it into the snackbar somehow.
But doing that with a State variable doesn't work
var snackbarMessage by remember { mutableStateOf<String?>(null) } 

    LaunchedEffect(true) {
        viewModel.events.collect { event ->
            when (event) {
                is TodoListViewModel.Event.ShowTaskSavedConfirmationMessage ->
                    snackbarMessage = event.msg
            }
        }
this doesn't work properly but I don't see how else you can recompose to show the snackbar
m

muthuraj

08/05/2021, 8:59 PM
In the collect block, I show the snackbar directly using
scaffoldState.snackbarHostState.showSnackbar
.
This way, if the next snackbar message comes in, it will wait until current snackbar is dismissed since the
showSnackbar
is a suspend function.
f

Florian

08/05/2021, 9:03 PM
yea I guess that makes more sense
but for this the event collection and the snackbar code has to be in the same function right?
m

muthuraj

08/05/2021, 9:03 PM
yeah
f

Florian

08/05/2021, 9:04 PM
now that I look at it again, the snackbarMessage MutableState doesn't make sense anyway
because it remembers the value
exactly what we don't want
so I guess you declare the
scaffoldState
in the outer function and pass it down to the Composable that actually has the
Scaffold
? Is that correct?
m

muthuraj

08/05/2021, 9:08 PM
My case is actually simpler. I use the
Scafflold
in the same function that I handle these events. But passing
scaffoldState
or even just
snackbarHostState
should work I think.
f

Florian

08/05/2021, 9:10 PM
thank you
ok cool, then it's pretty similar to XML times
@Colton Idle I think you should give this approach a try
a Kotlin channel (not a SharedFlow) is imo the easiest way to get a single event to a single observer
it was also recommended in a blog post by the Kotlin developers
@muthuraj I actually don't like that the snackbars queue up. Do you know if there is a way to cancel the currently visible snackbar?
I can't find any method for that
i think clicking the same wrong button 5 times in a row should send 5 full-duration snackbars one after another
instead, the previous one should be canceled
m

muthuraj

08/05/2021, 9:30 PM
You can use
collectLatest
instead of
collect.
*Latest
flow operators would cancel previous task when new event is received.
f

Florian

08/05/2021, 9:31 PM
@muthuraj you're a genius
thank you
works perfectly
👍 2
c

Colton Idle

08/06/2021, 12:02 AM
@Florian looking forward to the video/blog post. Make this easy for me please. 🙏
😃 1
1
s

sindrenm

08/06/2021, 8:02 AM
So the TL;DR is something like this?
sealed class Effect {
    // different effects ...
}

class ViewModel {
    private val _effect = Channel<Effect>()
    val effect: Flow<Effect> = _effect.receiveAsFlow()
}

@Composable
fun Screen(viewModel: ViewModel) {
    LaunchedEffect(Unit) {
        viewModel.effect.collect { effect ->
            when (effect) {
                // handle cases ...
            }
        }
    }
}
f

Florian

08/06/2021, 9:17 AM
Right. This is also what I did in XML times (minus the LaunchedEffect of course)
👍 1