Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed stack animation not updating to new child component instance when active configuration is unchanged #794

Merged
merged 1 commit into from
Oct 15, 2024

Conversation

arkivanov
Copy link
Owner

@arkivanov arkivanov commented Oct 12, 2024

Fixes #792

Summary by CodeRabbit

  • New Features

    • Enhanced stack transition animations for a smoother user experience.
    • Added internal functions for filtering and sorting Kotlin collections.
    • Introduced flexibility in animation duration settings.
  • Bug Fixes

    • Improved handling of user interactions during animations.
  • Tests

    • Added comprehensive unit tests for stack animations to ensure correct behavior during push and pop operations.
    • Updated test methods for better clarity and flexibility in testing stack animations.

@arkivanov arkivanov force-pushed the fix-stack-animations-2 branch from 82aee7d to d489220 Compare October 13, 2024 16:50
@arkivanov arkivanov marked this pull request as ready for review October 13, 2024 17:19
Copy link

coderabbitai bot commented Oct 13, 2024

Walkthrough

The pull request introduces several enhancements to the stack animation handling in the DefaultStackAnimation and related classes. Key modifications include the addition of a nextItems variable to manage upcoming animation items, refined logic for updating items during transitions, and improved handling of user interactions during animations. The ChildStackTest and ChildrenTest classes have been updated to accommodate new type parameters, while new test files for DefaultStackAnimation and SimpleStackAnimation have been created to validate animation behavior.

Changes

File Change Summary
.../DefaultStackAnimation.kt Modified DefaultStackAnimation class with a new nextItems variable, refined item update conditions, and enhanced input handling during animations. Updated getAnimationItems function logic.
.../ChildStackTest.kt Updated animation parameter type, added a new test method for child instances, and modified setContent method for flexibility. Introduced an overload for routerState.
.../DefaultStackAnimationTest.kt Introduced unit tests for DefaultStackAnimation, validating animation behavior during stack operations with helper functions.
.../AbstractStackAnimation.kt Added nextItems variable and refined logic for updating items. Introduced DisposableEffect for managing nextItems lifecycle.
.../ChildrenTest.kt Updated StackAnimation type parameters and modified method signatures for flexibility. Added a new test method for child instances.
.../SimpleStackAnimationTest.kt Introduced unit tests for SimpleStackAnimation, validating animation behavior with helper functions.

Assessment against linked issues

Objective Addressed Explanation
Strange behavior when quick replace is made (#792)

Possibly related PRs

Poem

In a world of stacks and hops,
Where animations dance and swap,
With nextItems in the fray,
Smooth transitions lead the way.
A rabbit's joy, a playful cheer,
For stack animations, bright and clear! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Outside diff range and nitpick comments (5)
extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (2)

30-77: Enhance test readability by adding descriptive comments.

The test method WHEN_animating_push_and_stack_popped_during_animation_THEN_animated_push_and_pop_fully() is complex and involves multiple steps and data collections. Adding comments to explain the purpose of key sections, such as the logic behind collecting values1 and values2, will improve readability and maintainability.


147-154: Leverage type inference for cleaner code.

In the child and stack functions, the explicit return types can be omitted, and you can simplify lambda expressions for better readability.

Apply this diff to simplify the functions:

-private fun child(config: Int): Child.Created<Int, Any> =
+private fun child(config: Int) =
     Child.Created(configuration = config, instance = Any())

-private fun stack(vararg stack: Int): ChildStack<Int, Any> =
+private fun stack(vararg stack: Int) =
     ChildStack(
         active = child(stack.last()),
-        backStack = stack.dropLast(1).map(::child),
+        backStack = stack.dropLast(1).map { child(it) },
     )
extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (3)

141-150: Ensure when expression in animateFloat is exhaustive

Although all enum cases of EnterExitState are covered in the when expression, explicitly marking the expression as exhaustive can improve code safety and readability.

Modify the when expression to be exhaustive:

            when (state) {
                EnterExitState.PreEnter -> 0F
                EnterExitState.Visible -> 1F
                EnterExitState.PostExit -> 0F
+               else -> error("Unknown state: $state")
            }

This ensures that any future additions to EnterExitState will cause a compilation error if not handled.


38-42: Consider passing progress as a parameter instead of using CompositionLocalProvider

Using CompositionLocalProvider to pass factor may introduce complexity, especially if multiple animations are involved. Passing factor as a parameter to the content composable could enhance clarity and reduce the reliance on composition locals.

Refactor the selector to pass factor directly:

-                        stackAnimator(animationSpec = tween(durationMillis = 1000)) { factor, _, content ->
-                            CompositionLocalProvider(LocalProgress provides factor) {
-                                content(Modifier)
-                            }
+                        stackAnimator(animationSpec = tween(durationMillis = 1000)) { factor, _, content ->
+                            content(Modifier, factor)

Adjust the content composable to accept the additional parameter.


26-27: Consider simplifying test function naming conventions

While descriptive test names are valuable, the annotation @Suppress("TestFunctionName") indicates that the function names may be excessively long. Consider adopting a naming convention that balances descriptiveness with brevity.

For example, you could use backticks to allow spaces in function names without suppressing the naming convention:

@Test
fun `animating push and stack popped during animation animates push and pop fully`() { ... }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 17852c5 and d489220.

📒 Files selected for processing (6)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (5 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (1 hunks)
  • extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (3 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (5 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (1 hunks)
🧰 Additional context used
🔇 Additional comments (22)
extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (4)

33-44: Effective handling of animation updates with nextItems

The introduction of nextItems and the updated logic correctly address the issue where the stack's active configuration remains unchanged but the child instance updates. This ensures that the animation stack reflects the new child component instance, resolving the animation inconsistency described in the PR objectives.


62-69: Appropriate use of DisposableEffect for state management

Utilizing DisposableEffect within the exit condition allows for the safe update of items with nextItems upon the disposal of the exiting item. This ensures that the transition to the new animation items occurs only after the exit animation completes, maintaining smooth and accurate animations.


75-75: Correct input handling during pending animations

The updated condition for displaying InputConsumingOverlay now accounts for (nextItems != null) in addition to (items.size > 1). This effectively prevents user interactions during ongoing or pending animations, enhancing the user experience by avoiding unintended behavior during transitions.


83-86: Improved initial animation item generation

The refinement in the getAnimationItems function ensures that when oldStack is null or the active keys are identical, a single AnimationItem is created with isInitial = true. This clarifies the initial state handling and avoids unnecessary animations when there are no actual changes to animate.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (1)

139-145: Ensure exhaustive handling of EnterExitState in animateFloat.

Currently, all possible states of EnterExitState are handled. However, to future-proof the code against potential additions to the enum, consider adding an else branch or a default case.

Run the following script to check for exhaustive when expressions over EnterExitState:

This script searches for when expressions that handle EnterExitState but lack an else branch. If any are found, consider adding an else case to handle any future states.

extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (3)

100-102: 🛠️ Refactor suggestion

Use remember instead of LaunchedEffect for side-effect-free state updates

Using LaunchedEffect to increment counter introduces a side effect within a composable, which may lead to unintended behavior. Instead, consider using remember to safely increment the counter without side effects.

Refactor the code as follows to use remember:

-                    if (it.key == 1) {
-                        LaunchedEffect(Unit) {
-                            counter++
-                        }
-                    }
+                    if (it.key == 1) {
+                        counter = remember { counter + 1 }
+                    }

Likely invalid or redundant comment.


160-162: 🛠️ Refactor suggestion

Avoid using companion object for mutable state

Defining LocalProgress within a companion object might cause unintended sharing of state across different instances of the test class, especially if tests are run in parallel.

Move LocalProgress outside the companion object:

-        private companion object {
-            private val LocalProgress = compositionLocalOf { 0F }
-        }
+        private val LocalProgress = compositionLocalOf { 0F }

This ensures that each instance of the test class has its own LocalProgress.

Likely invalid or redundant comment.


95-102: Verify that counter accurately reflects recompositions

The variable counter is incremented within LaunchedEffect, but it's important to ensure that it correctly counts the number of times the composable with key 1 is recomposed or restarted.

Run the following script to check for any shadowed variables and ensure counter is updated correctly:

Review the output to confirm that counter is declared only once.

✅ Verification successful

counter accurately reflects recompositions

The variable counter is declared only once and is incremented solely within the intended LaunchedEffect block. This ensures that it accurately counts the number of recompositions for the composable with key == 1.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that there are no shadowed declarations of `counter` in the test file.

# Search for declarations of `counter`. Expect: Only one declaration at line 95.
rg --line-number --word-regexp "var counter" extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt

Length of output: 198


Script:

#!/bin/bash
# Description: Verify that `counter++` is only called within the intended LaunchedEffect blocks for key == 1.

# Search for all LaunchedEffect blocks and check if `counter++` is present
ast-grep --lang kotlin --pattern '
LaunchedEffect($_) {
  $$$
  counter++
  $$$
}' extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt

Length of output: 209


Script:

#!/bin/bash
# Description: Search for all instances of `counter++` in the test file to verify their contexts.

rg --line-number --word-regexp "counter\+\+" extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt

Length of output: 209

extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (6)

29-29: LGTM: Importing assertSame for instance comparison

The addition of import kotlin.test.assertSame is appropriate for ensuring that the instances in tests are identical by reference.


34-34: Updated type parameter to Any for StackAnimation

Changing the animation parameter to StackAnimation<Config, Any>? enhances flexibility by allowing any type of instance, which is useful for testing different scenarios.


92-102: Added test for new instance with the same key

The new test GIVEN_child_displayed_WHEN_new_child_instance_with_the_same_key_THEN_new_child_instance_displayed correctly verifies that a new instance replaces the old one even when the key remains the same. This ensures the stack updates appropriately with new instances.


198-205: Modified setContent to accept generic instances

Updating the setContent method to accept State<ChildStack<Config, Any>> and a content lambda with Child.Created<Config, Any> allows for greater flexibility in rendering different instance types within the tests.


226-231: Overloaded routerState for Child.Created<Config, Any>

The new overloaded routerState function simplifies creating a ChildStack<Config, Any> from a variable number of Child.Created<Config, Any> instances, enhancing test setup versatility.


257-257: Updated getParameters to use StackAnimation<Any, Any>

Changing the return type of getParameters() to List<StackAnimation<Any, Any>?> accommodates a broader range of animations, allowing tests to cover more generic cases and ensuring compatibility with the updated type parameters.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (3)

3-3: Import AnimatedVisibilityScope Added

The addition of import androidx.compose.animation.AnimatedVisibilityScope is appropriate and necessary for the updated content parameter in the setContent function.


33-33: Import assertSame for Instance Equality Check

Including import kotlin.test.assertSame is correct as it's utilized in the new test method to verify that the new instance is displayed.


97-107: New Test for Child Instance Replacement

The test method GIVEN_child_displayed_WHEN_new_child_instance_with_the_same_key_THEN_new_child_instance_displayed effectively verifies that a new instance with the same key replaces the old instance. The use of assertSame correctly checks for instance equality.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (5)

52-52: LGTM: Introduced nextItems for managing upcoming animations

The addition of nextItems effectively manages the animation items for the next stack state, enhancing the handling of transitions when the active configuration remains unchanged.


62-65: LGTM: Updated condition for item updates

The refined condition accurately checks whether to update items based on both the active key and the instance of the active child. This ensures that animations are appropriately triggered even when the active configuration hasn't changed but the instance has.


71-76: LGTM: Proper assignment of newItems

The logic deciding whether to assign newItems to items or nextItems based on the size of items effectively manages transitions during stack updates.


95-102: LGTM: Correct cleanup of nextItems after exit animations

Using DisposableEffect to manage the disposal of nextItems ensures that items are correctly updated after an exit animation completes, preventing stale data and ensuring smooth transitions.


108-108: LGTM: Input correctly disabled during animations involving nextItems

By including (nextItems != null) in the condition, you ensure that user input is appropriately disabled when there are pending nextItems, enhancing the user experience during transitions.

@arkivanov arkivanov force-pushed the fix-stack-animations-2 branch from d489220 to 675ba35 Compare October 15, 2024 10:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (8)
extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (1)

8-11: LGTM! Consider a minor optimization for clarity.

The takeWhileIndexed function is well-implemented and follows functional programming principles. It effectively combines withIndex(), takeWhile, and map to achieve the desired result.

For slightly improved readability, you could consider using destructuring in the lambda:

internal fun <T> Iterable<T>.takeWhileIndexed(predicate: (Int, T) -> Boolean): List<T> =
    withIndex()
        .takeWhile { (index, item) -> predicate(index, item) }
        .map { it.value }

This change doesn't affect functionality but might make the code slightly more intuitive.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt (2)

10-13: LGTM! Consider adding KDoc for clarity.

The takeSorted function is well-implemented and efficient. It correctly uses the takeWhileIndexed function to create a sorted subset of the list based on the provided comparator.

Consider adding KDoc to explain the function's behavior, especially noting that it returns a prefix of the list that is already sorted according to the comparator. This would improve clarity for future maintainers.

/**
 * Returns the longest prefix of this list that is sorted according to the given [comparator].
 *
 * @param comparator The comparator to determine the order of elements.
 * @return A new list containing the longest sorted prefix of this list.
 */
internal fun <T> List<T>.takeSorted(comparator: Comparator<T>): List<T> = ...

15-18: LGTM! Consider adding KDoc for clarity.

The takeWhileIndexed function is well-implemented, making efficient use of Kotlin's standard library functions.

Consider adding KDoc to explain the function's behavior, especially noting the inclusion of the index in the predicate. This would improve clarity for future maintainers.

/**
 * Returns a list containing first elements satisfying the given [predicate] applied to the element's index and the element itself.
 *
 * @param predicate The predicate to be invoked on each element, which also receives the element's index.
 * @return A list containing the first elements satisfying the given [predicate].
 */
internal fun <T> Iterable<T>.takeWhileIndexed(predicate: (Int, T) -> Boolean): List<T> = ...
extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (2)

71-76: Approve the new logic for assigning items with a suggestion

The new logic for assigning items is a good improvement. It handles single-item and multi-item cases differently, which allows for more precise control over transitions. This approach should help in managing rapid changes more effectively, addressing the issues described in #792.

However, to improve code clarity, consider extracting the condition items.size == 1 into a named variable:

val isSingleItem = items.size == 1
if (isSingleItem) {
    items = newItems
} else {
    nextItems = newItems
}

This minor refactoring would make the intent of the condition more explicit and improve readability.


108-108: Approve the updated condition for disabling user input with a suggestion

The expanded condition for disabling user input during animations is a good improvement. By including a check for nextItems, it ensures that user interactions are properly managed during complex transitions, which is crucial for preventing unexpected behavior in scenarios like those described in #792.

To improve readability, consider extracting the condition into a separate function:

private fun shouldDisableInput(): Boolean =
    disableInputDuringAnimation && ((items.size > 1) || (nextItems != null))

// Usage
if (shouldDisableInput()) {
    Overlay(modifier = Modifier.matchParentSize())
}

This refactoring would make the code more self-documenting and easier to maintain.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (3)

35-36: Consider using more descriptive variable names for clarity

The variables values1 and values2 could be renamed to more descriptive names like valuesFirstChild and valuesSecondChild to improve code readability and maintainability.


43-46: Handle unexpected keys in the when expression

To increase the robustness of the test, consider adding an else branch to the when expression to handle unexpected it.key values.


116-120: Rename parameter stack to avoid confusion

In the function private fun stack(vararg stack: Int), the parameter name stack could be confusing since it suggests a complete stack but actually represents individual configurations. Consider renaming it to configs or items for better clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d489220 and 675ba35.

📒 Files selected for processing (9)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt (1 hunks)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (2 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (5 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (1 hunks)
  • extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (3 hunks)
  • extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (1 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (5 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt
🧰 Additional context used
🔇 Additional comments (22)
extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (2)

3-6: LGTM! Efficient implementation of takeSorted.

The takeSorted function is well-implemented and efficient. It cleverly uses takeWhileIndexed to create a new list that maintains the order specified by the comparator, stopping as soon as an out-of-order element is encountered. This approach is more efficient than sorting the entire list when only a portion of it needs to be in order.


1-11: Well-designed utility functions for collection processing.

These new utility functions, takeSorted and takeWhileIndexed, are well-implemented and provide valuable extensions to Kotlin collections. Their internal visibility is appropriate, limiting their use to within the module. Given their location in the commonTest source set, they appear to be designed for use in testing scenarios, which is a good practice for keeping test-specific utilities separate from production code.

The functions demonstrate a good understanding of functional programming concepts and efficient collection processing. They should prove useful for various testing scenarios within the module, particularly when dealing with ordered collections or when needing to process collections with access to both index and value.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt (1)

10-18: Good additions supporting the PR objectives.

The new utility functions takeSorted and takeWhileIndexed are well-implemented and appear to support the improvements in stack animation handling mentioned in the PR objectives. Their internal visibility appropriately limits their use to within the module.

These functions likely contribute to more efficient management of animation items during transitions, which aligns with the goal of fixing the stack animation issues described in the linked issue #792.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (1)

19-20: LGTM! Improved flexibility for animation testing.

The addition of the durationMillis parameter to the animateFloat function is a positive change. It allows for more precise control over animation timing in tests, which can be crucial for debugging issues like the one described in the PR objectives. The use of AnimationConstants.DefaultDurationMillis as the default value ensures backward compatibility.

This change enhances the utility of the function without introducing any apparent issues. It's well-aligned with the goal of improving stack animation handling.

extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (7)

5-5: LGTM: Import statement addition is appropriate.

The addition of the DisposableEffect import is consistent with its usage in the new functionality introduced later in the file.


33-33: LGTM: New variable nextItems enhances animation state management.

The introduction of nextItems as a nullable Map<Any, AnimationItem<C, T>> using mutableStateOf is a good addition. It allows for better handling of upcoming animation items, which aligns with the PR's objective to improve stack animation behavior.


35-44: Excellent update to the stack refresh condition.

The modification to check both stack.active.key and stack.active.instance addresses the core issue described in the PR objectives. This change ensures that the stack updates correctly when a new child component instance is created, even if the active configuration remains unchanged.

The subsequent logic for updating items and nextItems is also well-structured:

  1. If there's only one item, it directly updates items.
  2. Otherwise, it prepares the next set of items in nextItems.

This approach should resolve the animation issues when quickly replacing components in a nested structure.


62-69: Great addition of DisposableEffect for managing animation state transitions.

The introduction of the DisposableEffect for exiting items is a crucial improvement. It ensures that:

  1. The nextItems are applied to items when an exiting animation completes.
  2. The nextItems state is reset to null after being applied.

This mechanism provides a clean way to handle the transition between animation states, which is essential for the smooth operation of the stack animation, especially during rapid component replacements.


75-75: Improved InputConsumingOverlay condition enhances user interaction handling.

The updated condition ((items.size > 1) || (nextItems != null)) for displaying the InputConsumingOverlay is a valuable improvement. It ensures that user input is properly managed during both ongoing animations (when items.size > 1) and pending animations (when nextItems is not null). This change aligns well with the PR objectives and should prevent unexpected behavior during rapid component transitions.


83-83: Simplified and improved getAnimationItems condition.

The modification to the condition in the getAnimationItems function is a good improvement. By checking for (oldStack == null) || (newStack.active.key == oldStack.active.key), it correctly handles both initial animations and cases where the active key remains the same but the instance changes. This change supports the PR's goal of ensuring proper animation behavior during component updates and replacements.


Line range hint 1-114: Overall, excellent improvements to stack animation handling.

The changes made to AbstractStackAnimation.kt effectively address the issues outlined in the PR objectives. Key improvements include:

  1. Introduction of nextItems for better management of upcoming animation states.
  2. Enhanced condition for updating currentStack, ensuring proper handling of new child component instances.
  3. Addition of a DisposableEffect for clean transitions between animation states.
  4. Improved InputConsumingOverlay condition for better user interaction management during animations.
  5. Simplified logic in getAnimationItems for more robust animation item generation.

These changes collectively provide a more robust and flexible stack animation system, particularly for scenarios involving rapid component replacements and nested structures. The modifications should resolve the reported issues with animation behavior and improve overall user experience.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (4)

52-52: Approve the addition of nextItems variable

The introduction of the nextItems variable is a good solution to manage upcoming animation items during stack transitions. This addresses the issue described in #792 where quick replacements led to retaining references to old component instances. By temporarily storing the next set of items, the animation system can now handle rapid transitions more effectively.


62-64: Approve the refined condition for updating items

The updated condition for updating items is more comprehensive and precise. By checking both the key and the instance of the active child, it ensures that the animation system correctly handles cases where the active configuration remains unchanged but the child component instance is new. This directly addresses the core issue described in the PR, where the stack animation was not updating to new child component instances in certain scenarios.


95-102: Approve the addition of DisposableEffect for managing animation item lifecycle

The introduction of the DisposableEffect is a crucial improvement in managing the lifecycle of animation items. By ensuring that nextItems are correctly assigned to items after the exit animation completes, it addresses the issue of component instances not being refreshed correctly during quick transitions. This change is key to solving the problem described in #792, where rapid replacements led to unexpected behavior in nested component structures.

The null check on nextItems is a good practice, preventing unnecessary assignments and potential issues. This implementation ensures a smooth transition between animation states, even in scenarios of quick component replacements.


Line range hint 1-379: Overall assessment: Effective solution to the stack animation issue

The changes implemented in this PR effectively address the core issue described in #792, where stack animations were not updating to new child component instances when the active configuration remained unchanged. The introduction of nextItems, refined update conditions, and improved lifecycle management through DisposableEffect collectively provide a robust solution for handling rapid transitions in nested component structures.

These improvements should resolve the strange behavior observed during quick replacements between components like MainComponent and WelcomeComponent. The enhanced logic ensures that new instances of child components (e.g., InnerComponent) are correctly instantiated and displayed, even during rapid transitions.

The code quality is good, with only minor suggestions for improving readability. These changes align well with the PR objectives and should significantly improve the reliability and consistency of stack animations in complex UI scenarios.

extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (4)

92-103: Well-Implemented Test for Child Instance Replacement

The new test GIVEN_child_displayed_WHEN_new_child_instance_with_the_same_key_THEN_new_child_instance_displayed() correctly verifies that a new child instance with the same key replaces the old instance. The use of assertSame ensures that the new instance is properly displayed. This test enhances the reliability of component updates when instances change but keys remain the same.


198-205: Updated setContent Method Signature and Usage

The setContent method now accepts State<ChildStack<Config, Any>> and a content lambda. This change aligns with the updated instance type and provides greater flexibility in rendering child components. Ensure that all calls to setContent are updated accordingly and that the default content parameter functions as expected.


226-231: Addition of Overloaded routerState Function

The new routerState function accepts a variable number of Child.Created<Config, Any> parameters, allowing the creation of a ChildStack with diverse instance types. This addition improves test setup flexibility. Verify that this does not conflict with existing routerState methods and that it is used appropriately throughout the tests.


257-257: Consistent Type Parameters in getParameters Method

The getParameters() method now returns a List<StackAnimation<Any, Any>?>, which maintains consistency with the other changes to use Any as the instance type. This update ensures that the stack animations tested can handle any instance type, increasing the robustness of the tests.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (3)

97-107: Test Function Correctly Verifies Child Instance Replacement

The new test GIVEN_child_displayed_WHEN_new_child_instance_with_the_same_key_THEN_new_child_instance_displayed accurately checks that a new child instance with the same key replaces the previous instance in the UI. This ensures that the stack correctly updates to reflect the new instance, aligning with the PR's objective to fix the animation stack issue.


203-210: Enhanced Flexibility with Custom Content in setContent Function

The modification of the setContent function to accept a content parameter allows for more customizable UI components during testing. This enhancement improves the test suite's flexibility by enabling different content representations for child instances.


231-236: Overloaded routerState Function Improves Test Setup

The addition of the overloaded routerState function that accepts Child.Created<Config, Any> instances simplifies the creation of custom child stacks with specific instances and keys. This change enhances the ease of setting up test scenarios that require precise control over the stack's state.

@arkivanov arkivanov force-pushed the fix-stack-animations-2 branch from 675ba35 to 51a1cc3 Compare October 15, 2024 13:59
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (8)
extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (1)

35-44: LGTM: Improved handling of stack updates and animation items.

The changes effectively address the issue of the stack not updating to new child component instances when the active configuration is unchanged. The condition now checks both the key and instance of the active stack item, ensuring proper updates.

The refined handling of items and nextItems based on the current state is a good approach to manage animations smoothly.

Consider extracting the condition stack.active.key != currentStack.active.key || stack.active.instance != currentStack.active.instance into a separate function for better readability and reusability. For example:

private fun isStackChanged(newStack: ChildStack<C, T>, currentStack: ChildStack<C, T>): Boolean =
    newStack.active.key != currentStack.active.key || newStack.active.instance != currentStack.active.instance

Then use it in the condition:

if (isStackChanged(stack, currentStack)) {
    // ... existing code ...
}
extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (1)

92-102: LGTM: New test method addresses the core issue.

The new test method effectively verifies that a new child instance with the same key is displayed correctly, which aligns with the PR objectives. The test is well-structured and follows the Given-When-Then pattern.

Consider adding a brief comment explaining the purpose of this test, as it directly relates to the issue being fixed in this PR.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (3)

203-210: Approve changes to setContent with a suggestion

The addition of the content parameter enhances the flexibility of the test setup. However, consider using a generic type instead of Any to maintain type safety:

private fun <Instance> setContent(
    state: State<ChildStack<Config, Instance>>,
    content: @Composable AnimatedVisibilityScope.(Child.Created<Config, Instance>) -> Unit = {
        Child(name = it.key.toString())
    },
)

This change would be consistent with the earlier suggestion to parameterize the class.


231-236: Approve new routerState overload with a suggestion

The new overload enhances the flexibility of creating router states for testing. However, to maintain type safety, consider using a generic type:

private fun <Instance> routerState(vararg stack: Child.Created<Config, Instance>): ChildStack<Config, Instance> =
    ChildStack(
        active = stack.last(),
        backStack = stack.dropLast(1),
    )

This change would be consistent with the earlier suggestions to use generics throughout the class.


Line range hint 1-300: Summary of ChildStackTest.kt changes

The modifications to ChildStackTest.kt enhance the flexibility of the test class by allowing for more diverse instance types. However, the consistent use of Any as a type parameter introduces potential type safety issues.

The main recommendation is to parameterize the ChildStackTest class and use generics throughout instead of Any. This approach would maintain the desired flexibility while preserving type safety. Consider applying this change consistently across the class, including the constructor, methods, and parameter types.

These changes align well with the PR objectives of improving stack animation handling and ensuring correct updates when replacing components. The new test method effectively covers an important edge case in stack management.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3)

71-76: Approved: Enhanced item update logic

The new logic for updating items based on their current size is a smart approach to manage transitions more smoothly, especially for quick replacements. The use of nextItems for multiple items allows for a staged update, which can prevent visual glitches during complex transitions.

Consider extracting the condition items.size == 1 into a meaningful variable name to improve readability. For example:

val isSingleItem = items.size == 1
if (isSingleItem) {
    items = newItems
} else {
    nextItems = newItems
}

95-102: Approved: DisposableEffect for managing exiting items

The addition of this DisposableEffect is a crucial improvement. It ensures that nextItems are correctly reassigned to items after the exit animation completes, maintaining the proper state of animations during complex transitions.

For improved clarity, consider adding a brief comment explaining the purpose of this DisposableEffect. For example:

// Ensure nextItems are assigned to items after exit animation
DisposableEffect(Unit) {
    onDispose {
        nextItems?.also { items = it }
        nextItems = null
    }
}

108-108: Approved: Enhanced condition for disabling user input

The updated condition for disabling user input during animation is a good improvement. By including a check for nextItems, it ensures that user input remains disabled during complex transitions managed by nextItems, preventing potential unexpected behavior.

Consider extracting the condition into a separate function for improved readability:

private fun shouldDisableInput(): Boolean =
    disableInputDuringAnimation && ((items.size > 1) || (nextItems != null))

// Usage
if (shouldDisableInput()) {
    Overlay(modifier = Modifier.matchParentSize())
}

This refactoring would make the code more self-documenting and easier to maintain.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 675ba35 and 51a1cc3.

📒 Files selected for processing (9)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt (1 hunks)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (2 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (6 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (1 hunks)
  • extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (3 hunks)
  • extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (1 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (5 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt
  • extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt
🧰 Additional context used
🔇 Additional comments (16)
extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (6)

5-5: LGTM: Import statement addition is appropriate.

The addition of the DisposableEffect import is consistent with its usage in the new functionality introduced later in the file.


33-33: LGTM: New variable nextItems enhances animation state management.

The introduction of nextItems as a nullable Map<Any, AnimationItem<C, T>> using mutableStateOf is a good addition. It allows for better handling of upcoming animation items, which aligns with the PR's objective to improve stack animation behavior.


62-69: LGTM: DisposableEffect ensures proper cleanup of animation items.

The addition of the DisposableEffect is a good implementation to handle the cleanup of exiting items. It ensures that items are updated with nextItems when an item is disposed, which is crucial for proper management of animation items during transitions. This change directly addresses the PR's objective to fix issues with the animation stack updating.


75-75: LGTM: Improved InputConsumingOverlay condition enhances animation behavior.

The modified condition for displaying the InputConsumingOverlay now accounts for both multiple items and the presence of nextItems. This change ensures that user input is properly managed during animations, even when transitioning to new items. It aligns well with the PR's objective to improve animation behavior during quick replacements.


83-83: LGTM: Simplified and improved getAnimationItems logic.

The modification to the getAnimationItems function's condition is a good improvement. It now handles cases where the old stack is null or the active keys of the new and old stacks match, which simplifies the logic and improves the handling of initial states and unchanged configurations. This change aligns well with the PR's objective to fix issues with stack animation updating.


Line range hint 1-114: Overall assessment: Excellent implementation addressing the PR objectives.

The changes in this file effectively address the issues described in the PR objectives, particularly the problem of the animation stack not updating to new child component instances when the active configuration is unchanged. The introduction of nextItems, refinement of update conditions, and improved handling of animation items all contribute to solving the reported issues.

The code quality is high, with clear logic and appropriate use of Compose concepts. The suggestions made are minor and aimed at further improving readability and maintainability.

Great job on implementing these improvements!

extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (6)

29-29: LGTM: Import statement added correctly.

The import of kotlin.test.assertSame is necessary for the new test method and is correctly placed.


34-34: LGTM: Constructor parameter type updated correctly.

The change from StackAnimation<Config, Config>? to StackAnimation<Config, Any>? aligns with the PR objectives and allows for more flexibility in handling different types of child instances.


198-203: LGTM: setContent method signature updated correctly.

The changes to the setContent method signature align with the PR objectives and the constructor parameter type change. The addition of the content parameter with a default value increases flexibility while maintaining backward compatibility.


226-230: LGTM: New routerState method added correctly.

The new routerState method aligns with the type changes made throughout the class and provides a convenient way to create a ChildStack with the updated type parameters. The implementation correctly sets the last item as the active child and the rest as the back stack.


257-257: LGTM: getParameters method return type updated correctly.

The change in the return type from List<StackAnimation<Config, Config>?> to List<StackAnimation<Config, Any>?> is consistent with the type changes made throughout the class and ensures type consistency across the test class.


Line range hint 1-277: Overall assessment: Changes effectively address the PR objectives.

The modifications in this file successfully address the issue of stack animation not updating to new child component instances. The changes include:

  1. Updating type parameters from Config to Any for greater flexibility.
  2. Adding a new test method to verify correct handling of new child instances with the same key.
  3. Updating method signatures and return types to maintain consistency with the new type parameters.

These changes align well with the PR objectives and should resolve the reported issue while maintaining backward compatibility and increasing the flexibility of the ChildrenTest class.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (1)

97-107: Excellent addition of edge case test

This new test method effectively verifies that the stack updates correctly when a new child instance with the same key is pushed. It aligns well with the PR objectives and helps ensure proper component replacement behavior.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3)

52-52: LGTM: Introduction of nextItems variable

The addition of the nextItems variable is a good approach to manage upcoming animation items during stack transitions. This aligns well with the PR objective of fixing the stack animation update issue, particularly when quickly replacing components in a nested structure.


62-64: Crucial fix: Improved condition for updating items

This change is critical to addressing the core issue described in the PR. By checking not only the active key but also the instance of the active child, the code now ensures that the animation updates correctly even when only the instance changes. This directly fixes the problem where InnerComponent wasn't refreshing properly after quick transitions between MainComponent and WelcomeComponent.


Line range hint 1-380: Overall assessment: Excellent improvements to stack animation handling

The changes in this file effectively address the reported issue of stack animation not updating for new child component instances when the active configuration remains unchanged. The introduction of nextItems, refined update logic, and improved handling of transitions and user input collectively contribute to a more robust and reliable animation system.

The modifications align well with the PR objectives and should resolve the strange behavior observed during quick component replacements. The code quality is good, with only minor suggestions for improvement in terms of readability and organization.

Great job on implementing these crucial fixes and enhancements!

@arkivanov arkivanov force-pushed the fix-stack-animations-2 branch from 51a1cc3 to 92472d3 Compare October 15, 2024 17:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 51a1cc3 and 92472d3.

📒 Files selected for processing (9)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt (1 hunks)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (2 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (6 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (1 hunks)
  • extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (3 hunks)
  • extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (1 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (5 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt
  • extensions-compose/src/commonTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt
🧰 Additional context used
🔇 Additional comments (19)
extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (6)

5-5: LGTM: Import statement addition is appropriate.

The addition of the DisposableEffect import is consistent with its usage in the new functionality introduced later in the file.


33-33: LGTM: Addition of nextItems variable enhances animation handling.

The introduction of the nextItems variable is a key component in addressing the animation stack issue described in the PR objectives. It provides a mechanism to queue up the next set of animation items, allowing for smoother transitions between component states.


35-44: Excellent: Updated logic addresses core PR objective.

The changes in this section directly address the main issue described in the PR objectives:

  1. The condition stack.active.key != currentStack.active.key || stack.active.instance != currentStack.active.instance ensures that the stack is updated when either the key or the instance changes. This fixes the problem of components not refreshing correctly when only the instance changes.

  2. The new logic for handling items and nextItems provides a more robust way of managing the animation queue, allowing for smoother transitions between component states.

These changes are crucial in fixing the animation stack issue and ensuring that new child component instances are correctly updated and displayed.


62-69: LGTM: DisposableEffect addition improves animation item lifecycle management.

The introduction of the DisposableEffect is a crucial enhancement:

  1. It ensures proper cleanup of exiting animation items.
  2. It manages the transition to the next set of items by assigning nextItems to items upon disposal.
  3. This mechanism contributes to smoother animations and more predictable component lifecycle management.

This addition aligns well with the PR's objective of improving the animation stack behavior during quick component replacements.


75-75: LGTM: Improved InputConsumingOverlay condition enhances animation robustness.

The updated condition (items.size > 1) || (nextItems != null) for displaying the InputConsumingOverlay is a valuable improvement:

  1. It ensures that user input is properly managed during animations, including scenarios with pending next items.
  2. This change prevents unintended interactions during component transitions, contributing to a more stable and predictable user experience.
  3. The modification aligns well with the PR's objective of enhancing animation behavior during quick component replacements.

This improvement adds an extra layer of reliability to the animation process, particularly in scenarios involving rapid state changes.


Line range hint 1-114: Summary: Changes effectively address PR objectives and enhance animation handling.

The modifications in this file collectively solve the core issues described in the PR objectives and linked issue #792:

  1. The new nextItems variable and associated logic ensure proper handling of quick component replacements.
  2. The updated condition for refreshing the stack addresses the problem of components not updating when only the instance changes.
  3. The DisposableEffect addition improves the lifecycle management of animation items.
  4. The enhanced InputConsumingOverlay condition prevents unintended user interactions during animations.

These changes significantly improve the robustness and predictability of the animation system, particularly in scenarios involving rapid state changes and nested component structures. The implementation aligns well with the goal of fixing the strange behavior observed during quick replacements between MainComponent and WelcomeComponent.

Great job on addressing these complex animation issues!

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (2)

97-107: Well-structured test for child instance update

This new test method effectively verifies that when a new child instance with the same key is pushed onto the stack, the new instance is correctly displayed. The test follows good practices:

  1. Clear naming using the Given-When-Then pattern
  2. Proper setup of the initial state
  3. Correct simulation of updating the child instance
  4. Appropriate use of assertSame for instance equality check

Good job on adding this test to cover an important edge case in the stack animation behavior.


Line range hint 1-307: Summary: Consider using generics for improved type safety

The changes in this file introduce more flexibility by using Any as the instance type in various methods and parameters. While this allows for diverse test scenarios, it may lead to potential type safety issues. The main recommendation is to consider using a generic type parameter throughout the ChildStackTest class and its methods. This approach would maintain the desired flexibility while ensuring type safety.

The new test method GIVEN_child_displayed_WHEN_new_child_instance_with_the_same_key_THEN_new_child_instance_displayed is a valuable addition, effectively covering an important edge case in stack animation behavior.

By implementing the suggested generic approach, you can achieve a good balance between flexibility and type safety in your test suite.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (5)

25-72: Well-structured test for animation behavior

The test method WHEN_animating_push_and_stack_popped_during_animation_THEN_animated_push_and_pop_fully is comprehensive and effectively verifies the push and pop animations when the stack is manipulated during an ongoing animation. The use of value tracking and assertions ensures the animation states are correctly validated.


74-104: Effective test for child restart upon stack manipulation

The test method WHEN_animating_push_and_stack_popped_during_animation_THEN_first_child_restarted accurately checks that the first child is restarted when the stack is popped during an animation. The incrementing counter within the LaunchedEffect and the final assertion provide a clear validation of the expected behavior.


106-111: Efficient time advancement utility

The helper function advanceFramesBy provides an efficient way to simulate the passage of time within the tests. This utility enhances readability and reusability within the test suite.


113-114: Clear child creation helper function

The child function simplifies the creation of Child.Created instances for testing purposes. Using Any() as the instance is appropriate in this context and keeps the tests focused on the stack behavior.


116-120: Concise stack construction function

The stack function provides a clear and concise way to construct ChildStack instances from a variable number of configurations. This enhances the flexibility and readability of the test setups.

extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (6)

29-29: Importing assertSame for Instance Comparison

The import kotlin.test.assertSame statement is appropriate and necessary for asserting instance equality in the new test.


34-34: Consistent Update of Type Parameters for StackAnimation

The type parameter has been correctly updated to StackAnimation<Config, Any>?, ensuring consistency across the class and accommodating any child instance types.


92-102: Addition of Test for New Child Instance with Same Key

The new test method effectively verifies that a new child instance with the same key replaces the previous instance as expected. The use of assertSame confirms that the displayed instance is updated correctly.


198-205: Enhanced Flexibility in setContent Method

Updating the setContent method to accept State<ChildStack<Config, Any>> and a content lambda with Child.Created<Config, Any> improves the flexibility and reusability of the test setup.


226-231: Overloaded routerState Function for Generic Child Instances

The new routerState function overload facilitates the creation of ChildStack<Config, Any> with generic child instances, enhancing the versatility of the test configurations.


257-257: Updated Type Parameters in getParameters Method

The return type of getParameters has been updated to List<StackAnimation<Config, Any>?>, ensuring alignment with the updated type parameters and maintaining type safety.

@arkivanov arkivanov force-pushed the fix-stack-animations-2 branch from 92472d3 to 7dd61ec Compare October 15, 2024 18:47
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (9)
extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (3)

3-6: LGTM! Consider adding KDoc for better documentation.

The takeSorted function is well-implemented and efficiently returns a new list with elements in non-decreasing order according to the provided comparator. The logic is correct and the function name accurately describes its behavior.

Consider adding KDoc to describe the function's purpose and parameters for better documentation:

/**
 * Returns a new list containing elements from the original list,
 * starting from the first element and continuing as long as the elements
 * are in non-decreasing order according to the provided comparator.
 *
 * @param comparator The comparator used to determine the order of elements.
 * @return A new list with elements in non-decreasing order.
 */
internal fun <T> List<T>.takeSorted(comparator: Comparator<T>): List<T> = ...

8-11: LGTM! Consider adding KDoc for better documentation.

The takeWhileIndexed function is well-implemented and efficiently returns a list of elements that satisfy the given predicate, which has access to both the index and the item. The logic is correct and the function name accurately describes its behavior.

Consider adding KDoc to describe the function's purpose and parameters for better documentation:

/**
 * Returns a list containing elements from the original iterable
 * as long as the given predicate returns true for each element and its index.
 *
 * @param predicate A function that takes the index and item as parameters and returns a boolean.
 * @return A list containing elements that satisfy the predicate.
 */
internal fun <T> Iterable<T>.takeWhileIndexed(predicate: (Int, T) -> Boolean): List<T> = ...

1-11: Well-implemented utility functions for testing purposes.

This file introduces two well-designed and efficient utility functions, takeSorted and takeWhileIndexed, which are likely to be useful in various testing scenarios. The functions are appropriately marked as internal and placed in the test directory, indicating good separation of test utilities from the main library code.

Consider creating a dedicated TestUtils.kt file if more test-specific utility functions are added in the future, to keep all test utilities organized in one place.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (3)

19-26: LGTM! Consider adding a comment for the default value.

The changes to animateFloat function look good. The addition of the durationMillis parameter provides more flexibility for testing different animation durations while maintaining backward compatibility with the default value.

Consider adding a brief comment explaining the significance of AnimationConstants.DefaultDurationMillis for better code documentation:

internal fun Transition<EnterExitState>.animateFloat(
    // Default duration as defined in AnimationConstants
    durationMillis: Int = AnimationConstants.DefaultDurationMillis
): State<Float> = 
    // ... rest of the function

28-31: LGTM! Consider adding a brief function comment.

The takeSorted function is a useful addition that efficiently creates a sorted subset of a list. The implementation is concise and correct.

Consider adding a brief function comment to explain its behavior:

/**
 * Returns a list containing the first elements satisfying the given [comparator] in ascending order.
 * Stops taking elements when the order defined by the comparator is broken.
 */
internal fun <T> List<T>.takeSorted(comparator: Comparator<T>): List<T> =
    // ... rest of the function

33-36: LGTM! Consider adding a brief function comment.

The takeWhileIndexed function is a well-implemented utility that combines indexing and filtering operations efficiently. It's good to see it being used in the takeSorted function, demonstrating effective code reuse.

Consider adding a brief function comment to explain its behavior:

/**
 * Returns a list containing first elements satisfying the given [predicate] applied to the element and its index.
 * The operation is intermediate and stateless.
 */
internal fun <T> Iterable<T>.takeWhileIndexed(predicate: (Int, T) -> Boolean): List<T> =
    // ... rest of the function
extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (2)

33-33: LGTM: New variable nextItems added

The addition of the nextItems variable is a good approach to manage upcoming animation items. This change appears to address the issue with stack animation not updating to new child component instances.

Consider adding a brief comment explaining the purpose of nextItems to improve code readability.


35-44: LGTM: Improved logic for handling stack updates and animations

The changes in this section effectively address the issue of not updating when only the instance changes. The new logic for handling items and nextItems provides a smoother transition between animation states.

Consider extracting the condition stack.active.key != currentStack.active.key || stack.active.instance != currentStack.active.instance into a separate function for better readability, e.g., hasStackChanged(stack, currentStack).

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (1)

203-210: LGTM: Improved flexibility in setContent method

The updated setContent method signature with the new content parameter enhances the flexibility of content rendering in tests. This change aligns well with the constructor parameter update and allows for more diverse testing scenarios.

However, consider adding a comment explaining the purpose of the content parameter to improve code readability:

/**
 * @param content A composable function to render the child content. 
 *                Defaults to rendering a basic text with the child's key.
 */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 92472d3 and 7dd61ec.

📒 Files selected for processing (8)
  • extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (3 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (2 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (6 hunks)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt (1 hunks)
  • extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (3 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/Utils.kt (1 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt (5 hunks)
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimationTest.kt
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/ChildrenTest.kt
  • extensions-compose/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/SimpleStackAnimationTest.kt
🧰 Additional context used
🔇 Additional comments (16)
extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/TestUtils.kt (1)

Line range hint 1-37: Overall, excellent changes that enhance testing capabilities.

The modifications to TestUtils.kt are well-implemented and align with the PR objectives. The updated animateFloat function provides more flexibility for animation testing, while the new takeSorted and takeWhileIndexed functions offer useful utilities for handling collections in tests. These changes should contribute to more robust and flexible testing of stack animations.

extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation/AbstractStackAnimation.kt (4)

5-5: LGTM: Import statement added for DisposableEffect

The addition of the import statement for DisposableEffect is appropriate and consistent with its usage in the file.


62-69: LGTM: DisposableEffect added for proper handling of nextItems

The addition of the DisposableEffect is a good approach to ensure that nextItems are properly handled when an item exits the composition. This change contributes to the smooth transition between animation states and is consistent with Compose best practices for side effects.


75-75: LGTM: Improved condition for InputConsumingOverlay

The modification to the condition for displaying the InputConsumingOverlay is appropriate. It now correctly accounts for the presence of nextItems, ensuring that input is consumed during animations when either there are multiple items or when there are upcoming items to be animated.


Line range hint 1-114: Overall assessment: Effective solution for stack animation issues

The changes introduced in this file successfully address the issue described in the PR objectives. The new approach with nextItems, improved condition checking, and the addition of the DisposableEffect provide a more robust way to manage animations during quick replacements of child components.

Key improvements:

  1. The condition for updating currentStack now checks both the key and instance, addressing cases where only the instance changes.
  2. The introduction of nextItems allows for queuing up the next animation while the current one is still in progress.
  3. The DisposableEffect ensures proper handling of nextItems when items exit the composition.
  4. The modified condition for InputConsumingOverlay accounts for both current and upcoming animation items.

These changes collectively contribute to resolving the strange behavior observed during rapid component replacements, ensuring that new instances of child components are correctly instantiated and displayed.

extensions-compose-experimental/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackTest.kt (5)

3-3: LGTM: Import and constructor parameter updates

The addition of the assertSame import and the change in the constructor parameter type to StackAnimation<Config, Any>? improve the flexibility of the test class. These changes align well with the PR objectives by allowing for more diverse instance types in the animation, which is crucial for testing the stack animation updates.

Also applies to: 33-33, 39-39


97-107: Excellent addition: New test for child instance updates

This new test method directly addresses the core issue described in the PR objectives. It verifies that when a new child instance with the same key is pushed onto the stack, the new instance is correctly displayed. This is crucial for ensuring that the stack animation properly updates to reflect new child component instances, even when the active configuration remains unchanged.


231-236: LGTM: New routerState overload for flexible instance types

The addition of this new routerState overload is a good improvement. It allows for creating router states with more flexible instance types, which is consistent with the other changes in the class. This addition enhances the testing capabilities of the ChildStackTest class and supports the PR objectives by allowing for more diverse testing scenarios.


261-261: LGTM: Updated getParameters return type

The change in the return type of getParameters to List<StackAnimation<Config, Any>?> is consistent with the other updates in the class. This modification ensures that the test parameters align with the new flexible instance type approach, supporting more comprehensive testing scenarios.


Line range hint 1-301: Overall: Excellent improvements to ChildStackTest

The changes made to ChildStackTest effectively address the PR objectives and enhance the class's testing capabilities. Key improvements include:

  1. More flexible instance types throughout the class, allowing for diverse testing scenarios.
  2. A new test method that directly verifies the correct updating of child component instances.
  3. Enhanced flexibility in content rendering for tests.
  4. Consistent updates across related methods to support the new approach.

These modifications significantly improve the class's ability to test stack animations, particularly in scenarios where child component instances change while configurations remain the same. The changes are well-structured, maintain consistency, and align perfectly with the PR objectives.

extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/DefaultStackAnimation.kt (6)

52-52: LGTM: Introduction of nextItems variable

The addition of the nextItems variable is a good approach to manage upcoming animation items during stack transitions. This aligns well with the PR objective of fixing the stack animation update issue, particularly when quickly replacing components in a nested structure.


56-76: LGTM: Improved logic for updating animation items

The changes in this segment significantly improve the handling of stack transitions, particularly addressing the issue where the animation stack wasn't updating to new child component instances when the active configuration remained unchanged. The new logic ensures that items are updated not just when keys change, but also when instances change, which is crucial for resolving the reported issue.

The differentiation between single and multiple items in the update logic adds necessary complexity to handle various scenarios correctly, including the case of quickly replacing components in a nested structure.


94-102: LGTM: Addition of DisposableEffect for managing nextItems

The introduction of this DisposableEffect is a crucial addition that ensures proper management of nextItems after the exit animation completes. This change directly addresses the issue of components not refreshing correctly after quick transitions, as described in the PR objectives.

By reassigning nextItems to items and clearing nextItems on disposal, this effect helps maintain the correct state of the animation stack, even in scenarios involving rapid component replacements.


108-108: LGTM: Enhanced condition for disabling user input during animations

The modification to include a check for nextItems in the condition for disabling user input during animations is a valuable improvement. This change ensures that user interactions are properly managed not only when multiple items are animating but also when there are pending items (nextItems).

This enhancement contributes to a more robust handling of user input during quick transitions, aligning well with the overall goal of improving the animation behavior in such scenarios.


150-150: LGTM: Refined condition for creating animation items

The modification to the condition in the getAnimationItems function is a key improvement. By checking if the new stack's active key is the same as the old stack's active key, this change ensures that new animation items are created in the appropriate scenarios.

This refinement directly addresses the core issue of the animation stack not updating when the active configuration remains unchanged but the instance changes. It contributes significantly to solving the problem of components not refreshing correctly after quick transitions, as outlined in the PR objectives.


Line range hint 1-385: Overall assessment: Effective solution to the stack animation issue

The changes implemented in this file collectively provide an effective solution to the issue described in the PR objectives. The modifications address the core problem of the animation stack not updating to new child component instances when the active configuration remains unchanged, particularly during quick transitions between components.

Key improvements include:

  1. Introduction of nextItems for better management of upcoming animation items.
  2. Refined logic for updating items during transitions.
  3. Addition of a DisposableEffect to ensure proper handling of nextItems after exit animations.
  4. Enhanced control over user input during animations.
  5. Improved condition for creating animation items.

These changes significantly enhance the robustness and correctness of the stack animation behavior, effectively resolving the strange behavior reported in the linked issue #792.

@arkivanov arkivanov merged commit 1c67cb6 into master Oct 15, 2024
3 checks passed
@arkivanov arkivanov deleted the fix-stack-animations-2 branch October 15, 2024 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Strange behavior when quick replace is made
1 participant