Skip to content

[Predictive Back] Enhance in-app back animations#5026

Closed
sztomek wants to merge 13 commits intomainfrom
feat/predictive-back-enhancements
Closed

[Predictive Back] Enhance in-app back animations#5026
sztomek wants to merge 13 commits intomainfrom
feat/predictive-back-enhancements

Conversation

@sztomek
Copy link
Copy Markdown
Contributor

@sztomek sztomek commented Feb 23, 2026

Description

This PR adds the same animations that the system uses for predictive back. I did a thorough research on this topic and it turned out that we can't rely the system's predictive back animation when navigating back from our screens, because:

  • Android's system animations work on window/activity transitions (the system compositor handles cross-activity animations)
  • Fragment transitions happen within the same window - Android can't peek at "the previous activity" because there isn't one
  • The app must manually make Fragment B visible and animate both fragments in coordination.
  • BottomNavigator maintains 5 separate fragment stacks
  • MainActivity handles 7 different back scenarios with different animations:
    -- Fragment navigation → Scale + fade with peek
    -- Player bottom sheet → Scale to 80% then collapse
    -- Bottom sheets (UpNext) → System collapse animation (can use default)
    -- Modals → Custom slide animations
    -- Selection mode → No animation, just exit mode
    So the only viable way forward was to create custom transitions and hook them up on the appropriate places.
    I noticed a nasty bug that took a lot of time to get rid of. It was specific to the Profile>Bookmarks screen. I had to
    split it into two (BookmarksDialogFragment for Player, BookmarksNavigationFragment for Profile).
    Why? The original BookmarksContainerFragment was trying to be both a bottom sheet dialog (modal context) and a navigation fragment (backstack context). This caused navigation confusion - the back gesture didn't know which behavior to use. Separating them made each context explicit and predictable.

Fixes PCDROID-462

Testing Instructions

Smoke test the app, pay attention to the back navigation.

Screenshots or Screencast

Screen_recording_20260223_111918.mp4

Checklist

  • If this is a user-facing change, I have added an entry in CHANGELOG.md
  • Ensure the linter passes (./gradlew spotlessApply to automatically apply formatting/linting)
  • I have considered whether it makes sense to add tests for my changes
  • All strings that need to be localized are in modules/services/localization/src/main/res/values/strings.xml
  • Any jetpack compose components I added or changed are covered by compose previews
  • I have updated (or requested that someone edit) the spreadsheet to reflect any new or changed analytics.

@sztomek sztomek added this to the 8.7 milestone Feb 23, 2026
@sztomek sztomek requested a review from a team as a code owner February 23, 2026 19:46
@sztomek sztomek requested review from MiSikora and Copilot and removed request for a team February 23, 2026 19:46
@sztomek sztomek added [Type] Enhancement Improve an existing feature. [Area] Navigation labels Feb 23, 2026
@dangermattic
Copy link
Copy Markdown
Collaborator

1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements predictive back gesture animations throughout the Pocket Casts Android app to provide smooth Material Design-compliant navigation experiences. The implementation includes custom transition helpers, comprehensive testing, and integration across fragments, activities, and navigation systems.

Changes:

  • Added new utility classes (PredictiveBackAnimator, PredictiveBackTransition, ComposeNavigationBackHelper, ActivityBackHelper) to handle predictive back animations with scale and fade effects
  • Integrated predictive back animation support into BaseFragment and BaseDialogFragment with customizable behavior via hooks
  • Split BookmarksContainerFragment into two specialized fragments (BookmarksDialogFragment for Player modal context, BookmarksNavigationFragment for Profile navigation context) to resolve navigation conflicts
  • Enhanced BottomNavigator with methods to support fragment peek animations during back gestures
  • Added comprehensive unit tests for all new helper classes

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated no comments.

Show a summary per file
File Description
PredictiveBackAnimator.kt New utility providing scale/fade animation methods for back gestures with hardware layer optimization
PredictiveBackTransition.kt New transition manager for seekable AndroidX transitions during predictive back
PredictiveBackExtensions.kt Extension functions for Compose navigation back handling
ComposeNavigationBackHelper.kt Helper object for Compose navigation backstack management
ActivityBackHelper.kt Helper for setting up predictive back on activities with various configurations
BaseFragment.kt Added default predictive back animation support with customizable hooks
BaseDialogFragment.kt Added predictive back animation for bottom sheet dialogs
MainActivity.kt Integrated predictive back for bottom navigator and player sheet with coordinated animations
BottomNavigator.kt Added previousFragment() and animation helper methods
StackOfStacks.kt Added peekValueBelowTop() for fragment peek support
BookmarksDialogFragment.kt Refactored from BookmarksContainerFragment for modal use case
BookmarksNavigationFragment.kt New fragment for navigation context from Profile
AccountActivity.kt Added PredictiveBackTransition usage
WebViewActivity.kt Added predictive back animation
StoriesActivity.kt Added Compose-based predictive back handling
PodcastFragment.kt Calls notifyBackstackChanged() when entering/exiting multi-select mode
Test files Comprehensive unit tests for all new helper classes
Build files Added androidx.transition:transition-ktx dependency

