-
-
Notifications
You must be signed in to change notification settings - Fork 359
fix(session-replay): Fixes orientation change misalignment for session replay on Android #5321
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
Changes from 14 commits
960f850
185b920
87ce208
c8266c3
1672112
464601e
cf4ca99
97097b5
919dff2
5c6a512
a0c30cc
1f6a939
1099bd7
f4690cc
dbee1d4
21a37a5
bbddc27
b408aed
e8e516f
e8debff
a8ad163
f963590
affda3d
f56e75c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| package io.sentry.react; | ||
|
|
||
| import android.os.Bundle; | ||
| import android.util.DisplayMetrics; | ||
| import android.view.View; | ||
| import android.view.ViewGroup; | ||
| import android.view.ViewTreeObserver; | ||
| import androidx.annotation.NonNull; | ||
| import androidx.fragment.app.Fragment; | ||
| import androidx.fragment.app.FragmentManager; | ||
|
|
@@ -13,9 +15,14 @@ | |
| import com.facebook.react.uimanager.events.EventDispatcher; | ||
| import com.facebook.react.uimanager.events.EventDispatcherListener; | ||
| import io.sentry.ILogger; | ||
| import io.sentry.Integration; | ||
| import io.sentry.ScopesAdapter; | ||
| import io.sentry.SentryLevel; | ||
| import io.sentry.SentryOptions; | ||
| import io.sentry.android.core.BuildInfoProvider; | ||
| import io.sentry.android.core.internal.util.FirstDrawDoneListener; | ||
| import io.sentry.android.replay.ReplayIntegration; | ||
| import java.lang.ref.WeakReference; | ||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
|
|
||
|
|
@@ -25,6 +32,15 @@ public class RNSentryReactFragmentLifecycleTracer extends FragmentLifecycleCallb | |
| private @NotNull final Runnable emitNewFrameEvent; | ||
| private @NotNull final ILogger logger; | ||
|
|
||
| @SuppressWarnings("PMD.AvoidUsingVolatile") | ||
| private @Nullable volatile ReplayIntegration replayIntegration; | ||
|
|
||
| private int lastWidth = -1; | ||
| private int lastHeight = -1; | ||
|
|
||
| private @Nullable View currentView; | ||
| private @Nullable ViewTreeObserver.OnGlobalLayoutListener currentListener; | ||
|
|
||
| public RNSentryReactFragmentLifecycleTracer( | ||
| @NotNull BuildInfoProvider buildInfoProvider, | ||
| @NotNull Runnable emitNewFrameEvent, | ||
|
|
@@ -95,6 +111,103 @@ public void onEventDispatch(Event event) { | |
| } | ||
| } | ||
| }); | ||
|
|
||
| // Add layout listener to detect configuration changes after detaching any previous one | ||
| detachLayoutChangeListener(); | ||
| attachLayoutChangeListener(v); | ||
| } | ||
|
|
||
| @Override | ||
| public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) { | ||
| detachLayoutChangeListener(); | ||
| } | ||
|
|
||
| private void attachLayoutChangeListener(final View view) { | ||
| final WeakReference<View> weakView = new WeakReference<>(view); | ||
|
|
||
| final ViewTreeObserver.OnGlobalLayoutListener listener = | ||
| new ViewTreeObserver.OnGlobalLayoutListener() { | ||
| @Override | ||
| public void onGlobalLayout() { | ||
| final View v = weakView.get(); | ||
| if (v != null) { | ||
| checkAndNotifyWindowSizeChange(v); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| currentView = view; | ||
| currentListener = listener; | ||
|
|
||
| view.getViewTreeObserver().addOnGlobalLayoutListener(listener); | ||
| } | ||
|
|
||
| private void detachLayoutChangeListener() { | ||
| if (currentView != null && currentListener != null) { | ||
| try { | ||
| ViewTreeObserver observer = currentView.getViewTreeObserver(); | ||
| if (observer != null) { | ||
| observer.removeOnGlobalLayoutListener(currentListener); | ||
| } | ||
| } catch (Exception e) { | ||
| logger.log(SentryLevel.DEBUG, "Failed to remove layout change listener", e); | ||
| } | ||
| } | ||
|
|
||
| currentView = null; | ||
| currentListener = null; | ||
|
antonis marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| private void checkAndNotifyWindowSizeChange(View view) { | ||
| try { | ||
| DisplayMetrics metrics = view.getContext().getResources().getDisplayMetrics(); | ||
| int currentWidth = metrics.widthPixels; | ||
| int currentHeight = metrics.heightPixels; | ||
|
|
||
| if (lastWidth == currentWidth && lastHeight == currentHeight) { | ||
| return; | ||
| } | ||
| lastWidth = currentWidth; | ||
| lastHeight = currentHeight; | ||
|
|
||
| notifyReplayIntegrationOfSizeChange(currentWidth, currentHeight); | ||
| } catch (Exception e) { | ||
| logger.log(SentryLevel.DEBUG, "Failed to check window size", e); | ||
| } | ||
| } | ||
|
|
||
| private void notifyReplayIntegrationOfSizeChange(int width, int height) { | ||
| if (replayIntegration == null) { | ||
| replayIntegration = getReplayIntegration(); | ||
| } | ||
|
|
||
| if (replayIntegration == null) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| replayIntegration.onWindowSizeChanged(width, height); | ||
|
antonis marked this conversation as resolved.
Outdated
|
||
| } catch (Exception e) { | ||
| logger.log(SentryLevel.DEBUG, "Failed to notify replay integration of size change", e); | ||
| } | ||
| } | ||
|
|
||
| private @Nullable ReplayIntegration getReplayIntegration() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think you could simplify this method by calling
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternatively, we could even expose the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great idea @romtsn 🙇
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. |
||
| try { | ||
| final SentryOptions options = ScopesAdapter.getInstance().getOptions(); | ||
| if (options == null) { | ||
| return null; | ||
| } | ||
|
|
||
| for (Integration integration : options.getIntegrations()) { | ||
| if (integration instanceof ReplayIntegration) { | ||
| return (ReplayIntegration) integration; | ||
| } | ||
| } | ||
| } catch (Exception e) { | ||
| logger.log(SentryLevel.DEBUG, "Error getting replay integration", e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static @Nullable EventDispatcher getEventDispatcherForReactTag( | ||
|
|
||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @romtsn 🙇
Makes sense 👍 Updated with bbddc27
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
There was a problem hiding this comment.
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