Jetpack Compose run animation after recomposition - android

Is there any way how to run animation once recomposition (of screen, or some composable) is done?
When animation is running and there is recomposition at the same time, animation has very bad performance (it is not smooth at all).
I tried delay to delay animation for a while, but I don't think that's a good idea. There must be better way of doing this.

Please provide producible example so you won't need to ask it again. You can run an animation with Animatable calling animatable.animateTo() inside LaunchedEffect after composition is completed. And instead of creating recomposition by using Modifier.offset(), Modifier.background() you can select Modifiers counterparts with lambda.
https://developer.android.com/jetpack/compose/performance/bestpractices#defer-reads
You can animate alpha for instance without triggering recomposition after composition is completed inside Draw phase of frame
val animatable = remember {
Animatable(0f)
}
LaunchedEffect(key1 = Unit) {
animatable.animateTo(1f)
}
Box(
Modifier
.size(100.dp)
.drawBehind {
drawRect(Color.Red.copy(alpha = animatable.value))
}
)
https://stackoverflow.com/a/73274631/5457853

Related

Why does this code get executed twice? If recomposition... what triggers the recomposition?

I am Learning Android Compose, And I was looking/playing with this code from developers.android, in github.
The projects is a simple app to demonstrate adaptive screen. Sports App
Everything works fine, but am a but confused.
I logged an item/line to Logcat. And I see that it gets executed twice? Recomposition? What is causing it?
In your code:
Log.i("info", "xxx")
Column(
modifier = Modifier.padding(4.dp)
) {
Box {
Image(painter = painterResource(R.drawable.xx))
Text()
}
Text(
text = stringResource(R.string.app_name),
)
}
The stringResource and painterResource can cause recomposition.
In compose when something triggers a recomposition, it happens in the nearest scope.
However the Box and the Column are inline function, and it means that both don't have an own recompose scopes.
In your code when the Image and the Text are recomposed all the composable is recomposed.

Manage condition logic in stateless compose in jetpack compose

I am learning State hosting in jetpack compose. I have created two separated function ContentStateful and ContentStateLess. In my ContentStateLess there is a lot of view inside them and I am checking some condition and change view accordingly. I am guessing that there is no condition/business logic inside Stateful compose. So what is the proper way of doing this kind of logic in here.
ContentStateful
#Composable
fun ContentStateful(
viewModel: PairViewModel = getViewModel()
) {
ContentStateLess(viewModel)
}
ContentStateLess
#Composable
fun ContentStateLess(
viewModel: PairViewModel
) {
Text()
Text()
Image()
if (viewModel.isTrue) {
Image()
// more item here
} else {
Text()
// more item here
}
Image()
}
So what is the best recommendation for this if - else logic in ContentStateLess(). Many Thanks
If you are building stateless Composables it's better not to pass anything like ViewModel. You can pass Boolean parameter instead. When you wish to move your custom Composable to another screen or another project you will need to move ViewModel either.
The reason Google recommends stateless Composables is it's difficult to test, you can easily test a Composable with inputs only.
Another thing you experience the more states inner composables have to more exposure you create for your composable being in a State that you might not anticipate.
When you build simple Composables with one, two, three layers might not be an issue but with more states and layers state management becomes a serious issue. And if you somehow forget or miss a state inside a Composable you might end up with a behavior that's not expected. So to minimize risks and make your Composables testable you should aim to manage your states in one place and possible in a state holder class that wraps multiple states.
#Composable
fun ContentStateLess(
firstOneTrue: Boolean
) {
Text()
Text()
Image()
if (firstOneTrue) {
Image()
// more item here
} else {
Text()
// more item here
}
Image()
}

NetworkOnMainThreadException when used as a LaunchedEffect key

When I use
LaunchedEffect(Dispatchers.IO)
I get,
NetworkOnMainThreadException
How should I use this function to run on background thread?
this is my code:
LaunchedEffect(Dispatchers.IO) {
val input = URL("https://rezaapp.downloadseriesmovie.ir/maintxt.php").readText()
println(input)
}
I'm using it inside my jetpack compose project
LaunchedEffect is one of the many Side Effects in Jetpack Compose, but instead of just explaining, it would be better for us to just have very simple compose use-case. Though I'm expecting that you already know what is re-composition and how a MutableState triggers it.
What we'll have:
a screen with a button in the middle
a MutableState increment-able integer value
a Log statement inside LaunchedEffect
What we'll do
click the button and increment the MutableState integer value
print the incremented value
What we'll expect
Logcat will display the value coming from LaunchedEffect, even before clicking the button
Our simple Composable
#Composable
fun ComposeSample() {
var intNum by remember {
mutableStateOf(0)
}
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(onClick = {
intNum++
}) {
Text(
text = "Increment the integer"
)
}
}
LaunchedEffect(Unit) {
Log.e("IntNumber", "Current value: $intNum")
}
}
At this point, pay attention to the key I supplied with the LaunchedEffect.
When the screen is rendered for the first time, LaunchedEffect will trigger and we'll see a logcat print.
E: Current value: 0
But when I click the button it doesn't show the incremented value. Because LaunchedEffect needs a key that will change if you intend to trigger it every re-composition.
Now I changed the key I supplied to LaunchedEffect using the MutableState intNum variable,
LaunchedEffect(intNum) {
Log.e("IntNumber", "Current value: $intNum")
}
every click the logcat prints, because every time the intNum changes, the LaunchedEffect is triggered and triggers the Logcat statement.
E: Current value: 0
E: Current value: 1
E: Current value: 2
E: Current value: 3
When a key of a LaunchedEffect has changed and a composition happens, it will trigger anything inside its block.
I'm not sure what you are trying to achieve with your posted code, I don't even know how did it happen, but I suppose there aren't any use-case (to the best of my knowledge) where you will use a specific coroutine Dispatcher as a LaunchedEffect key.
Let me give it to you straight and clear,
lifecyclescope, or any coroutine scope in compose ui is on main
thread by default.
Especially for the compose coroutine scope you can not change their
dispatcher,
I strongly suggest that you call ViewModel method and there you launch the coroutine in side viewmodelSope, also keep in mind that the compose coroutine scopes are for light suspending operation, do not perform any heavy lift in those scopes.

Use of LaunchedEffect vs SideEffect in jetpack compose

Hi guys I am learning side-effect in my project. I want to know when should I use LaunchedEffect and SideEffect in which scenario. I am adding some piece of code using both effect. Please lemme know if I am doing wrong here.
1st using LaunchedEffect, please guide me if we need effect on this function or not.
#Composable
fun BluetoothRequestContinue(multiplePermissionsState: MultiplePermissionsState) {
var launchPermission by remember { mutableStateOf(false) }
if (launchPermission) {
LaunchedEffect(Unit) {
multiplePermissionsState.launchMultiplePermissionRequest()
}
}
AbcMaterialButton(
text = stringResource(R.string.continue_text),
spacerHeight = 10.dp
) {
launchPermission = true
}
}
2nd using SideEffect to open setting using intent
#Composable
fun OpenPermissionSetting(router: Router = get()) {
val activity = LocalContext.current as Activity
var launchSetting by remember { mutableStateOf(false) }
if (launchSetting) {
SideEffect {
activity.startActivity(router.permission.getPermissionSettingsIntent(activity))
}
}
AbcMaterialButton(
text = stringResource(R.string.open_settings),
spacerHeight = 10.dp
) {
launchSetting = true
}
}
Please let me know if we need Effect or not. Also guide me if we need different effect as well. Thanks
There difference between
if (launchSetting) {
SideEffect {
// Do something
}
}
and
if (launchPermission) {
LaunchedEffect(Unit) {
multiplePermissionsState.launchMultiplePermissionRequest()
}
}
both enters recomposition when conditions are true but LaunchedEffect is only invoked once because its key is Unit. SideEffect is invoked on each recomposition as long as condition is true.
SideEffect function can be used for operations that should be invoked only when a successful recomposition happens
Recomposition starts whenever Compose thinks that the parameters of a
composable might have changed. Recomposition is optimistic, which
means Compose expects to finish recomposition before the parameters
change again. If a parameter does change before recomposition
finishes, Compose might cancel the recomposition and restart it with
the new parameter.
When recomposition is canceled, Compose discards the UI tree from the
recomposition. If you have any side-effects that depend on the UI
being displayed, the side-effect will be applied even if composition
is canceled. This can lead to inconsistent app state.
Ensure that all composable functions and lambdas are idempotent and
side-effect free to handle optimistic recomposition.
Sample from official docs
To share Compose state with objects not managed by compose, use the
SideEffect composable, as it's invoked on every successful
recomposition.
#Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {
val analytics: FirebaseAnalytics = remember {
/* ... */
}
// On every successful composition, update FirebaseAnalytics with
// the userType from the current User, ensuring that future analytics
// events have this metadata attached
SideEffect {
analytics.setUserProperty("userType", user.userType)
}
return analytics
}
LaunchedEffect is for calling a function on composition or on recomposition if keys are changed.
If you write your LaunchedEffect as
LaunchedEffect(key1= launchPermission) {
if(launchPermission) {
// Do something
}
}
code block inside if will not be called in composition if key is not true but whenever it changes from false to true code block will be invoked. This is useful for one-shot operations that are not fired by user interaction directly or when an operation requires a CoroutineScope invoked after user interaction, animations or calling suspend functions such as lazyListState.animateScrollToItem()
Definition of concept of side-effect from Wikipedia
In computer science, an operation, function or expression is said to
have a side effect if it modifies some state variable value(s) outside
its local environment, which is to say if it has any observable effect
other than its primary effect of returning a value to the invoker of
the operation. Example side effects include modifying a non-local
variable, modifying a static local variable, modifying a mutable
argument passed by reference, performing I/O or calling other
functions with side-effects. In the presence of side effects, a
program's behaviour may depend on history; that is, the order of
evaluation matters. Understanding and debugging a function with side
effects requires knowledge about the context and its possible
histories.

OnClick execute function multiple time

i am trying to learn jetpack compose but i have some hard time working with navigation, specifically i cant understand why this code print "notwork" two time.
#Composable
fun NavigationController() {
var navController = rememberNavController()
NavHost(navController, startDestination = DummyRoutes.Dummy.route) {
composable(route = DummyRoutes.Dummy.route) {
Dummy(
openHome = { navController.navigate(SomeRoutes.SomeOther.route) },
)
}
}
}
#Composable
fun Dummy(
openHome: () -> Unit,
) {
Log.d("hello", "notwork: ")
Button(onClick = {openHome()}) {
}
NavHost recomposing its destinations because of transition animations.
With the initial route it only happens two times, but if you try to navigate to an other screen, there will be more recompositions of both appearing/disappearing screens. This is expected behaviour.
If you use your own animation, your view will be recomposed up to once a frame, and this is OK too.
There're times when you can reduce number of recompositions, e.g. when you use an often changing state, like lazy list state, check out this answer for more details.
But with animations and navigation you can't reduce this number, and you shouldn't, because screen is actually needs to be redrawn that often. Extra recompositions won't affect your app performance if you build it right - handle side effects correctly. See more details about recompositions and side effects in Thinking in Compose.
Composable component is recomposed every time when its parameters are changed. In your case, it is openHome. It might be that the lambda is changed in NavigationController.
Here is an interesting article about understanding recomposition with a case study

Categories

Resources