Skip to content

Conversation

@antonis
Copy link
Contributor

@antonis antonis commented Oct 30, 2025

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Fixes orientation change misalignment for session replay on Android by manually triggering the onWindowSizeChanged when the screen size changes. This should cover orientation changes or other screen size changes (e.g. foldables).

⚠️ This also fixes a PII issue since the orientation misalignment results in masking failures.

💡 Motivation and Context

Fixes #4982

💚 How did you test it?

Tested with the latest SDK and the screenshotStrategy makes a difference on how the bug is manifested

canvas pixelCopy
Before
After
After example replay example replay

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

@antonis antonis changed the title fix(session-replay): Fixes orientation change misalignment for session replay fix(session-replay): Fixes orientation change misalignment for session replay on Android Oct 30, 2025
@antonis antonis marked this pull request as ready for review October 30, 2025 13:16
@antonis antonis requested a review from romtsn October 30, 2025 13:16
@antonis
Copy link
Contributor Author

antonis commented Oct 30, 2025

@romtsn I took the liberty of adding you as a reviewer too since you have more context on the internals of SR on Android 🙇

@antonis antonis marked this pull request as draft October 30, 2025 13:20
@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 436.42 ms 429.40 ms -7.02 ms
Size 43.75 MiB 47.99 MiB 4.23 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
c94a927+dirty 366.16 ms 375.68 ms 9.52 ms
77061ed+dirty 369.55 ms 408.35 ms 38.80 ms
3bd3f0d+dirty 447.21 ms 472.31 ms 25.10 ms
93137d1+dirty 400.15 ms 424.74 ms 24.59 ms
bc9680d 375.15 ms 401.12 ms 25.97 ms
7be1f99 454.83 ms 461.36 ms 6.53 ms
bfe454a+dirty 573.44 ms 579.46 ms 6.02 ms
955f2eb+dirty 422.74 ms 410.19 ms -12.55 ms
6a70a7e+dirty 381.72 ms 413.94 ms 32.22 ms
95aaf8a 437.89 ms 419.45 ms -18.44 ms

App size

Revision Plain With Sentry Diff
c94a927+dirty 17.75 MiB 19.70 MiB 1.95 MiB
77061ed+dirty 17.75 MiB 19.68 MiB 1.94 MiB
3bd3f0d+dirty 17.75 MiB 19.70 MiB 1.95 MiB
93137d1+dirty 17.75 MiB 19.70 MiB 1.95 MiB
bc9680d 17.75 MiB 20.15 MiB 2.41 MiB
7be1f99 17.75 MiB 20.15 MiB 2.41 MiB
bfe454a+dirty 17.75 MiB 19.69 MiB 1.94 MiB
955f2eb+dirty 17.75 MiB 19.70 MiB 1.95 MiB
6a70a7e+dirty 17.75 MiB 19.69 MiB 1.94 MiB
95aaf8a 17.75 MiB 19.68 MiB 1.93 MiB

Previous results on branch: antonis/replay-orientation

Startup times

Revision Plain With Sentry Diff
de62b18+dirty 346.52 ms 372.72 ms 26.20 ms
d1e5478+dirty 406.67 ms 428.49 ms 21.81 ms
4c0adf8+dirty 364.89 ms 395.48 ms 30.59 ms
c7e9027+dirty 496.56 ms 533.29 ms 36.72 ms

App size

Revision Plain With Sentry Diff
de62b18+dirty 17.75 MiB 19.75 MiB 2.00 MiB
d1e5478+dirty 43.75 MiB 47.99 MiB 4.23 MiB
4c0adf8+dirty 17.75 MiB 19.75 MiB 2.00 MiB
c7e9027+dirty 43.75 MiB 47.99 MiB 4.23 MiB

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 396.55 ms 437.44 ms 40.89 ms
Size 43.94 MiB 48.81 MiB 4.88 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
d1fd647+dirty 374.46 ms 409.51 ms 35.05 ms
170d5ea+dirty 348.79 ms 406.94 ms 58.15 ms
6479fd5+dirty 393.06 ms 434.04 ms 40.98 ms
3e0a5f9+dirty 379.92 ms 450.96 ms 71.04 ms
1226664+dirty 377.65 ms 453.94 ms 76.29 ms
95aaf8a+dirty 342.82 ms 393.75 ms 50.93 ms
46da307+dirty 356.62 ms 415.02 ms 58.40 ms
5ee3314+dirty 358.69 ms 394.00 ms 35.31 ms
2adbd1e+dirty 366.13 ms 419.49 ms 53.36 ms
1853710+dirty 360.67 ms 396.28 ms 35.61 ms

App size

Revision Plain With Sentry Diff
d1fd647+dirty 7.15 MiB 8.43 MiB 1.28 MiB
170d5ea+dirty 7.15 MiB 8.42 MiB 1.27 MiB
6479fd5+dirty 7.15 MiB 8.41 MiB 1.26 MiB
3e0a5f9+dirty 7.15 MiB 8.42 MiB 1.27 MiB
1226664+dirty 7.15 MiB 8.46 MiB 1.30 MiB
95aaf8a+dirty 7.15 MiB 8.41 MiB 1.26 MiB
46da307+dirty 7.15 MiB 8.41 MiB 1.26 MiB
5ee3314+dirty 7.15 MiB 8.43 MiB 1.28 MiB
2adbd1e+dirty 7.15 MiB 8.43 MiB 1.28 MiB
1853710+dirty 7.15 MiB 8.41 MiB 1.26 MiB

Previous results on branch: antonis/replay-orientation

Startup times

Revision Plain With Sentry Diff
de62b18+dirty 293.96 ms 339.25 ms 45.29 ms
d1e5478+dirty 374.04 ms 395.37 ms 21.32 ms
4c0adf8+dirty 312.60 ms 365.30 ms 52.70 ms
c7e9027+dirty 371.88 ms 388.36 ms 16.48 ms

App size

Revision Plain With Sentry Diff
de62b18+dirty 7.15 MiB 8.46 MiB 1.31 MiB
d1e5478+dirty 43.94 MiB 48.81 MiB 4.88 MiB
4c0adf8+dirty 7.15 MiB 8.46 MiB 1.31 MiB
c7e9027+dirty 43.94 MiB 48.81 MiB 4.88 MiB

@antonis antonis marked this pull request as ready for review October 30, 2025 14:33
@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1220.22 ms 1218.43 ms -1.79 ms
Size 3.41 MiB 4.57 MiB 1.16 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
9f211e3+dirty 1218.80 ms 1233.88 ms 15.08 ms
785ffb1+dirty 1237.63 ms 1240.50 ms 2.87 ms
ec14be7+dirty 1234.64 ms 1245.54 ms 10.90 ms
955f2eb+dirty 1235.06 ms 1253.88 ms 18.81 ms
1226664+dirty 1220.59 ms 1221.61 ms 1.02 ms
2adbd1e+dirty 1207.51 ms 1218.98 ms 11.47 ms
4167e15+dirty 1213.39 ms 1222.50 ms 9.11 ms
6a70a7e+dirty 1225.82 ms 1230.79 ms 4.98 ms
d1fd647+dirty 1219.35 ms 1233.18 ms 13.83 ms
05bef0e+dirty 1221.02 ms 1237.04 ms 16.02 ms

App size

Revision Plain With Sentry Diff
9f211e3+dirty 2.63 MiB 3.91 MiB 1.28 MiB
785ffb1+dirty 2.63 MiB 3.81 MiB 1.18 MiB
ec14be7+dirty 2.63 MiB 3.98 MiB 1.34 MiB
955f2eb+dirty 2.63 MiB 3.98 MiB 1.35 MiB
1226664+dirty 2.63 MiB 4.00 MiB 1.37 MiB
2adbd1e+dirty 2.63 MiB 4.00 MiB 1.36 MiB
4167e15+dirty 2.63 MiB 4.00 MiB 1.37 MiB
6a70a7e+dirty 2.63 MiB 3.98 MiB 1.34 MiB
d1fd647+dirty 2.63 MiB 3.99 MiB 1.36 MiB
05bef0e+dirty 2.63 MiB 3.99 MiB 1.36 MiB

Previous results on branch: antonis/replay-orientation

Startup times

Revision Plain With Sentry Diff
de62b18+dirty 1213.62 ms 1226.20 ms 12.58 ms
c7e9027+dirty 1229.94 ms 1225.36 ms -4.57 ms
d1e5478+dirty 1215.80 ms 1211.24 ms -4.57 ms
4c0adf8+dirty 1218.67 ms 1226.43 ms 7.76 ms

App size

Revision Plain With Sentry Diff
de62b18+dirty 2.63 MiB 4.01 MiB 1.38 MiB
c7e9027+dirty 3.41 MiB 4.57 MiB 1.16 MiB
d1e5478+dirty 3.41 MiB 4.57 MiB 1.16 MiB
4c0adf8+dirty 2.63 MiB 4.01 MiB 1.38 MiB

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1218.21 ms 1221.33 ms 3.12 ms
Size 3.41 MiB 4.57 MiB 1.16 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
9f211e3+dirty 1215.38 ms 1218.15 ms 2.77 ms
785ffb1+dirty 1213.71 ms 1213.37 ms -0.35 ms
ec14be7+dirty 1229.62 ms 1230.53 ms 0.91 ms
955f2eb+dirty 1225.78 ms 1239.27 ms 13.49 ms
1226664+dirty 1237.10 ms 1245.27 ms 8.16 ms
2adbd1e+dirty 1220.65 ms 1230.20 ms 9.56 ms
4167e15+dirty 1228.96 ms 1242.15 ms 13.19 ms
6a70a7e+dirty 1231.40 ms 1239.49 ms 8.09 ms
d1fd647+dirty 1218.16 ms 1225.82 ms 7.65 ms
05bef0e+dirty 1223.31 ms 1225.55 ms 2.24 ms

App size

Revision Plain With Sentry Diff
9f211e3+dirty 3.19 MiB 4.48 MiB 1.29 MiB
785ffb1+dirty 3.19 MiB 4.38 MiB 1.19 MiB
ec14be7+dirty 3.19 MiB 4.54 MiB 1.36 MiB
955f2eb+dirty 3.19 MiB 4.55 MiB 1.36 MiB
1226664+dirty 3.19 MiB 4.57 MiB 1.38 MiB
2adbd1e+dirty 3.19 MiB 4.56 MiB 1.38 MiB
4167e15+dirty 3.19 MiB 4.57 MiB 1.38 MiB
6a70a7e+dirty 3.19 MiB 4.54 MiB 1.36 MiB
d1fd647+dirty 3.19 MiB 4.56 MiB 1.37 MiB
05bef0e+dirty 3.19 MiB 4.56 MiB 1.37 MiB

Previous results on branch: antonis/replay-orientation

Startup times

Revision Plain With Sentry Diff
de62b18+dirty 1218.02 ms 1216.85 ms -1.17 ms
c7e9027+dirty 1221.62 ms 1214.66 ms -6.96 ms
d1e5478+dirty 1209.60 ms 1215.76 ms 6.16 ms
4c0adf8+dirty 1218.00 ms 1228.30 ms 10.30 ms

App size

Revision Plain With Sentry Diff
de62b18+dirty 3.19 MiB 4.58 MiB 1.39 MiB
c7e9027+dirty 3.41 MiB 4.57 MiB 1.16 MiB
d1e5478+dirty 3.41 MiB 4.57 MiB 1.16 MiB
4c0adf8+dirty 3.19 MiB 4.58 MiB 1.39 MiB

@lucas-zimerman
Copy link
Collaborator

important fix for pixel copy since it avoid leaks!

@lucas-zimerman
Copy link
Collaborator

@sentry review

@antonis
Copy link
Contributor Author

antonis commented Oct 31, 2025

@sentry review

@lucas-zimerman
Copy link
Collaborator

Overall looks good! lets get the approval of @romtsn before merging.

}
}

