Vsevolod Ganin
06/08/2021, 2:27 PMAnimatedContent
in a dev branch and I have a question: what if I can’t map targetState
to actual content statically? Seems like I need to store map of states to content to be able to map any targetState
to some content at any time, but that’s not always possible. Let’s say I have a screen backstack (a list, basically). Then my targetState
would be the last index in that list. And let’s say I’m popping the front screen from backstack. Then the index to that screen is no longer valid and I can’t display exit animation for it. How to approach this?Doris Liu
06/08/2021, 5:25 PMtargetState
, so that it can be mapped to the screen content even after it's popped off the backstack. Using index as the targetState
is going to be problematic - If you push another screen onto the backstack before the previously popped off screen finishes animating out, you'd briefly have 3 active screens, two of which would have the same index.Vsevolod Ganin
06/10/2021, 10:04 PMDoris Liu
06/10/2021, 10:20 PMequals
for the screen object to do more meaningful comparison. I could give you more specific suggestion if you don't mind sharing a snippet. 🙂Vsevolod Ganin
06/11/2021, 10:29 AMsealed class Screen : Parcelable {
abstract fun reduce(action: Action): Screen
@Parcelize
data class ClickTrackList(val state: ClickTrackListScreenState) : Screen() {
...
}
@Parcelize
data class PlayClickTrack(val state: PlayClickTrackScreenState) : Screen() {
...
}
@Parcelize
data class EditClickTrack(val state: EditClickTrackScreenState) : Screen() {
...
}
@Parcelize
data class Metronome(val state: MetronomeScreenState?) : Screen() {
...
}
@Parcelize
data class Settings(val state: SettingsScreenState?) : Screen() {
...
}
@Parcelize
data class SoundLibrary(val state: SoundLibraryState?) : Screen() {
...
}
}
@Composable
fun ContentView(
screen: Screen,
position: Int,
dispatch: Dispatch,
) {
val modifier = Modifier.fillMaxSize()
val previousPosition = remember { mutableStateOf(position) }
val isPush = remember { mutableStateOf(true) }
if (position != previousPosition.value) {
isPush.value = position > previousPosition.value
previousPosition.value = position
}
AnimatedContent(
targetState = position,
transitionSpec = {
if (isPush.value) {
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Left) with
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Left)
} else {
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Right) with
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Right)
}
}
) { targetPosition ->
val targetScreen by remember { screen }
when (@Suppress("NAME_SHADOWING") val screen = targetScreen) {
is Screen.ClickTrackList -> ClickTrackListScreenView(screen.state, modifier, dispatch)
is Screen.PlayClickTrack -> PlayClickTrackScreenView(screen.state, modifier, dispatch)
is Screen.EditClickTrack -> EditClickTrackScreenView(screen.state, modifier, dispatch)
is Screen.Metronome -> MetronomeScreenView(screen.state, modifier, dispatch)
is Screen.Settings -> SettingsScreenView(screen.state, modifier, dispatch)
is Screen.SoundLibrary -> SoundLibraryScreenView(screen.state, modifier, dispatch)
}
}
}
A bit pseudocody for compactness, sorry. Overriding equals
is interesting idea… 🤔 feels a bit hacky thoughVsevolod Ganin
06/11/2021, 10:43 AMscreen
, so that change in position results in animation but change in value without changing position should not trigger animation (because it’s just updating content of front screen).Vsevolod Ganin
06/11/2021, 10:46 AMremember
screen inside passing lambda but this blocks any further updates of this screen without changing position. I can’t just pass screen
as targetState
because it will trigger animation on every little `screen`’s state changeDoris Liu
06/11/2021, 7:41 PMscreen
as it is designed isn't suitable as targetState
. But how do you intend to handle the following sequence of events when using position as the key:
backstack [B, A]
backstack [B] // A popped off
backstack [B, C] // C was pushed onto the stack while A is animating out.
A and C would have the same position
at the end of that example, should A disappear immediately, or continue animating out? Using position alone as the key would prevent you from achieving the latter.Doris Liu
06/11/2021, 7:48 PMposition
s, you could simply create a MutableMap
that keeps the up to date mapping between position and state.
val map = remember { mutableMapOf<Int, Screen>() }
map[position] = screen
AnimatedContent(...) {targetState ->
map[targetState]?.let { // return appropriate composable based on the screen object } ?: {}
}
Didn't test this snippet. Though this is the main idea. If you'd like to also clean up the map, you could use the Transition-based AnimatedContent API, and reset the map if transition.targetState == transition.initialState
Vsevolod Ganin
06/11/2021, 9:55 PM