Skip to content

fix: Handle configuration changes during WebAuth flow to prevent memory leak#941

Open
utkrishtsahu wants to merge 4 commits intov4_developmentfrom
SDK-6233-The-SDK-doesn-t-handle-configuration-changes-correctly-on-device-rotation-and-causes-a-memory-leak
Open

fix: Handle configuration changes during WebAuth flow to prevent memory leak#941
utkrishtsahu wants to merge 4 commits intov4_developmentfrom
SDK-6233-The-SDK-doesn-t-handle-configuration-changes-correctly-on-device-rotation-and-causes-a-memory-leak

Conversation

@utkrishtsahu
Copy link
Copy Markdown
Contributor

@utkrishtsahu utkrishtsahu commented Mar 24, 2026

Changes

When the Activity is destroyed during a WebAuth login or logout flow due to a configuration change (e.g. device rotation, locale change, dark mode toggle), the SDK previously retained a strong reference to the destroyed Activity via the static WebAuthProvider.managerInstance → OAuthManager/LogoutManager → callback chain, causing a memory leak. Additionally, the authentication result was delivered to the destroyed Activity's callback, which could crash or silently fail.

Root cause: WebAuthProvider.managerInstance is a static field. OAuthManager and LogoutManager held a strong reference to the callback, which captured the Activity/Fragment this. On configuration change, the old Activity was destroyed but never garbage collected because of this reference chain.

What changed:

  • LifecycleAwareCallback (new): A DefaultLifecycleObserver wrapper around the user's callback. It observes the host Activity/Fragment lifecycle and sets inner = null immediately and deterministically when onDestroy fires — breaking the memory leak chain without relying on GC timing. If a result arrives after onDestroy, it is routed to the onDetached lambda instead of the dead callback.
  • OAuthManager: Retains a strong callback reference (no change). The leak is prevented upstream by LifecycleAwareCallback.
  • LogoutManager: Same — strong reference retained, leak prevented upstream.
  • WebAuthProvider: login().start() and logout().start() now wrap the callback in LifecycleAwareCallback when the context is a LifecycleOwner. Added the following new public APIs:
    • consumePendingLoginResult(callback) — Checks for and atomically delivers a cached login result to the provided callback. Returns true if a pending result was found and delivered.
    • consumePendingLogoutResult(callback) — Same for logout results.
    • PendingResult<S, E> (internal sealed class) — Represents a cached Success or Failure result, stored in an AtomicReference for thread safety.
  • EXAMPLES.md: Added "Handling Configuration Changes During Authentication" section with usage example.
  • V4_MIGRATION_GUIDE.md: Added documentation under "New APIs" section with accurate description of LifecycleAwareCallback.

Usage:

override fun onResume() {
    super.onResume()
    WebAuthProvider.consumePendingLoginResult(loginCallback)
    WebAuthProvider.consumePendingLogoutResult(logoutCallback)
}

No existing public APIs were removed, deprecated, or changed in signature. The suspend fun await() API is unaffected since it calls startInternal() directly and does not go through LifecycleAwareCallback.

Testing

Unit tests added (19 new tests in WebAuthProviderTest.kt):

  • shouldConsumePendingLoginSuccessResult
  • shouldConsumePendingLoginFailureResult
  • shouldReturnFalseWhenNoPendingLoginResult
  • shouldNotConsumeLoginResultTwice
  • shouldConsumePendingLogoutSuccessResult
  • shouldConsumePendingLogoutFailureResult
  • shouldReturnFalseWhenNoPendingLogoutResult
  • shouldNotConsumeLogoutResultTwice
  • shouldCacheLoginResultWhenLifecycleCallbackIsDetachedOnDestroy
  • shouldCacheLoginFailureWhenLifecycleCallbackIsDetachedOnDestroy
  • shouldCacheLogoutSuccessWhenLifecycleCallbackIsDetachedOnDestroy
  • shouldCacheLogoutFailureWhenLifecycleCallbackIsDetachedOnDestroy
  • shouldDeliverDirectlyWhenLifecycleCallbackIsAlive
  • shouldDeliverFailureDirectlyWhenLifecycleCallbackIsAlive
  • shouldDeliverLogoutDirectlyWhenLifecycleCallbackIsAlive
  • shouldRegisterAsLifecycleObserverOnInit
  • shouldUnregisterLifecycleObserverOnDestroy
  • shouldClearPendingLoginResultOnNewLoginStart
  • shouldClearPendingLogoutResultOnNewLogoutStart
    All existing unit tests pass without modification.

Manual testing performed on device (Android 14):

  • Scenario 1 — Normal login (no rotation): result delivered directly via callback ✅
  • Scenario 2 — Rotate during login (callback path): result cached → recovered via consumePendingLoginResult() in onResume() ✅
  • Scenario 3 — Rotate during login (await() coroutine path): coroutine continuation survives rotation, result delivered without consumePending ✅
  • Scenario 4 — Process death during login: state restored from Bundle via OAuthManager.fromState(), result delivered via addCallback listeners ✅
  • Scenario 5 — Rotate during logout (callback path): result cached → recovered via consumePendingLogoutResult() in onResume() ✅
  • Scenario 6 — Rotate then cancel login: cancellation cached → consumePendingLoginResult() delivers the error correctly ✅
  • Scenario 7 — Normal logout (no rotation): result delivered directly ✅

Checklist

@utkrishtsahu utkrishtsahu requested a review from a team as a code owner March 24, 2026 14:21
…ry leak and lost results

- Add LifecycleAwareCallback to null the user callback on onDestroy, eliminating the Activity memory leak during rotation
- Cache results arriving after destroy in AtomicReference (pendingLoginResult / pendingLogoutResult) for recovery via consumePendingLoginResult / consumePendingLogoutResult in onResume
- Revert OAuthManager and LogoutManager to hold a strong callback reference (leak prevention is now LifecycleAwareCallback's responsibility)
- Add 19 unit tests covering config-change caching, double-consume protection, observer lifecycle, and stale-result clearing
…nfiguration-changes-correctly-on-device-rotation-and-causes-a-memory-leak
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.

1 participant