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

franztesca

03/27/2023, 2:33 PM
Hello! When working with composable functions, the preferred approach is to use a unidirectional data flow architecture, where an immutable description of the composition goes down in the composable functions parameters, and events come up through lambdas that are passed to the composable... In particular, it is important for composable functions to be idempotent. Therefore, it is an anti-pattern to use mutable state references in the parameters of a composable function because it breaks idempotency, right? There is a twitter detekt rule for it AFAIS. However, looking at the Scaffold composable function, it uses a reference to a mutable
ScaffoldState
instead of using a unidirectional data flow. Is it done for convenience (encapsulating part of state management)? Doesn't this break idempotency of the
Scaffold
function? Is this a general good practice for custom composable functions? :thank-you:
c

CLOVIS

03/27/2023, 2:39 PM
Is
ScaffoldState
really mutable? Looking at its source, I only see https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]arHost.kt%20class:androidx.compose.material.SnackbarHostState which is encapsulated in a MutableState, so tracked by compose
c

Casey Brooks

03/27/2023, 2:49 PM
There are a number of classes needed by built-in widgets that seem “mutable” (rememberScrollState() is another example), but in the end the properties in those classes are backed by a
MutableState
rather than normal properties. This makes it so that those properties can be changed by the UI widgets, and it’s not much different from you defining your own
mutableStateOf()
to make your UI work. In general, no this is not something you should reach for first. It’s a case where the Compose/Material designers made the intentional decision that you, as the consumer of a particular component, don’t need/shouldn’t have precise control over the widget like you might with the
enabled
property of a button. They chose not make the property “internally mutable” so you don’t need to worry about it and mess with all the extra boilerplate. Based on how Compose and States work, it’s not technically breaking the UDF pattern, but it does add some nuance to it for the sake of ease-of-use. For your own code, the official recommendation is to lift state as you would expect, not to hide properties like many of the Material widgets do.
f

franztesca

03/27/2023, 3:19 PM
The
ScaffoldState
is mutable. The fact that it is observable through the Snapshot system and thus it works with compose it's clear. I was asking more of a conceptual question and trying to realize best practices and how much to follow them. In our project we have fairly complex UI and composable functions, so we could write some components that encapsulate their own logic and state, while still interacting with the external world through a mutable (observable) state, just like
ScaffoldState
or
rememberScrollState
. On one side, practically speaking, following the scaffold approach creates simpler implementation and code that can be resused more easily (without the ceremony of lifting the state every time), on the other side it doesn't seem to follow stricly the state lifting, and idempotent composable function approach, which is promoted by the official documentation. So there is this choice to make in our own code. We are trying to figure out the actual best practice
c

Casey Brooks

03/27/2023, 3:34 PM
It’s best to keep things explicit in Compose, and the particular way that many of the Compose UI APIs are set up is not. It’s intentional, to keep you from being overwhelmed by boilerplate that most people don’t need to worry about, but is definitely a case where the Compose library designers are very smart and know what they’re doing, and know when it’s OK to break the rules. But that’s not to say that you can’t create components which maintain some internal state. Just be explicit about it when you do, so it’s easy to tell when a component is managing its own state. A common pattern here is to have 2 overloads for each component, a “stateless” version which exposes only properties and callbacks (the state is fully lifted), and a second which internally sets up the MutableStates to simplify usage. This keep things simple to use in your app, but also explicit about when a given function is managing state, along with an easy way to manage the state from a higher level if needed. For example:
@Composable
fun SomeComplexFunction() {
    var stringValue by remember { mutableStateOf("") }
    var intValue by remember { mutableStateOf(0) }
    var booleanValue by remember { mutableStateOf(false) }

    SomeComplexFunction(
        stringValue = stringValue,
        intValue = intValue,
        booleanValue = booleanValue,

        updateStringValue = { stringValue = it },
        updateIntValue = { intValue = it },
        updateBooleanValue = { booleanValue = it },
    )
}

@Composable
fun SomeComplexFunction(
    stringValue: String,
    intValue: Int,
    booleanValue: Boolean,

    updateStringValue: (String) -> Unit,
    updateIntValue: (Int) -> Unit,
    updateBooleanValue: (Boolean) -> Unit,
) {
    // TODO()
}
c