@wpmobilebot
Copy link
Copy Markdown
Collaborator

Project dependencies changes

list
+ New Dependencies
androidx.transition:transition-ktx:1.6.0
tree
++--- com.automattic.tracks:crashlogging:6.0.7
+|    \--- io.sentry:sentry-compose-android -> 8.18.0
+|         \--- androidx.compose.material3:material3:1.2.1 -> 1.4.0
+|              \--- androidx.compose.material3:material3-android:1.4.0
+|                   \--- androidx.activity:activity-compose:1.8.2 -> 1.12.4
+|                        \--- androidx.compose.ui:ui:1.0.1 -> 1.10.0
+|                             \--- androidx.compose.ui:ui-android:1.10.0
+|                                  \--- androidx.transition:transition:1.6.0
+|                                       \--- androidx.transition:transition-ktx:1.6.0 (c)
++--- project :modules:features:account
+|    +--- project :modules:features:search
+|    |    \--- project :modules:services:views
+|    |         \--- androidx.transition:transition-ktx:1.5.1 -> 1.6.0
+|    |              +--- androidx.transition:transition:1.6.0 (*)
+|    |              +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.3.10 (*)
+|    |              +--- androidx.transition:transition:1.6.0 (c)
+|    |              \--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.3.10 (c)
+|    \--- androidx.transition:transition-ktx:1.5.1 -> 1.6.0 (*)
+\--- project :modules:features:navigation
+     +--- androidx.compose:compose-bom:2025.12.01 (*)
+     \--- project :modules:services:views (*)

Copy link
Copy Markdown
Contributor

@MiSikora MiSikora left a comment

Choose a reason for hiding this comment

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

I only tested it for now. I'll do the code review later. I found a couple of things that I think don't work well:

  1. The predictive animation for the player sheet appears a bit janky. Notice how it jumps and loses the fade transition especially at the top of the screen. It may be better to remove it in this context.
Before After
before.mp4
after.mp4
  1. The initial scaling of the previous fragment on the back stack feels visually abrupt.
screen-20260223-220909-1771880933121.mp4
  1. Predictive back doesn't work for me during the onboarding flow. I don't think it is necessary to add it there, especially given how convoluted the code is. I'm highlighting it just in case.

I also found two bugs (likely the same underlying issue occurring in two places) that aren't related to this change, but probably to earlier predictive back integration work. When opening both Referrals and Up Next from the mini player, back navigation is not handled correctly and instead closes the app. We should fix this in a separate PR before it reaches beta.

screen-20260223-220618-1771880761173.mp4

@CookieyedCodes
Copy link
Copy Markdown

If this is implemented I wouldn't be against a setting for fast, medium, long animations & no animations, some people like an instant response

@sztomek
Copy link
Copy Markdown
Contributor Author

sztomek commented Feb 24, 2026

thanks @MiSikora, i've ticketed the bug you discoered here https://linear.app/a8c/issue/PCDROID-466/bug-broken-back-nav as it doesn't seem to be an easy/obvious one. I already sinked some time into it

@sztomek sztomek force-pushed the feat/predictive-back-enhancements branch from 2ce292c to cc3e66b Compare February 26, 2026 14:42
@sztomek
Copy link
Copy Markdown
Contributor Author

sztomek commented Feb 26, 2026

@MiSikora
let me respond to your observations

  1. The predictive animation for the player sheet appears a bit janky. Notice how it jumps and loses the fade transition especially at the top of the screen. It may be better to remove it in this context.

I've disabled the custom transition on the player bottom sheet, so now you should see the default one.

  1. The initial scaling of the previous fragment on the back stack feels visually abrupt.

Polished the animation params so now it seems to be smoother to my eyes.

  1. Predictive back doesn't work for me during the onboarding flow. I don't think it is necessary to add it there, especially given how convoluted the code is. I'm highlighting it just in case.

Yeah, it doesn't work for me either, already spent some time on it without success. I'd lean towards just letting it go 👋

Please check the app again and review the code if you feel like these efforts are justified.

@sztomek sztomek requested a review from MiSikora February 26, 2026 14:45
Copy link
Copy Markdown
Contributor

@MiSikora MiSikora left a comment

Choose a reason for hiding this comment

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

