Is it possible in KOTLIN or by remember in Jetpack Compose to change the value of a variable after some seconds?
For example, I have a variable var currentResult1 = remember { mutableStateOf(true) }.
How can I say that after my activity opens, this currentResult1.value change to false after 1 second?
Yes, you can change value of any MutableState value using LaunchedEffect or coroutineScope builder functions.
LaunchedEffect(Unit) {
delay(1000)
currentResult1.value = false
}
You can check out this answer how to use it for a timer that changes current value every second.
Related
I am beginner at jetpack compose. I was debugging recomposition but suddenly I saw a unusual recomposition in Header compose function when app start.
I find out the reason or culprit for the recomposition that I used in Header compose function to get string text by stringResource().. If I use context.getString() or hardcode string value instead of stringResource() then I got no recomposition.
This code when showing the recomposition
#Composable
fun MainScreen() {
Header()
}
#Composable
fun Header() {
Text(
text = stringResource(id = R.string.app_name)
)
}
But If I use these codes No more recomposition. But why?
#Composable
fun MainScreen() {
Header()
}
#Composable
fun Header() {
val context = LocalContext.current
Text(
text = context.getString(R.string.app_name)
)
}
So what can I do for get rid of recomposition when using stringResource() into compose functions
if you have the value saved in the res/values/strings.xml file then using compose the only thing you will need to do is calling the stringResource(R.string.app_name). Jetpack Compose handles getting the resource on its own. you wont even need to get it from your Context.
see here for docs on resource.
that should not cause recomposing every time but if it does it is always a good practice to save your values inside of a remember so that it knows not to recompose every time. the problem might be from a different part of your code.
First of all, this behavior shouldn't be happening, I recommend creating a clean project and trying again.
But... to avoid recomposing inside Composable, the Effect API would be useful:
val context = LocalContext.current
var appName = ""
LaunchedEffect(Unit) {
appName = context.getString(R.string.app_name)
}
Text(
text = appName
)
The codes inside the LaunchedEffect block are only executed once, even if the recomposition happens.
Documentaion of api Side Effects
I have a cookie clicker rip off app about beans in Kotlin and Compose. I have a LaunchedEffect running a timer that updates the amount of beans every second. The only problem is that I never stop the LaunchedEffect timer, so every time I execute the code, it creates another one leading to many timers adding many beans. A code snippet is below showing the code I am talking about.
if (showHome) {
val updateAmount by remember { mutableStateOf((greenBeans) + (10 * kidneyBeans) + (100 * coffeeBeans) + (1000 * pintoBeans) + (10000 * chocolateBeans) + (100000 * jellyBeans))}
LaunchedEffect(key1 = true) {
updateBeans {
beans += updateAmount
}
}
}
Is there a way to prevent this problem from occurring?
The LaunchedEffect itself is a composable, so every time the LaunchedEffect leave your compose scope, change screen, or etc. It will got cancelled.
Simple example is your code, when the showHome is set to false, then the LaunchedEffect leave composition and got destroyed. It will only started again when the showHome is set to true
The thing with your code is your conditional code block enters recomposition when showHome is true and creates a new LaunchedEffect and updateAmount
if (showHome) {
// Anything here is recomposed when it toggle
}
Anything inside this block enters composition if showHome is true initially and on every recomposition updateAmount changes from false to true. You might want to move updateAmount outside of if block for starters.
If you don't want to trigger a timer every time showHome is true
you might have it something Like
LaunchedEffect(key1 = true) {
if (showHome) {
updateBeans {
beans += updateAmount
}
}
}
With the snippet above LaunchedEffect will be launched once but update only when showHome is true.
If you want to reset timer every time you set showHome true you can use it as
LaunchedEffect(key1 = showHome) {
if (showHome) {
updateBeans {
beans += updateAmount
}
}
}
Ive recently got into doing animations using jet pack compose and am wondering how you can make it so that when you increase a value in an offset, once the animation reaches that value it then changes the value to another value. So like update transition but instead of at the same time, one after the other.
Actually #RaBaKa's answer is partially correct, but it's missing information about how the animation should be run.
It should be done as a side effect. For example, you can use LaunchedEffect: it is already running in a coroutine scope. It is perfectly normal to run one animation after another - as soon as the first suspend function finishes, the second will be started:
val value = remember { Animatable(0f) }
LaunchedEffect(Unit) {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
Text(value.value.toString())
If you want to do this in response to some action, such as pressing a button, you need to run the coroutine yourself. The main thing is to run the animations in the same coroutine so that they are chained.
val value = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
}) {
}
Text(value.value.toString())
The correct answer is to use Kotlin coroutines. I managed to get it working fine. You have to use coroutines in order to launch the animations in the correct sequence like this:
animationRoutine.launch {
coroutineScope {
launch {
animate(
startingValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
launch {
animate(
initialValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
}
Each of launch scope launches everything in a non blocking way if you tell it to allowing you to run multiple animations at once at a lower level and to sequence the animations you add another coroutine for the next part of the animation.
Maybe you can use Animatable
val value = remember { Animatable(0f) } //Initial Value
Then in compose you can just use
value.animateTo(20f)
then
value.animateTo(10f)
For more information visit the official documentation
I'm learning Compose, the following code is from the article.
The author use var toolbarTitle by remember { mutableStateOf("Home") } only for a title, is it necessary ?
I think var toolbarTitle= mutableStateOf("Home") is enough, right?
Source Code
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposeScaffoldLayoutTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
var toolbarTitle by remember {
mutableStateOf("Home")
}
val scaffoldState =
rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier.background(Color.White),
scaffoldState = scaffoldState,
topBar = {
AppToolbar(
scaffoldState = scaffoldState,
scope = scope,
toolbarTitle = toolbarTitle
)
}, drawerContent = {
DrawerContent(scaffoldState = scaffoldState, scope = scope)
},
...
)
}
}
}
}
}
If you don't use remember the value will reset again to Home on every recomposition by using remember the value will be persisted even after a recomposition
by the way recomposition means when the composable renders again which can happen a lot of times when something changes on the screen and needs to be rendered again
I think, if the article is from a reputed source, the variable might have some further usage in the project. The very reason for initialising the variable as a MutableStateis that the developer wants recompositions to occur upon the change of this value.
If this was not the case, it could have been just var title = "Home", or better instead just use "Home" in the parameter, no need of a variable at all. You see, if you are creating a MutableState, in most scenarios, it is useless to declare it without using remember. In fact, the only scenario I can think of, to declare a MutableState without remeber is to trigger recompositions manually using the var as a trigger.
Anyway, most of the times, you want to read the value of the var that is declared MutableState. If any modifications are made to the value of the var, then a recomposition is triggered. Now, if you declare it without any rememberance, the value will be re-initlaised to whatever you provided as the initial value. The updated value is gone for good in this case.
Hence, in the latest versions of the compiler, I think it will not even allow you to create a MutableState var without using remember. If not a compile-time error, I'm sure it gives at least a warning (though I am almost certain it won't allow you to compile, which makes me think Compose Developers do not want us to trigger dummy recompositions!)
PS: The recompositions can be triggered manually by using remember too, so I guess that was not their motto.
If you want to change toolbarTitle later in your code you would have to use remember { mutableStateOf("Home") }
If it is always supposed to be "Home" you can just use val toolbarTitle = "Home" or use "Home" directly in AppToolbar()
I want to create a Kotlin coroutines Flow that emits values when
they change, and
periodically emits the last value available, every x duration since the last change or last emit.
You could create a Flow that emits at a regular interval and then just use combine. Each time you'd combine the values, you'd really just be passing along the current value of the original Flow you are interested in.
// This is the main flow you are interested in. This uses
// a Flow builder just as a simple example but this could
// be any kind of Flow, like a (Mutable)StateFlow.
val emitter = flow {
emit("Your data")
// ...
}
// This just serves as a timer.
val timer = flow {
while (currentCoroutineContext().isActive) {
emit(Unit)
delay(500)
}
}
// This will emit whenever either of the Flows emits and
// continues to do so until "emitter" stops emitting.
combine(
emitter,
timer
) { value, ticker ->
// Always just return the value of your
// main Flow.
value
}
This seems to work -- every time a new value arrives, transformLatest cancels any previous lambdas and starts a new one. So this approach emits, and then continues to emit periodically until a new value arrives.
flow.transformLatest { value ->
while(currentCoroutineContext().isActive) {
emit(value)
delay(x)
}
}