CLOVIS

03/27/2023, 7:27 PM
Note that sharing "immutable" objects that are Snapshot-aware is recommended in Compose for domain logic, example here: https://developer.android.com/jetpack/compose/architecture#example It's not recommended for regular UI design components, however.
s

Stylianos Gakis

03/27/2023, 8:16 PM
It’s not recommended for regular UI design components, however.
Regarding regular UI components actually, I encountered this case recently, where I opted to use such a
State
object, and I don’t know if I am gonna regret it or not. I had a design system component, which basically let’s say takes in a string, and exposes a lambda to update it, it basically contains a TextField, and some more things. Now, I was hoisting this information all the way up to the ViewModel for a screen, since I had to have access to it there, for some reasons. Then there were some other screens that were using this exact same design component, and again, had the requirement where this state was hoisted to the ViewModel. This wiring started to become quite cumbersome, moving it from the Viewmodel’s UiState, to the navigation-destination level composable, then to the screen-level composable, and then often to child composables too. I was doing this for 4+ screens at this point. I then thought let’s try doing what Scaffold and all these
…State
objects do, and had this design component take this one object in, which follows these guidelines like being @Stable and if it contains any mutability it’s all backed by snapshot state. Then the ViewModel also contains inside its exposed UiState, an object of that exact same type. Then passing it down to the child composable is as simple as passing this 1 object (or inside the screen-level UiState). And on the ViewModel, when I need to access it to do something it’s as simple as getting it out of that containing object. It has been quite a simplification on the code there, but I do wonder if I will go overboard with this approach.
c

Casey Brooks

03/27/2023, 10:03 PM
I maintain the Ballast MVI library, which can be helpful in combining multiple state properties together for a screen, while still making it easy to share across the UI and explicit how the state changes. Might make things a bit cleaner and easier to work with hiding MutableStates behind interfaces, which can obscure how those values change. To the point of “I do wonder if I will go overboard with this approach”, I think in general this can be a great way to wrangle the complexity of screens, but you will want to make sure it’s clear how to update that state, not just how to share it and pass it around easily, so that it’s understandable for new developers who may need to work with the code
s

Stylianos Gakis

03/27/2023, 10:22 PM
These
…State
objects, just like the ScaffoldState or
DatePickerState
from material3, usually contain a way to mutate that state by themselves, shouldn’t be hard to figure that out as I see it. And this was actually one of the reasons I begun to try this out in the first place. I had a DatePickerState which I wanted to hoist all the way up to the ViewModel, and it was the only way to read and write the date picker state using the material3 component. It received a DatePickerState parameter, which it internally mutated as you select a new date and whatnot, doesn’t give back a callback for when the state is updated. So I kinda had to take this approach there. And this worked well so I tried it out with my own components too. But again, not 100% confident in this technique atm, I might have different opinions in the future.
b

Ben Trengrove [G]

03/27/2023, 10:40 PM
We call these state objects "state holders". You can read the full docs here https://developer.android.com/topic/architecture/ui-layer/stateholders But there is also an example in the advanced state codelab. https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects#6
s

Stylianos Gakis

03/27/2023, 10:56 PM
Yes, state holders is the right term for it. And from your perspective Ben, hoisting those all the way up to the ViewModel, exposing them as part of the UiState (The one StateFlow that the VM exposes) and then passing it down to individual composables to both use to display stuff but even to mutate internally? Like you have to do with DatePickerState with the DatePicker component. These docs either don’t mention doing this exactly, or I am missing something
f

franztesca

03/27/2023, 11:06 PM
Ok, that answered my question. i didn't find those examples showing the
...State
parameter pattern. To sum up, it's fine (and desirable) to create this state holder helper classes to simplify our composable functions. Even if this breaks the conceptual idempotency of the composable function, we don't care much because we know that our state holder is using the snapshot system anyway (aka the composable is aware of the state changes) and we help the compiler trust us by using the @Stable annotation?
b

Ben Trengrove [G]

