-
Notifications
You must be signed in to change notification settings - Fork 30
[MOB-11508] Fix Android SDK auth token refresh in background #910
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
Conversation
eebe6ea
to
e8ff15e
Compare
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.
Pull Request Overview
This PR fixes the aggressive background JWT token refresh in the Android SDK by adding lifecycle awareness to the IterableAuthManager so that refresh timers are cleared when the app is backgrounded and resumed when foregrounded.
- Implements IterableActivityMonitor.AppStateCallback in IterableAuthManager
- Adds tests to verify lifecycle behavior in IterableApiAuthTests and IterableActivityMonitorTest
- Unregisters lifecycle callbacks on reset to prevent unintended refreshes
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java | Adds test for pausing token refresh on background transition |
iterableapi/src/test/java/com/iterable/iterableapi/IterableActivityMonitorTest.java | Verifies proper lifecycle registration and unregistration |
iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java | Implements lifecycle callbacks to clear and resume token refresh timers |
Comments suppressed due to low confidence (1)
iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java:17
- [nitpick] For consistency with Java constant naming conventions, consider renaming 'expirationString' to 'EXPIRATION_STRING'.
private static final String expirationString = "exp";
String authToken = api.getAuthToken(); | ||
|
||
if (authToken != null) { | ||
queueExpirationRefresh(authToken); | ||
// If queueExpirationRefresh didn't schedule a timer (expired token case), request new token | ||
if (!isTimerScheduled && !pendingAuth) { | ||
IterableLogger.d(TAG, "Token expired, requesting new token on foreground"); | ||
requestNewAuthToken(false, null, true); | ||
} | ||
} else if ((api.getEmail() != null || api.getUserId() != null) && !pendingAuth) { | ||
IterableLogger.d(TAG, "App foregrounded, user identified, no auth token present. Requesting new token."); | ||
requestNewAuthToken(false, null, true); | ||
} | ||
} catch (Exception e) { | ||
IterableLogger.e(TAG, "Error in onSwitchToForeground", e); | ||
} | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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.
Adding lifecycle methods has added a bit bloat in this manager class, but seems necessary to have it handled by itself different flows that can occur.
Here's a loom link of what I tested
|
||
// After reset, lifecycle methods should still work (no exceptions) | ||
authManager.onSwitchToBackground(); | ||
authManager.onSwitchToForeground(); |
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.
Probably a better check would be to trigger parent level onSwitchToBackground to check if authmanager's onSwitchToBackground is actually called or not to check the unregistering of callbacks.
authManager.onSwitchToForeground(); | ||
shadowOf(getMainLooper()).runToEndOfTasks(); | ||
|
||
// Test passes if no exceptions were thrown and lifecycle methods executed successfully |
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.
Should we not test anything after onSwitchToForeground? Like if the jwt is being fetched again or not? Or if the timer is no more running. Or probably running as there is new requests SDK is making?
public void onSwitchToForeground() { | ||
try { | ||
IterableLogger.v(TAG, "App switched to foreground. Re-evaluating auth token refresh."); | ||
String authToken = api.getAuthToken(); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
…management" This reverts commit f3a783b.
Problem
Priceline was experiencing significantly higher request volumes (10x higher RPM) from the Android SDK compared to iOS, causing infrastructure strain. The root cause was that the Android SDK was aggressively refreshing JWT tokens even when the app was in the background, unlike the iOS SDK which naturally pauses token refreshes when the app is backgrounded.
Root Cause
The Android
IterableAuthManager
usesjava.util.Timer
which runs on its own background thread, independent of the app's lifecycle. This means the timer continues to fire and make token refresh requests even when the app is not active, whereas iOS timers are tied to the main run loop and pause when the app is suspended.Solution
Added lifecycle awareness to
IterableAuthManager
by implementingIterableActivityMonitor.AppStateCallback
:Changes Made
Core Implementation
IterableAuthManager.java
:IterableActivityMonitor.AppStateCallback
interfaceonSwitchToBackground()
to clear timersonSwitchToForeground()
to resume token evaluationreset()
methodTests Added
IterableApiAuthTests.java
: AddedtestAuthTokenRefreshPausesOnBackground()
to verify lifecycle behaviorIterableActivityMonitorTest.java
: AddedtestAuthManagerLifecycleRegistration()
to verify proper registration/cleanupImpact
Testing
Ticket
Fixes https://iterable.atlassian.net/browse/MOB-11508
Additional Notes
This is a minimal, focused fix that addresses the specific issue without introducing unnecessary complexity. The changes align Android's behavior with iOS while maintaining all existing authentication functionality.
📱 Manual Testing Guidelines
Setup Requirements
Test Case 1: Background Token Refresh Pause
Objective: Verify token refresh requests stop when app goes to background
Steps:
/users/getByEmail
or similar endpointsTest Case 2: Foreground Token Refresh Resume
Objective: Verify token refresh resumes when app returns to foreground
Steps:
Test Case 3: Expired Token Handling
Objective: Verify expired tokens are refreshed immediately on foreground
Steps:
Test Case 4: Rapid Background/Foreground Transitions
Objective: Verify system handles rapid transitions gracefully
Steps:
Test Case 5: No User Set Scenarios
Objective: Verify behavior when no user email/ID is configured
Steps:
Logging & Monitoring
Enable verbose logging to see auth manager activity:
Key log messages to look for:
"App switched to background. Clearing auth refresh timer."
"App switched to foreground. Re-evaluating auth token refresh."
"Token expired, requesting new token on foreground"
Network monitoring checklist: