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
y

Yan Pujante

03/20/2021, 5:03 PM
I have the following code:
class Model {
    var value1 by mutableStateOf(0)
        private set

    var value2 = 0
        private set

    fun onClick1() { value1++ }
    fun onClick2() { value2++ }
}

val model = Model()

@Composable
fun TestModel() {
    val m = Modifier.padding(10.dp)
    Column {
        Row { Text(model.value1.toString(), modifier = m); Button(onClick = {model.onClick1()}) { Text("Val1++")} }
        Row { Text(model.value2.toString(), modifier = m); Button(onClick = {model.onClick2()}) { Text("Val2++")} }
    }
}
which produces the result in the attached screenshot. I have a few questions: 1) Is it valid to have the state in an external object (like) Model (as opposed to have it directly inside the composable function)? Are there caveats? 2) I don't understand how Compose recognizes that value1 has changed since its underlying representation is a
State
but it is exposed as a simple value. How does compose know this? How is value2 different from value1 from a user point of view (IntelliJ tells me that value1 is
public final var value1: Int
when using Ctrl-Q)? 3) if I understand correctly (some of) the magic of compose comes from the compiler. Is there a way to "see" this magic in action? Like a flag? (something like compiling
.cpp
to
.s
to "see" the assembly code)
a

Adam Powell

03/20/2021, 5:06 PM
1. Is it valid? Yes, and often encouraged. It's often good practice to "hoist" the remembered state of a composable function into an object that looks a lot like this, and then pass it as a parameter with a default value, so that callers that want to manipulate it can do so, and callers that don't care don't need to see it. The default state parameter style generally looks like this:
@Composable
fun MyComposable(
  state: MyState = remember { MyState() }
2. This is the "snapshots" system that is included with the compose runtime. You can take a snapshot and track reads and writes to any snapshot-backed records. Both compose-runtime and compose-ui do this to track invalidations of composition, layout, and drawing in the same way. See the
Snapshot
API for more info on it.
3. No, snapshots do not involve any compiler magic. It's all just run-time kotlin code.
The
snapshotFlow {}
API is a high-level way to see all of the machinery in action; you can put any code you want into the
snapshotFlow
block, and when you
.collect
the flow, it will emit a new value returned by that block whenever any of the snapshot state that was read inside the block changes.
You can also use
snapshotFlow
to observe changes to your own snapshot-backed objects outside of any sort of compose-related context. If you were to write
snapshotFlow { model.value1 }
  .collect { println("new value: $it" }
you would be able to see the values changing
y

Yan Pujante

03/20/2021, 5:14 PM
I guess I don't understand how snapshotFlow knows that value1 is being accessed. where is the "magic"?
the magic is
Snapshot.takeSnapshot
and then the call to
enter
that runs the expression block
y

Yan Pujante

03/20/2021, 5:18 PM
I guess the
mutableStateOf
call must somehow registers itself somewhere otherwise there is no way that an opaque block of code could be analyzed by
snapshotFlow
to determine what is going on...
a

Adam Powell

03/20/2021, 5:21 PM
more or less.
Snapshot.enter
sets up threadlocals for the current snapshot and that forms the communication path
in effect, the current thread local snapshot is like a process-global redux store of sorts, consisting of all snapshot state objects in the process, keeping them all internally consistent with each other
y

Yan Pujante

03/20/2021, 5:23 PM
ok. I will have to dig deeper if I want to go down this route. But clearly calling
mutableStateOf
or (
flow.collectAsState
) is required for the system to work
a

Adam Powell

03/20/2021, 5:24 PM
snapshot observation is the primary/preferred mechanism of invalidating compose-runtime/compose-ui componentry, yes
(
Flow.collectAsState
just uses a
LaunchedEffect
to collect the flow and write into a
mutableStateOf
object, so it's the same thing)
y

Yan Pujante

03/20/2021, 5:29 PM
thanks for the help
👍 1
it's easy to find tutorial/examples that tells you to do this, but it doesn't explain why...
a

Adam Powell

03/20/2021, 6:21 PM
yeah. What kind of docs on this would you have wanted to find in some official materials? Pointers to the source as I linked above? something else?
we'd like to make sure we don't scare folks off who would be overwhelmed by the underlying mechanics for fear that they have to understand all of it in order to use the system at all
y

Yan Pujante

03/20/2021, 6:34 PM
Even with your explanations, it is still very vague in my mind how the "magic" works. There is clearly an interaction between what
mutableStateOf
does and the compose/snapshot system. The proof being that if you just declare a "regular" variable it doesn't work. I really thought that the compiler was doing some kind of trick (I have seen in some places reference to the compose compiler, so the compose compiler is doing something) but I guess I was wrong. At the end of the day I understand it's not magic 😉 and I would like to see some paper or presentation, describing how when I write
mutableStateOf
somehow it "notifies" (probably not the right word) that whenever a snapshot is taken then this "state" needs to be part of it (ThreadLocal? Global Objects?). There IS a side effect happening when I call
mutableStateOf
that reaches OUTSIDE of my class and that is the magic that scares me (scares is probably too strong of a word 😉) . Yes I can look at the source code but a high level explanation/architecture would be super useful (at least to me).
👍 1
3
a

Adam Powell

03/20/2021, 6:41 PM
the side effect happens when
get
or
set
is called on
SnapshotMutableState.value
c

Chuck Jazdzewski [G]

03/22/2021, 6:40 PM
It is not reaching outside your class so much as it is reaching outside the instance created by
mutableStateOf
. This instance is observable (https://en.wikipedia.org/wiki/Observer_pattern). Whenever the object's value property is written to the snapshot mechanism will inform any listeners that the object was changed (through a global snapshot apply observer). Composition is one such observer (as is layout and draw). During composition, a snapshot is created with a read observer which will be informed whenever a read of a observable object is performed (even transitively). It records an association with the part of the composition that is being either produced or updated (called a recompose scope) and when it is informed later that the object was changed it will schedule recompose scope to be recomposed. In the above example, reading
value1
in
TestModel
will cause
TestModel
to recompose whenever
value1
changes. Incrementing
value1
causes an apply notification to be sent to the composer which will invalidate
TestModel
and schedule a recompose to happen in the next choreographer frame.
TestModel
is re-invoked by the composer and the new value is updated in the
Text
. Changing
value2
updates the value but does not schedule a recompose as nothing tells the composer that it has changed.