https://kotlinlang.org logo
Join the conversationJoin Slack
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
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
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
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 Linen
compose
  • p

    Philip Dukhov

    12/19/2021, 3:05 AM
    I'm trying to develop an Android Compose project in IntelliJ IDEA. I tried creating a new project in AS and updating the gradle plugin to version 4.2.2 - after sync in IDEA everything works as expected. But in my actual project, the syntax highlighting doesn't work for any Compose code. The other Kotlin code highlighting/suggestions work fine, and the project also builds and runs fine. When I hover over any
    @Composable
    function, it shows the following:
    @[ERROR : Composable]
    public fun Screen(): Unit
    I didn't find any related errors in the gradle sync output, also filtering
    idea.log
    on the word
    "compose"
    didn't show anything interesting. What could be causing this problem? Is there a place where I can look for related logs?
    c
    c
    j
    • 4
    • 12
  • e

    elye

    12/19/2021, 2:31 PM
    Hi… With ComposeState, do we still need LiveData for Jetpack Compose? The ComposeState (e.g. with
    rememberSaveable
    ) seems to cover Both onConfiguration and State Restoration.
    c
    a
    • 3
    • 2
  • r

    Rick Regan

    12/20/2021, 2:23 AM
    Can an app that’s been in the background for a while ultimately be killed off and not restarted even if it has saved instance state?  I recently added saved instance state to my app (
    RememberSaveable
    and explicit writes to the bundle in
    onSaveInstanceState()
    ) and it restores except sometimes after long stretches in the background (8-12 hours) on my real device. (Some other apps on my device appear to behave similarly, and others don’t, which makes me more curious.)
    a
    • 2
    • 10
  • i

    iamthevoid

    12/20/2021, 8:38 AM
    How can i prevent BottomDrawer opening with finger? I want to open (expand) it from closed state by side effect command only.
    l
    • 2
    • 2
  • a

    Ankit Shah

    12/20/2021, 12:21 PM
    I need to prepare a UI with a circular slider where I can pass value or move drag the pointer to slide in the slider similar to the screenshot attached.  How can I achieve it?
    c
    p
    • 3
    • 2
  • m

    Matti MK

    12/20/2021, 12:25 PM
    I want to animate the radius of all corners of a rectangle, independently in different ranges. Moreover, I’d like this to be state-based so I can change over to a different animation at a given time. The use case is to show this on a loading screen the during the loading state, then at some point later transition to a scale animation. My first approach was to test the
    rememberInfiniteTransition()
    as shown below which works nicely, but is not state based. I’m wondering which animation I should go with to have a
    repeatable
    animation with
    RepeatMode.Reverse
    and have it be state-based? I looked into
    repeatable
    (https://developer.android.com/jetpack/compose/animation#repeatable), but I still have to provide an iteration count, which I do not know beforehand. Thanks 👍
    rememberInfiniteTransition().animateValue(
            initialValue = 60,
            targetValue = 80,
            typeConverter = Int.Companion.VectorConverter,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = ANIMATION_TIME, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )
    d
    • 2
    • 1
  • t

    Tim Malseed

    12/20/2021, 11:10 PM
    I’m using Compose + Hilt. I have this ‘onboarding’ set of screens. Screen A makes a backend request, and then passes the data to Screen B. It’s a simple POJO, and I don’t have a persistence layer. I’m trying to decide if I should use one ViewModel for both screens, and just hold this data in the VM. But, if the user navigated from B back to A, I’m going to have to notify the VM and keep it in-sync with navigation state, which just smells a little to me. Or, I can use a separate ViewModel for Screen B, and pass the data (somehow), via navigation arguments.. Thoughts?
    d
    c
    • 3
    • 7
  • m

    Manojna Chintapalli

    12/20/2021, 11:50 PM
    Hello, When using BottomSheetScaffold is there a way to HALF_EXPAND it similar to ModalBottomSheet? I see that it can be done using ModalBottomSheet, but the issue with using ModalBottomSheet is that when it is dismissed it disappears from the screen whereas BottomSheetScaffold can be shown at the bottom partially all the time. Thanks.
    c
    • 2
    • 2
  • p

    pablisco

    12/21/2021, 1:57 AM
    Random thought/question. Has anyone made a Compose runtime to render to html files directly? Without using K/S to manipulate the DOM as a web app.
    j
    • 2
    • 8
  • m

    Matti MK

    12/21/2021, 6:16 AM
    I’ve got a
    Row
    with
    horizontalScroll
    . All the elements of the
    Row
    have a
    Text
    that can be of any width. I’d like to make all the elements of the Row to take up the same width as the largest element. Any tips on how to go about this? In the ConstraintLayout world something like this could be perhaps achieved with
    Barrier
    y
    a
    +2
    • 5
    • 12
  • i

    iamthevoid

    12/21/2021, 7:59 AM
    I want to prevent BottomDrawer opening with finger, and seems like
    gestureEnabled
    is what am i looking for, but looks like
    gestureEnabled
    disable gestures inside BottomDrawer content too. Is there way to disable gestures for bottom drawer, but leave it for its content?
    • 1
    • 1
  • a

    Ankit Shah

    12/21/2021, 11:07 AM
    Hello everyone, I am working on a custom circular slider on jetpack compose, and with help some resources I was able to do it at some extent. But the problem is that it starts from 3'0 Clock. I want it to start from 12'O Clock. Can someone help me with this small tweak? I have replied the code in the thread
    m
    • 2
    • 4
  • f

    Florian Walther (live streaming)

    12/21/2021, 12:11 PM
    Do I need to read this result value in a side-effect or can I put it directly into my Composable? Or does
    observeAsState
    take care of that? https://stackoverflow.com/a/66837741
    • 1
    • 1
  • f

    Florian Walther (live streaming)

    12/21/2021, 1:30 PM
    I want to keep my FloatingActionButton in the same position wether or not the bottom navigation is visible. For that, I need the height of the bottom bar. How can i get that in Compose?
    c
    a
    • 3
    • 2
  • b

    Brian Donovan

    12/21/2021, 3:27 PM
    Hey all, When I run the emulator I am getting a blank screen. Am I missing something here? The url link does work. Thanks
    b
    f
    • 3
    • 5
  • t

    Tim Malseed

    12/21/2021, 10:40 PM
    A follow up from a previous question.. I’m trying to figure out how to share a ViewModel across multiple composable screens, but it doesn’t feel quite right. I’ve written some psuedo code to try to explain the approach..
    a
    • 2
    • 8
  • t

    Tim Malseed

    12/22/2021, 6:48 AM
    I have the following in a UI test, but the test seems to hang..
    @Test
    fun myTest() {
    
        composeTestRule.activity.runOnUiThread {
            val backPressedDispatcher = (composeTestRule.activity as OnBackPressedDispatcherOwner).onBackPressedDispatcher
            backPressedDispatcher.onBackPressed()
        }
    
        assertEquals(composeTestRule.activityRule.scenario.state,  Lifecycle.State.DESTROYED)
    • 1
    • 1
  • s

    Stylianos Gakis

    12/22/2021, 6:54 AM
    I have a code-style related question. How do you prefer to write your layouts A:
    Column {
        Item()
        Spacer(Modifier.height(16.dp))
        OtherItem()
    }
    B:
    Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Item()
        OtherItem()
    }
    I’d love to hear arguments/opinions too if someone feels like they have strong opinions about either way.
    🅰️ 3
    🅱️ 13
    f
    j
    +2
    • 5
    • 21
  • s

    Stylianos Gakis

    12/22/2021, 8:01 AM
    Got a problem using
    MonetaryAmount
    from
    org.javamoney:moneta
    in compose @Preview functions. It seems like the
    JDKCurrencyProvider
    isn’t yet created for the previews, therefore it fails fetching the currency. Anyone had the same issue before that can provide some workaround? It would also be nice to be able to run a debugger on my code for the preview in order to see what fails exactly potentially? This is probably too hard though, considering I do not understand under which special rules the previews run exactly.
    c
    t
    • 3
    • 14
  • v

    Vitaliy Zarubin

    12/22/2021, 8:42 AM
    Hello. Problem opening deep link. Used by ProvideWindowInsets. Tell me pls where to look, what to do, have any ideas?
    j
    c
    c
    • 4
    • 7
  • a

    Anastasia Rozovskaya

    12/22/2021, 11:49 AM
    Hi, all. I’m getting this exception
    java.lang.IllegalStateException: LayoutNode should be attached to an owner at androidx.compose.ui.node.LayoutNodeKt.requireOwner(LayoutNode.kt:1372)
    while clicking on a particular list item (2nd item actually). The set up is as follows: Fragment with recyclerview, where each item is a compose card. Compose version is 1.0.5. Any help would be appreciated.
    a
    a
    • 3
    • 6
  • d

    dimsuz

    12/22/2021, 1:43 PM
    I have
    Box {
      // 500dp intrinsic width image
      Image(
        painter = painterResource(R.drawable_image_500x56), 
        contentScale = FillHeight,
        alignment = TopStart)
      Text("hello")
    }
    Currently this box has size
    500.dp
    due to `Image`'s intrinsic width. But I want this box to have width of the
    Text
    and for
    Image
    to be painted and horizontally clipped on the right. Is this possible with default layouts?
    m
    • 2
    • 2
  • b

    Benjamin Deroche

    12/22/2021, 4:11 PM
    Why does the
    zIndex
    do nothing in a
    LazyVerticalGrid
    ? I've implemented a drag and drop feature that work perfectly well in
    LazyColumn
    and
    LazyRow
    , but in the
    LazyVerticalGrid
    , the
    zIndex
    I apply to the dragged element (to always show it on top) have no effect. The dragged element is always shown behind the next elements in the list and above the previous elements in the list. I also tried another "static" implementation using
    Row
    and
    Column
    but it have the same problem.
    j
    a
    • 3
    • 2
  • y

    Yingding Wang

    12/22/2021, 7:04 PM
    Hallo everyone, I just started my journey with compose and I am intending to use androidx.wear.compose to migrate all my view and java based wear os codes (watchface, complication, wear os app) to kotlin and compose. Nice to meet you all.
    👋 2
    a
    • 2
    • 2
  • m

    mattinger

    12/22/2021, 7:33 PM
    Hey all (i’ve asked this before, but haven’t gotten a response). I have a scrollable column with a text input in it. When i focus on it, the keyboard comes up and obscures the text field. I presume it’s because it’s resizing the content view and doesn’t forcibly bring the focused control into view. I’m struggling with how to get this to happen.
    p
    • 2
    • 6
  • n

    Nat Strangerweather

    12/22/2021, 10:01 PM
    Hi guys, I was trying to use the ScrollToTopButton composable but for some reason it is not showing in my dropdown menu on AS. Any ideas what I might be doing wrong?
    c
    • 2
    • 10
  • s

    Sher Sanginov

    12/23/2021, 1:10 AM
    Hey everyone I am running into this error when setting up
    androidx
    UI testing with compose in my project. Any idea what may be wrong? I tried to play with dependencies but no success.
    java.lang.RuntimeException: Unable to resolve activity for: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.myapp.debug.test/androidx.activity.ComponentActivity }
    	at androidx.test.core.app.InstrumentationActivityInvoker.startActivity(InstrumentationActivityInvoker.java:387)
     ...
    Full stacktrace in thread.
    a
    • 2
    • 4
  • u

    uptoyou

    12/23/2021, 12:09 PM
    what DI framework is more preferable to maximize shared code in KMM project ? I viewed custom implementation from Anna Zharkova (

    https://www.youtube.com/watch?v=Z3KhZzAhCIY▾

    ) but consider it as a fallback choice. Maybe someone has experience with Hilt and some custom extensions ?
    j
    j
    j
    • 4
    • 8
  • l

    Lukasz Kalnik

    12/23/2021, 12:55 PM
    I have a
    Row
    containing a free
    Text
    element and two `TextButton`s. I want the
    Text
    be aligned by baseline with the `TextButton`s:
    Row(modifier = Modifier.alignByBaseline()) {
                Text(
                    text = "Geräte",
                    style = MaterialTheme.typography.h6
                )
                TextButton(
                    onClick = { /*TODO*/ },
                ) {
                    Text(
                        text = "Alle an",
                        style = MaterialTheme.typography.h6,
                    )
                }
                TextButton(onClick = { /*TODO*/ }) {
                    Text(
                        text = "Alle aus",
                        style = MaterialTheme.typography.h6,
                    )
                }
            }
    s
    • 2
    • 5
  • s

    Stylianos Gakis

    12/23/2021, 2:49 PM
    I have a question regarding compose and some differences between different ways to use
    remember
    ,
    mutableStateOf
    ,
    derivedStateOf
    where all of them seem like they’d do the same thing-ish but with slight differences and potential premature-optimizations by me? More on Thread 🧵
    a
    c
    a
    • 4
    • 14
