CCCT-2438 Extract Post-Login Routing And Inline App Seating#3743
Conversation
[AI] Added a pure PostLoginRouter and its PostLoginDestination sealed type with unit tests covering Home and TerminalFailure routing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Added a unit test confirming redirectToConnectOpportunityInfo does not affect the resolved Home destination. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Added AppSeater suspend port with SeatResult sealed type and unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Refactored SeatAppActivity to delegate seating to AppSeater; removed the Thread/Handler/SeatAppProcess machinery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Routed the DispatchActivity LOGIN_USER success result through PostLoginRouter to produce a PostLoginDestination instead of encoding the seating decision inline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Extracted a named restoreSession local in DispatchActivity.routeLoginResult for clarity and wrapped an over-115-char comment in SeatAppActivity to satisfy checkstyle LineLength. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Guarded AppSeater.seatIfNeeded against thrown exceptions so seating failures are logged and returned as a SEAT_ERROR result that cancels instead of stranding the seating screen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Suggested Review Order
|
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3743 +/- ##
============================================
+ Coverage 25.55% 25.80% +0.25%
+ Complexity 4368 4364 -4
============================================
Files 950 947 -3
Lines 57646 56993 -653
Branches 6894 6782 -112
============================================
- Hits 14732 14709 -23
+ Misses 41088 40455 -633
- Partials 1826 1829 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
📝 WalkthroughWalkthroughThis PR introduces a post-login routing abstraction and refactors two CommCare activities to use it. It adds PostLoginRouter to centralize navigation decisions based on LoginResult and context flags, returning either a Home destination with login state or a TerminalFailure with error. It also extracts app seating into a reusable Kotlin component (AppSeater) that handles app initialization on a lifecycle-aware coroutine scope with injectable dependencies. SeatAppActivity is refactored to delegate to AppSeater and remove internal threading, while DispatchActivity is refactored to route post-login outcomes through PostLoginRouter instead of managing state directly. Tests cover routing logic for success and failure scenarios, and app seating for missing records, ready/corrupted states, and exception cases. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
app/src/org/commcare/activities/SeatAppActivity.java (2)
33-41: 💤 Low valueRemove the in-code comment.
Per the repository convention, in-code comments should not be added unless explicitly requested. The result-mapping logic itself is correct.
As per coding guidelines: "Do not add any in-code comments unless explicitly requested".♻️ Proposed change
private void finishWithResult(SeatResult result) { - // A corrupted seat still returns RESULT_OK; DispatchActivity detects - // STATE_CORRUPTED and routes to recovery. boolean treatAsSeated = result instanceof SeatResult.Success || (result instanceof SeatResult.Failed && ((SeatResult.Failed)result).getReason() == SeatFailure.CORRUPTED); setResult(treatAsSeated ? RESULT_OK : RESULT_CANCELED, new Intent(getIntent())); finish(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/org/commcare/activities/SeatAppActivity.java` around lines 33 - 41, Remove the in-code explanatory comment inside the finishWithResult method: delete the two-line comment beginning with "A corrupted seat still returns RESULT_OK; DispatchActivity detects STATE_CORRUPTED and routes to recovery." so the method contains only the result-mapping logic using SeatResult, SeatResult.Success, SeatResult.Failed, SeatFailure.CORRUPTED and the setResult/finish calls; leave the logic unchanged.
29-30: ⚡ Quick winGuard against a missing
KEY_APP_TO_SEATextra inSeatAppActivity
getIntent().getStringExtra(KEY_APP_TO_SEAT)can benull; passing that intonew AppSeater().start(this, appId, ...)can crash ifappIdis treated as non-null. Java launchers currently set the extra, but this defensive check prevents unexpected NPEs.🛡️ Proposed guard
String appId = getIntent().getStringExtra(KEY_APP_TO_SEAT); +if (appId == null) { + setResult(RESULT_CANCELED, new Intent(getIntent())); + finish(); + return; +} new AppSeater().start(this, appId, progress -> {}, this::finishWithResult);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/org/commcare/activities/SeatAppActivity.java` around lines 29 - 30, SeatAppActivity currently calls getIntent().getStringExtra(KEY_APP_TO_SEAT) and passes the possibly-null appId into new AppSeater().start(...), which can lead to NPEs; update SeatAppActivity to guard against a missing KEY_APP_TO_SEAT by checking if appId is null (from getIntent().getStringExtra(KEY_APP_TO_SEAT)) and handle that early (e.g., log the error, show a user-friendly message or set an error result and call finishWithResult / finish) instead of calling AppSeater.start with a null value; reference the KEY_APP_TO_SEAT constant, the appId variable, the AppSeater.start(this, appId, progress -> {}, this::finishWithResult) invocation, and the finishWithResult method when making the change.app/unit-tests/src/org/commcare/login/AppSeaterTest.kt (1)
13-13: 💤 Low valueReplace
STATE_READY = 2with a non-corrupted value derived fromCommCareApplication.STATE_CORRUPTED
AppSeater.seatIfNeededonly checksresourceState == CommCareApplication.STATE_CORRUPTED; any other value returnsSeatResult.Success, so the test doesn’t need to hardcode “ready” as2.private const val STATE_READY = 2Prefer returning a value like
CommCareApplication.STATE_CORRUPTED + 1(inline or via a renamed constant) to make the test intent (“not corrupted”) explicit and avoid brittle numeric coupling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/unit-tests/src/org/commcare/login/AppSeaterTest.kt` at line 13, Replace the hardcoded numeric STATE_READY with a value derived from CommCareApplication.STATE_CORRUPTED so the test clearly represents a "not corrupted" state: update the STATE_READY constant (used by AppSeater.seatIfNeeded in the test) to CommCareApplication.STATE_CORRUPTED + 1 (or rename to STATE_NOT_CORRUPTED and assign that expression) so the test no longer relies on the magic number 2 and explicitly conveys "not corrupted".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/src/org/commcare/activities/SeatAppActivity.java`:
- Around line 33-41: Remove the in-code explanatory comment inside the
finishWithResult method: delete the two-line comment beginning with "A corrupted
seat still returns RESULT_OK; DispatchActivity detects STATE_CORRUPTED and
routes to recovery." so the method contains only the result-mapping logic using
SeatResult, SeatResult.Success, SeatResult.Failed, SeatFailure.CORRUPTED and the
setResult/finish calls; leave the logic unchanged.
- Around line 29-30: SeatAppActivity currently calls
getIntent().getStringExtra(KEY_APP_TO_SEAT) and passes the possibly-null appId
into new AppSeater().start(...), which can lead to NPEs; update SeatAppActivity
to guard against a missing KEY_APP_TO_SEAT by checking if appId is null (from
getIntent().getStringExtra(KEY_APP_TO_SEAT)) and handle that early (e.g., log
the error, show a user-friendly message or set an error result and call
finishWithResult / finish) instead of calling AppSeater.start with a null value;
reference the KEY_APP_TO_SEAT constant, the appId variable, the
AppSeater.start(this, appId, progress -> {}, this::finishWithResult) invocation,
and the finishWithResult method when making the change.
In `@app/unit-tests/src/org/commcare/login/AppSeaterTest.kt`:
- Line 13: Replace the hardcoded numeric STATE_READY with a value derived from
CommCareApplication.STATE_CORRUPTED so the test clearly represents a "not
corrupted" state: update the STATE_READY constant (used by
AppSeater.seatIfNeeded in the test) to CommCareApplication.STATE_CORRUPTED + 1
(or rename to STATE_NOT_CORRUPTED and assign that expression) so the test no
longer relies on the magic number 2 and explicitly conveys "not corrupted".
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 61b37eb8-d6c6-4f44-8a2a-fc93b8ec5431
📒 Files selected for processing (7)
app/src/org/commcare/activities/DispatchActivity.javaapp/src/org/commcare/activities/SeatAppActivity.javaapp/src/org/commcare/login/AppSeater.ktapp/src/org/commcare/login/PostLoginDestination.ktapp/src/org/commcare/login/PostLoginRouter.ktapp/unit-tests/src/org/commcare/login/AppSeaterTest.ktapp/unit-tests/src/org/commcare/login/PostLoginRouterTest.kt
[AI] Tidied DispatchActivity import ordering and formatting, adopted a pattern-matching instanceof in applyPostLoginDestination, and simplified the AppSeater progress test call to a trailing lambda. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shubham1g5
left a comment
There was a problem hiding this comment.
Have not reviewed tests yet as wanted to get some clarification on design before diving into those.
… into CCCT-2438-login-routing-extraction-and-inline-app-seating
[AI] Made SeatAppActivity always return RESULT_OK so callers rely on the existing app-state check, and removed AppSeater's generic-exception catch so seating fails fast like the legacy code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Moved the connectManagedLogin intent-extra extraction into routeLoginResult to localise login-result intent parsing in one place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OrangeAndGreen
left a comment
There was a problem hiding this comment.
All looks good to me, just one question to consider
…ommcare-android into CCCT-2438-login-routing-extraction-and-inline-app-seating
Linted Java files. Fixed small merge issue with CCCT-2437-headless-login-engine branch
The base branch was changed.
…-2438-login-routing-extraction-and-inline-app-seating
[AI] Wrapped the when-entry bodies in PostLoginRouter.route in braces to satisfy the ktlint when-entry-bracing rule failing in lint CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-2438-login-routing-extraction-and-inline-app-seating
…-2438-login-routing-extraction-and-inline-app-seating
…-2438-login-routing-extraction-and-inline-app-seating
OrangeAndGreen
left a comment
There was a problem hiding this comment.
While reviewing the next PR after this one (3753) I noticed we remove two of the classes introduced in this PR. Given that this one is still open, it seems like it would make more sense to remove those classes here rather than in a follow-up PR
[AI] Inlined the DispatchActivity post-login intent handling and removed the unused PostLoginRouter and PostLoginDestination classes along with their test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CCCT-2438
Technical Summary
Phase 2 of the login/Connect decoupling (CCCT-2164 plan), stacked on the
CCCT-2437-headless-login-enginebranch.PostLoginRouter/PostLoginDestination: a pure, Android-free mapping from aLoginResult+LaunchContextto a navigation destination.DispatchActivity'sLOGIN_USERresult handler now delegates to it instead of unpacking intent extras inline.SeatAppActivity'sThread/HandlerintoAppSeater, a lifecycle-scoped coroutine wrapper with injectable record-lookup / seat / dispatcher;SeatAppActivitybecomes a thin UI host.Safety Assurance
Safety story
What gives me confidence:
PostLoginRoutermapping (including negative cases) andAppSeatersuccess / not-found / corrupted / exception paths.Risks to review:
DispatchActivityrouting integration and theSeatAppActivityhost are exercised manually, not by automated tests.SeatAppActivitydrops the oldsavedInstanceState(KEY_IN_PROGRESS) rotation guard; seating now relies onlifecycleScopecancellation, so re-seating behaviour on config change / process recreation is worth a look.PostLoginRouterdoes not yet own the full Connect-vs-home routing decision (that still lives indispatch()), so today it maps only success → Home and failure → TerminalFailure.Automated test coverage
PostLoginRouterTest— success routes toHomecarrying login mode and launch context; assertsredirectToConnectOpportunityInfodoes not leak into theHomedestination; eachLoginErrormaps toTerminalFailure.AppSeaterTest— missing record →APP_NOT_FOUNDwithout seating; ready state →Success; corrupted state →CORRUPTED; thrown exceptions from bothseatAppandrecordLookuppropagate (fail-fast); emitsSeatingprogress.