03/27/2023, 11:06 PM
It really depends on the situation (cop out answer I know). Most state holders are for UI logic and so hoisting them into the viewmodel feels wrong to me. In general you only want to hoist as far as the lowest common consumer and normally thats not quite as high as the viewmodel. This is what this section is trying to describe https://developer.android.com/topic/architecture/ui-layer/stateholders#choose-viewmodel
As for annotating with stable, yes state holders are normally stable because they notify the sCompose of mutations via MutableState. The compiler should pick that up itself but you can make sure it happens with the annotation
s

Stylianos Gakis

03/27/2023, 11:12 PM
Then for this specific situation, where you want to have a DatePicker, and you need this state inside the ViewModel so that you can use the existing selected date and so on to derive your UiState from. If you don’t hoist it all the way up there what is your best bet? A LaunchedEffect, with a snapshotFlow in there, reading the internal date picker state and calling a function on the ViewModel which will update some other state representation that you will have in there which will be a mirror of the DatePickerState but you need to make sure to keep it in sync by yourself?
b

Ben Trengrove [G]

03/27/2023, 11:51 PM
It's an interesting question, I haven't actually tried the new date picker yet so first off if the API is hard to use in the real world and you have any feedback do file a bug. Then to answer what I would do, it really depends, but I would think the reason you are needing it in the viewmodel is that you have to react to a date being selected and then kicking off some longer running event? In that case I probably would have done a dialog rather than an inline UI date picker and then fired the event on a confirm button. snapshot flow would also work but would react to every change which you may or may not want. Hoisting it to the viewmodel could work as well, the one thing I wouldn't do would be to expose it as part of some overall UI state stateflow as that to me sounds like stacking mutable layers which always leads to issues in my experience.
s

Stylianos Gakis

03/28/2023, 12:39 AM
Thanks a lot for the response. Let’s discuss some points.
I would think the reason you are needing it in the viewmodel is that you have to react to a date being selected and then kicking off some longer running event
The reason I want it in the ViewModel is that I use the date state to • I take this date selected, and if it is not null is is passed down so that one of the UI elements will show the currently selected date (not the Dialog in this case, but think like a row item which shows what you’ve selected when you used the dialog). • If there is a date selected, I want to show more fields (it’s a screen that works kinda like a form to entry stuff), so this means that the UiState needs to change to reflect that these new items should show on the screen • I also want to use this at the end of entering everything, to perform a network request, and yes this one I understand that I could have the local date picker state in the composable, and on the button click take that current local state and pass it to the lambda which would eventually call the VM. But I am mostly interested in the first two points. So then we go to
Hoisting it to the viewmodel could work as well, the one thing I wouldn’t do would be to expose it as part of some overall UI state
And I wonder, would it then have to be part of another StateFlow that the ViewModel is exposing, so you’d have both of these as parts of the VM’s API? And if yes, what happens if it’s existence relies on the other UiState being in a specific situation? Think like:
sealed interface SomeUiState {
  object Loading: SomeUiState
  data class Content(foo: Foo): SomeUiState
}
And the date picker state only is applicable when the
SomeUiState
is
Content
but not otherwise. And instead of a simple loading/content situation think of a more complex UI where it has multiple distinct “States” that the entire UI is in which would render quite dinstinct UIs depending on it. This would also be super inconvenient as you’re polluting the public VM API with something that is not always applicable. If on the other hand it is part of the Content, like let’s say
sealed interface SomeUiState {
  object Loading: SomeUiState
  data class Content(foo: Foo, datePickerState: DatePickerState): SomeUiState
}
Then you get it only when it’s applicable. With the downside that then you got a
StateFlow<SomeUiState>
which internally holds the
DatePickerState
which is also mutable internally, meaning that the StateFlow internal item may be mutated without the flow re-emitting. Which well, isn’t a problem (I guess?) when consuming this from compose, but only if you are compose-only. Which in my case is the case anyway. Would love to hear more of your thoughts around this!
b

Ben Trengrove [G]

03/28/2023, 1:07 AM
First part, that all sounds like UI logic to me so I would try to have that all in my composables probably hoisted all the way to the top screen level composable, but not to the viewmodel. I would then pass the event to the viewmodel when it was actually confirmed like you said. Why do you need it in the viewmodel for your first two bullets? And for the second part, I don't think there would even be a need for a StateFlow. The compose state objects are your state flow in this case. And for polluting the public API, that's part of the reason why I don't like hoisting that far.