Powered by Linen
Title
s

Stylianos Gakis

12/23/2021, 2:49 PM
I have a question regarding compose and some differences between different ways to use
remember
,
mutableStateOf
,
derivedStateOf
where all of them seem like they’d do the same thing-ish but with slight differences and potential premature-optimizations by me? More on Thread 🧵
My use case is that I have a composable that takes a timeStamp of when something was submitted
submittedAt: Instant
and I want to show on the UI the time passed since then. I can now do this in all these three ways and it all works.
@Composable
fun MyComposable(submittedAt: Instant) {
    val now by currentTimeAsState(updateIntervalInSeconds = 1L)
    val someText1 = remember(submittedAt, now) {
        getRelativeTimeSpanString(submittedAt, now)
    }
    val someText2 by remember(submittedAt, now) {
        mutableStateOf(getRelativeTimeSpanString(submittedAt, now))
    }
    val someText3 = remember(submittedAt, now) {
        mutableStateOf(getRelativeTimeSpanString(submittedAt, now))
    }
    val someText4 by remember {
        derivedStateOf { getRelativeTimeSpanString(submittedAt, now) }
    }
    LogCompositions("RECOMPOSING!")
    Text(text = someTextX) // on someText3 I used `someText3.value` instead
}
As I understand for options #: 1. A simple remember with the keys that
getRelativeTimeSpanString
relies on so that when either of them change the
getRelativeTimeSpanString
function is called again and updates
someText1
. This will be recalculated every time
now
changes which in this case is every second (could be faster too in some other use case) therefore we get a bunch of useless recompositions. The
LogCompositions
functions confirms that. 2. I am not even sure if I was expecting a difference here, but I didn’t get one anyway. I am assuming I would define something like this if it were a
var
that was also mutated inside this composable, not even sure how different this is from #1. 3. Same thing, unsurprisingly 4. This should read
submittedAt
and
now
from the snapshot automatically and only update
someText4
when the result of
getRelativeTimeSpanString
is different from the previous result, therefore acting as some sort of conflate method that flows have, therefore doing recompositions only when it’s needed?
LogCompositions
does confirm that recompositions only happen when the text changes in the end. Bonus points for not even having to be explicit about the keys since
derivedStateOf
has some internal check as I understand. So in conclusion, should we always go for option #4 then? Since
derivedStateOf
isn’t free either, is this internal check that
derivedStateOf
does so expensive that we shouldn’t always do it? How would we make an informed decision on this, I am unsure. Note that even on options #1-#3
MyComposable
did not recompose when the
someText
was read inside another composable down lower that was not inline, therefore it had its own “smaller” recompose scope. But since it only work in that case it feels like a thing that I don’t want to think about in general, and most of the time this isn’t even the case.
Appendix of the functions that you see in the code above ^^
@Composable
fun currentTimeAsState(
    updateIntervalInSeconds: Long = 1L,
    clock: Clock = Clock.systemUTC(),
): State<Instant> {
    return produceState(initialValue = Instant.now(clock)) {
        while (isActive) {
            delay(updateIntervalInSeconds * 1_000)
            value = Instant.now(clock)
        }
    }
}