private @Nullable ReplayIntegration getReplayIntegration() {
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think you could simplify this method by calling ScopesAdapter.getInstance().getOptions().getReplayController() and then checking if it's instanceof ReplayIntegration. This would spare a few cycles.

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, we could even expose the onWindowSizeChanged method in the ReplayController interface, but would require a new Android SDK release, so I'll leave it up to you to decide :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea @romtsn 🙇
Updated with dbee1d4 and verified that everything works as before.

Alternatively, we could even expose the onWindowSizeChanged method in the ReplayController interface, but would require a new Android SDK release, so I'll leave it up to you to decide :)

I'll add a note to iterate on this as an improvement. It would be nice to ship the fix soon since this is also a PII.

}
});

// Add layout listener to detect configuration changes after detaching any previous one
Copy link
Member

Choose a reason for hiding this comment

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

so, as I understood this logic only applies when ReactNavigationIntegration is used and ttid-tracking is enabled. I was wondering if we should actually make it independent from the both above, and just introduce a replay-specific FragmentLifecycleCallbacks? Also, is this only a problem when fragments are used (i.e. reactNavigation), or also applies to activity-based apps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch @romtsn 🙇

make it independent from the both above, and just introduce a replay-specific FragmentLifecycleCallbacks?

Makes sense 👍 Updated with bbddc27

Also, is this only a problem when fragments are used (i.e. reactNavigation), or also applies to activity-based apps?

I was able to still reproduce the issue on a bare app that seems to have only Activities. I think for most real world scenarios the fragment implementation would be used and I would opt for handling this case separately.

Example Activity only app

import React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import * as Sentry from '@sentry/react-native';

Sentry.init({
  dsn: 'DSN',

  // Adds more context data to events (IP address, cookies, user, etc.)
  // For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
  sendDefaultPii: true,

  // Enable Logs
  enableLogs: true,

  // Configure Session Replay
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1,
  integrations: [Sentry.mobileReplayIntegration(), Sentry.feedbackIntegration()],

  // uncomment the line below to enable Spotlight (https://spotlightjs.com)
  // spotlight: __DEV__,
});

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello, Minimal App!</Text>
      <Button title='Try!' onPress={ () => { Sentry.captureException(new Error('First error')) }}/>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  text: {
    fontSize: 20,
  },
});

export default Sentry.wrap(App);

Copy link
Member

Choose a reason for hiding this comment

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

I agree, let's not care about it for now. If we get reports we can do that later 👍 Let me review the updated logic

Copy link
Member

@romtsn romtsn left a comment

Choose a reason for hiding this comment

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

One nit comment and one question - going to approve once we figure those out. But this looks great already, thanks for investigating!

@antonis
Copy link
Contributor Author

antonis commented Nov 4, 2025

There seems to be a CI issue at this point irrelevant from this PR that causes failures like:

Error: Server answered with HTTP 500 when performing the request to https://repo.yarnpkg.com/3.6.4/packages/yarnpkg-cli/bin/yarn.js; for troubleshooting help, see https://github.com/nodejs/corepack#troubleshooting

final RNSentryReplayFragmentLifecycleTracer fragmentLifecycleTracer =
new RNSentryReplayFragmentLifecycleTracer(logger);

final @Nullable FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity();
Copy link
Member

Choose a reason for hiding this comment

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

I assume it should be always a FragmentActivity but maybe worth to have an instanceof check just in case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used the pattern from above but I think it's a good idea to add the check 👍 Updated with a8ad163

private @NotNull final ILogger logger;

@SuppressWarnings("PMD.AvoidUsingVolatile")
private @Nullable volatile ReplayIntegration replayIntegration;
Copy link
Member

Choose a reason for hiding this comment

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

I assume volatile is not really needed here since everything is being accessed from the main thread (OnGlobalLayoutListener is likely called on the main thread too). But it's also fine as it is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated with f963590

private int lastWidth = -1;
private int lastHeight = -1;

private @Nullable View currentView;
Copy link
Member

Choose a reason for hiding this comment

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

theoretically this shouldn't leak since we nullify it in onFragmentViewDestroyed but maybe worth to have it as a WeakRef just in case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea 👍 Updated with f56e75c

Copy link
Member

@romtsn romtsn left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for addressing the feedback!

@antonis antonis merged commit f9dd020 into main Nov 4, 2025
119 of 143 checks passed
@antonis antonis deleted the antonis/replay-orientation branch November 4, 2025 15:26
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.

Orientation change doesn't work in replays on Android

5 participants