Hi all, just a quick sanity check: am i doing this...
# compose
a
Hi all, just a quick sanity check: am i doing this right? wrote some code to fade text in and out and cycle through a list of messages, but its my first time doing this so dont know if theres apis im missing that would simplify some of this process (eg AnimatedVisibility etc) or if im doing anything silly/inefficiently.
Copy code
val coroutineScope = rememberCoroutineScope()
    var counter by remember { mutableStateOf(0)}
    val currentText by remember {
        mutableStateOf(messages[counter])
    }
    var showText by remember { mutableStateOf(false)}
    val textAlpha: Float by animateFloatAsState(
        targetValue = if (showText) 1f else 0f,
        finishedListener = { finalValue ->
            if (finalValue == 0f) {
                showText = true
                counter = (counter + 1) % (messages.size)
            } else {
                coroutineScope.launch {
                    delay(20.seconds)
                    showText = false
                }
            }
        }
    )
s
What’s the end result that you’re going after? Is it that the text as it changes it fades out and then the new text fades in?
Maybe a video would also help. But I really feel like you could simplify this a lot with a
CrossFade
or just
AnimatedContent
or something like that.
a
yes, it fades out, swaps to a new message when invisible, and fades another one in
s
Can you try giving a look into the
CrossFade
composable? See if it’s giving you more or less exactly what you want just by changing the text that you pass into it?
a
sounds promising certainly! thanks :)
s
You may not like it since for some frames both texts will be showing at the same time. To get more control you may need AnimatedContent instead. Soemthing like
Copy code
var text by remember { mutableStateOf(messages.first()) }
LaunchedEffect(Unit) {
  var counter = 1
  while (isActive) {
    text = messages[counter % messages.size]
    counter = counter + 1
    delay(20.seconds)
  }
}
AnimatedContent(
  targetState = text,
  transitionSpec = {
    val enterTransition = fadeIn(tween(durationMillis = 220, delayMillis = 220))
    val exitTransition = fadeOut(tween(durationMillis = 220, delayMillis = 220))
    enterTransition with exitTransition
  },
  contentAlignment = Alignment.Center,
) { resultingText ->
  Text(resultingText)
}
would make it so that they don’t overlap. And you can play with the transitions as you wish
a
This is more readable:
Copy code
var counter by remember { mutableStateOf(0) }
val alpha = remember { Animatable(0f) }
LaunchedEffect(messages) {
    while (true) {
        alpha.animateTo(1f)
        delay(20.seconds)
        alpha.animateTo(0f)
        counter = (counter + 1) % (messages.size)
    }
}
2
Also I don’t know what
currentText
is for but it looks suspicious.
a
i think youre right.. I was thinking currentText would change every time counter does but im realising thats not what im doing at all. i should just use it as a normal val or inline it when i pass it as a Text composable's value
s
My understanding is that your composable receives a list of Strings, and you just want to cycle through those right?
a
yeah, for this reason i wouldnt need to key the LaunchedEffect off messages
but the code seems to stand otherwise :)
a
If
messages
can change, you should do. Or at least
LaunchedEffect(messages.size)
.
a
messages is a function parameter so it can't change
or like, not in a way that matters right
s
If you do
LaunchedEffect(Unit)
, if messages changes, your LaunchedEffect won’t be restarted and it will keep a reference to the old instance of
messages
that you had passed into your composable.
Hence Albert did
LaunchedEffect(messages) {
to avoid this
a
ohh i didnt realise
would this still be the case if this composable left the composition entirely?
s
If it leaves composition then
LaunchedEffect
will get cancelled and everything will leave the memory completely. If you then call it again it will be a fresh instance yeah
a
the list of messages doesnt change in my use but evidently i should key off them on the off-chance it does in the future
s
But if you don’t make it leave the composition entirely, but instead you only call it again with different parameters, that’s when the trouble may exist.
In general, if you are using some parameter of your composable inside the lambda of your LaunchedEffect, you should use it as a Key. That’s a good thing to keep in mind as step 1. The alternative is to use rememberUpdatedState like this:
Copy code
fun Composable(input: String) {
  val updatedInput by rememberUpdatedState(input)
  LaunchedEffect(Unit) {
    while(something) {
      doSomethingWith(updatedInput)
    }
  }
}
This now would mean that if your LaunchedEffect tries to use the updatedInput again, it will now get the latest value that was passed into your composable, whatever that is. This will not make it re-trigger in case input changes though, so it really depends what you’re going for, and you decide according to your needs what approach you prefer.
a
thank you both, very good to know
my project is in a State right now so i cant get a build going and confirm what works best for me but im sure i'll get to the bottom of it
💪 1