fun getRelativeTimeSpanString(
    from: Instant,
    to: Instant = Instant.now(),
): String {
    return DateUtils.getRelativeTimeSpanString(
        from.toEpochMilli(),
        to.toEpochMilli(),
        DateUtils.SECOND_IN_MILLIS
    ).toString()
}

// Taken from <https://www.jetpackcompose.app/articles/donut-hole-skipping-in-jetpack-compose>
@Composable
inline fun LogCompositions(msg: String) {
    if (BuildConfig.DEBUG) {
        val ref = remember { Ref(0) }
        SideEffect { ref.value++ }
        d { "Compositions: $msg ${ref.value}" }
    }
}

class Ref(var value: Int)
a

Albert Chang

12/23/2021, 3:32 PM
It's probably better to use
derivedStateOf
when either: 1. the calculation is expensive 2. the derived state changes less frequently than the original state(s) changes Otherwise you can just use simple calculation without
remember
. Since your case falls into category 2, I would say pattern 4 is the best. Pattern 2 and 3 are meaningless as you are never changing the value of the mutable state.
s

Stylianos Gakis

12/23/2021, 3:44 PM
Okay so at least I am happy that it seems like I understand 2-3 correctly, I’d only want to wrap it like that if I were to change that value as you said. For point #1, isn’t this a point that one would make to just use
remember
though? With this wording alone, I’d first think of using a simple remember if all I knew about my use case is that it’s “expensive” This does fall into the point #2 you made though, it’s just that it’s not what first comes to my mind when dealing with such cases. I guess it’s a pattern that I’ll have to learn to spot by intuition? It’s definitely non-obvious in my head still.
c

Casey Brooks

12/23/2021, 4:22 PM
remember { }
is intended to be used for values that need to be kept constant across recompositions. In this case, anytime
now
changes, you want to recompute the elapsed time and thus do not want it remembered across recompositions. So that right there should be a signal that #1-3 are not a good fit, and
derivedStateOf { }
is what you want to use, or else just compute it directly if it's a fast calculation.
derivedStateOf { }
has internal mechanisms for detecting when it is reading
State
values, and only changes itself when those values themselves are changed. It's less about being tracked across recomposition, and more about optimizing chains of
State
variables that depend on each other. One huge difference between
remember { }
and
derivedStateOf { }
is that the former is
@Composable
while the latter is not. You can stick a call to
derivedStateOf { }
in other layers of your application (not just the
@Composable
layer), for example, a ViewModel that makes computations based on
State
variables. It's probably not the best practice to do that often (it's still a Compose API and you generally don't want to couple your other layers to Compose), but in some specific scenarios it may be an invaluable tool.
And one other note about the subtleties of these functions:
mutableStateOf()
doesn't work without
remember { }
. It can only track its changes internally if the
State
itself is remembered across compositions. You could also hold those
State
objects in some other class that lives outside of Compose, such as your ViewModel, and not need the call to
remember { }
since the ViewModel keeps the state alive across recomposition. Also,
State
objects in general can always be assigned to variables by delegate, so the differences between #2 and #3 are purely syntactic, but do the exact same thing. The implementation of the
by State<T>
delegate it literally just an inlined call to
.value
, so there is no runtime difference between one way or the other
a

Alex Vanyo

12/23/2021, 6:51 PM
Just another point to subtleties: #4 has a very subtle bug as written.
derviedStateOf
can only track changes to
State
objects that it reads in its block.
now
is one such state (due to the delegate), but
submittedAt
is not: it’s a plain value passed in as an argument. Because of that,
derivedStateOf
will continue to use the same, stale
submittedAt
value even if
MyComposable
recompose with a different
submittedAt
value because
remember
has no keys. There are two ways to get correct behavior from this: Adding a key to
remember
for `submittedAt`:
val someText4 by remember(submittedAt) {
        derivedStateOf { getRelativeTimeSpanString(submittedAt, now) }
    }
Using `rememberUpdatedState`:
val currentSubmittedAt by rememberUpdatedState(submittedAt)
    val someText4 by remember {
        derivedStateOf { getRelativeTimeSpanString(currentSubmittedAt, now) }
    }
(the example at https://developer.android.com/jetpack/compose/side-effects#derivedstateof has the same bug right now, we’re fixing that!)
I would treat
derivedStateOf
like a caching tool: It can be very useful in some situations, but you then also need to be extremely careful with how that caching gets invalidated. In most cases you’d probably be better off just doing the calculation directly.
s

Stylianos Gakis

12/23/2021, 8:30 PM
That is incredibly interesting actually. Would this bug stop existing if
submittedAt
was passed into this function as a
State<Instant>
instead of an
Instant
?
a

Alex Vanyo

12/23/2021, 9:01 PM
I though the same at first, but no, there’s the exact same issue (although made even more subtle). The
derivedStateOf
will capture the old
State<Instant>
instance. If you just changed the value of the same
State<Instant>
instance, it might work, but if
MyComposable
got recomposed with a different
State<Instant>
instance passed into it, the
derivedStateOf
will still be stuck listening for changes on the old one. (separately, having
[Mutable]State<Instant>
as a parameter is frowned upon: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1637855772048800?thread_ts=1637854750.046000&amp;cid=CJLTWPH7S)
s

Stylianos Gakis

12/24/2021, 9:02 AM
Wow I see, that makes sense now the way that you explain it 🤯 All these subtleties are quite interesting, and now I'm curious why Adam suggests
() -> T
too. What's the use case for that over just
T
? Interestingly, in this case if I had
() -> T
it's more likely that I'd intuitively use
updatedState
since I'm used to seeing it being used with lamdas and it makes sense that it'd be stale inside
derivedStateOf
but now I see that this can be true for normal variables too 🤔
a

Alex Vanyo

01/04/2022, 8:08 PM
If you have a callback or some other object that wants to “get” the current value of some state, that can be different than what the value of the object was when the callback was composed. One way to do that would be to pass the state reference via
State<T>
, for calling
State<T>.value
at some later point in time, but it’s preferred to just pass in the direct
() -> T
lambda for that case. And yup, I was in the same boat of thinking
rememberUpdatedState
was just for lambdas, but it’s a more general tool to convert a parameter for a
@Composable
back into a
State<T>
that can be observed with deferred reads like any other state further downstream. If you wanted to do that manually without
rememberUpdatedState
, I bet you’d get pretty close to the exact implementation of it: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]ate.kt;l=295-297;drc=44c23a7214a34d089ac218acb9460a66e5405c12
👏 1
s

Stylianos Gakis

01/05/2022, 4:20 AM
This conversation has been eye-opening for me, thank you so much to all of you for contributing in with your ideas and knowledge!
🎉 1
View count: 8