I know a substantial amount of thoughtful work and research went into this change. However, after additional testing, I am concerned that it may frustrate more users than it benefits. If you would like to proceed, I strongly recommend distributing a prototype build to the team, particularly the designers, for validation before merging.

On iOS, the animations feel more natural. For example, Settings pages transition with a horizontal scroll. In our implementation, the current screen fades out completely before the previous screen appears. With gesture navigation, this looks ok(ish?). However, when using Up navigation or 3 button navigation, the transition feels abrupt and visually disruptive.

iOS Android
ios.mov
screen-20260226-163956-1772120376263.mp4

Polished the animation parameters so now it seems to be smoother to my eyes.

It still feels abrupt to me in practice, especially during rapid navigation. The issue is most noticeable on screens with a lot of content such as Discover or Podcasts. Would it be possible to remove the background fragment scaling while retaining the improved fade transition for the foreground fragment?

@MainThread
fun finish() {
try {
seekController?.animateToEnd()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ Should the seekControler be cleared here?

@MainThread
fun cancel() {
try {
seekController?.currentFraction = 0f
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ Should the seekControler be cleared here?

* Starts a seekable transition with scale and fade effects.
*/
@MainThread
fun start(backEvent: BackEventCompat) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ We can remove backEvent as it isn't used in the implementation.

* Tests the seekable transition-based animations for predictive back gestures.
* Note: These are basic smoke tests since TransitionSeekController requires a real Android environment.
*/
class PredictiveBackTransitionTest {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ I’m having trouble understanding the value these tests are providing.

PredictiveBackTransition is a thin wrapper around TransitionSeekController. The tests verify that calling the wrapper methods does not throw. That’s a very low-signal assertion and doesn’t give us meaningful correctness of behavior.

In general, tests are most valuable when they validate observable behavior or interactions. Otherwise, they add maintenance cost without increasing confidence in the implementation.

I’d lean toward removing them. If there’s particular coverage they’re meant to provide, I’m happy to revisit.

import androidx.activity.OnBackPressedCallback
import androidx.navigation.NavController

object ActivityBackHelper {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ I’m not sure where this feedback best fits, so I’ll leave it here.

ActivityBackHelper, ComposeNavigationBackHelper, and PredictiveBackExtensions all appear to provide a similar category of functionality, but their API shapes are quite different.

  • ActivityBackHelper is both an object with functions and a set of extension functions that delegate to that object. And that object has extension functions inside as well.
  • ComposeNavigationBackHelper is an object exposing functions.
  • PredictiveBackExtensions is a collection of extension functions.

Why they don’t follow a consistent design approach? Is there a specific constraint or usage pattern that drove these differences? If not, it may be worth aligning them to a single, consistent API style to reduce cognitive overhead and improve discoverability.

@wpmobilebot
Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in 📱 Mobile by scanning the QR code below to install the corresponding build.

App Name📱 Mobile
Build TypePrototype
Build Number9409
Version8.6-rc-2
Application IDau.com.shiftyjelly.pocketcasts
Commitcc3e66b
Installation URL1c00isj52598o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in ⌚ Wear by scanning the QR code below to install the corresponding build.
App Name⌚ Wear
Build TypeDebugProd
Build Number9409
Version8.6-rc-2
Application IDau.com.shiftyjelly.pocketcasts
Commitcc3e66b
Direct Downloadpocketcasts-wear-prototype-build-pr5026-cc3e66b.apk
Installation URL1c00isj52598o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.
App Icon📲 You can test the changes from this Pull Request in 🚗 Automotive by scanning the QR code below to install the corresponding build.
App Name🚗 Automotive
Build TypeDebugProd
Build Number9409
Version8.6-rc-2
Application IDau.com.shiftyjelly.pocketcasts
Commitcc3e66b
Direct Downloadpocketcasts-automotive-prototype-build-pr5026-cc3e66b.apk
Installation URL1c00isj52598o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot wpmobilebot modified the milestones: 8.7, 8.8 Mar 5, 2026
@wpmobilebot
Copy link
Copy Markdown
Collaborator

Version 8.7 has now entered code-freeze, so the milestone of this PR has been updated to 8.8.

@wpmobilebot wpmobilebot modified the milestones: 8.8, 8.9 Mar 18, 2026
@wpmobilebot
Copy link
Copy Markdown
Collaborator

Version 8.8 has now entered code-freeze, so the milestone of this PR has been updated to 8.9.

@sztomek
Copy link
Copy Markdown
Contributor Author

sztomek commented Mar 18, 2026

We agreed not to include these animations in the app. internal discussion: p1773860264187239/1772474323.176949-slack-C05RR9P9RAT

@sztomek sztomek closed this Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Area] Navigation [Type] Enhancement Improve an existing feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants