Chuck Stein
08/19/2021, 5:19 PMremember but instead of recalculating if dependencies change, it recalculates if a given predicate is true?
My use case is I have some UI content within an AnimatedVisibility block, where visible = uiState is InitialUiState. (My UI state is a sealed class with subtypes we can call InitialUiState and SecondaryUiState.) Some of the UI content I'm animating depends on a property contained within InitialUiState, but not SecondaryUiState. So when it's animating out of visibility, my uiState no longer contains the data needed in order to render the UI. I can wrap the content in if (uiState is InitialUiState), but then the content just disappears instead of animating out. My thought is I need to memoize the most recent InitialUiState value, using something like val initialUiState = rememberUnless(uiState is InitialUiState) { uiState as? InitialUiState }, so that we can access the data contained within the last InitialUiState, even when the current value of uiState is a SecondaryUiState. In this example it would recalculate on every composition where uiState is InitialUiState, but "remember" and return the previous calculation otherwise.Colton Idle
08/19/2021, 5:44 PMChuck Stein
08/19/2021, 6:17 PMderivedStateOf before, but after reading the docs I'm not sure how it would apply to my situation. It seems like it will always use the most recent value of the state it depends on, so when the uiState is a SecondaryUiState, how would I access the most recent InitialUiState?Chuck Stein
08/19/2021, 8:59 PM@Composable
inline fun <T> rememberAndRecalculateIf(predicate: Boolean, calculation: @DisallowComposableCalls () -> T): T {
var recalculationKey by remember { mutableStateOf(false) }
if (predicate) {
// changing the key means we should recalculate, and we only change it if the predicate is true
recalculationKey = !recalculationKey
}
return remember(recalculationKey, calculation)
}Chuck Stein
08/19/2021, 8:59 PMAnimatedVisibility, to not capture and make available within its scope the last Snapshot state before visible became false...Colton Idle
08/19/2021, 9:22 PMDoris Liu
08/19/2021, 11:08 PMAnimatedContent would work better for your use case, where you specify what the UI looks like for initialUIState and {} (i.e. empty) for SecondaryUiState . Then when the targetState changes from initialUIState to SecondaryUiState, AnimatedContent will keep a reference to the previous state until the previous content has been animated out.Chuck Stein
08/20/2021, 9:19 PMAnimatedContent! Following the flowchart in the documentation led me to AnimatedVisibility, because I wouldn't say the answer to "Animating appearance/disappearance" is "No" for my use case š
So AnimatedContent works except for one major issue -- since my targetState is the current uiState (as it contains the data I need to keep a reference to while animating out), it is now running the transition each time there are any changes to the data contained within InitialUiState! Ideally I want to only run the transition each time uiState is InitialUiState changes, but if I set that to my targetState then I only have access to that boolean within the content block, whereas what I really need is a reference to the previous InitialUiState, so that I can animate that data out once the actual uiState is SecondaryUiState. So, is there any way to run the transition every time a particular state changes, but then each time that state changes, also hold onto additional state to make available within the content block? If not, I don't see any way to achieve my (pretty normal) use case with the current animation APIs.Doris Liu
08/20/2021, 9:40 PMAnimatedContent perceive all InitialUiState instances as the same state. One thing you could try is overriding equals in InitialUiState . Seems like AnimatedContent should have customizable equality support.Doris Liu
08/20/2021, 9:42 PMChuck Stein
08/20/2021, 10:58 PMequals in InitialUiState to return true if the other value is also an InitialUiState, then whenever I modify a property of InitialUiState, the UI does not update to reflect the change, I assume because the Compose runtime thinks the State has not changed since it sees that it is "equal" to the previous State.Doris Liu
08/20/2021, 11:24 PMequals :
val targetState = key(state is InitialUiState) {
rememberUpdatedState(state)
}
AnimatedContent(targetState) { target ->
if (target.value is InitialUiState) {
// Content for initial ui state
} else {
// Content for secondary ui state
}
}Chuck Stein
08/20/2021, 11:24 PMAnimatedContent seems like it would get the job done. Although I wonder if this would be flexible enough for all use cases -- I could see the content block potentially needing to reference multiple state objects from when the old content started animating out. Almost seems like when the targetState changes, instead of calling the content block with the previous targetState to animate the outgoing content, we should enter into a Snapshot taken just before targetState changed, and then call the content block inside of that Snapshot to animate the outgoing content. Wouldn't that automatically take care of making all state objects from the old content still available while it's animating out?Chuck Stein
08/20/2021, 11:25 PMDoris Liu
08/20/2021, 11:29 PMAlmost seems like when theĀWe are working on such a concept in the low level of Compose. It would be very useful as a default behavior for removal animation, once it's done.Ā changes, instead of calling theĀtargetStateĀ block with the previousĀcontentĀ to animate the outgoing content, we should enter into aĀtargetStateĀ taken just beforeĀSnapshotĀ changed, and then call theĀtargetStateĀ block inside of thatĀcontentĀ to animate the outgoing content.Snapshot
Chuck Stein
08/20/2021, 11:33 PMChuck Stein
08/21/2021, 1:15 AMkey and rememberUpdatedState seems to work, but I can't figure out why. What exactly is key doing here? I can't tell from the docs, and the examples I've seen all have to do with identifying composables within a for loop. At first I assumed it would only run the block to update the state if the value of state is InitialUiState changed, but I tested and this doesn't seem to be the case. So if the targetState is changing even when it's changing from one InitialUiState to another InitialUiState, why does no transition occur between the two?Doris Liu
08/21/2021, 1:25 AMkey specifies the identity of containing composable. In that sense, it's the same as specifying different keys for different content in a loop. What it does here is to create a new rememberUpdatedState composable as the identity changes. Therefore you get a new State<UiState> instance when changing from InitialUiState to SecondaryUiState.
When changing from one InitialUIState to another, the key stays the same (i.e. true), only the State<UiState>#value gets updated.Chuck Stein
08/21/2021, 1:29 AM