Skip to content

CCCT-2441 Silent App Launch For Job Intro And Download Screens#3765

Open
conroy-ricketts wants to merge 21 commits into
masterfrom
CCCT-2441-new-login-path-for-intro-and-download
Open

CCCT-2441 Silent App Launch For Job Intro And Download Screens#3765
conroy-ricketts wants to merge 21 commits into
masterfrom
CCCT-2441-new-login-path-for-intro-and-download

Conversation

@conroy-ricketts

@conroy-ricketts conroy-ricketts commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

CCCT-2441

Product Description

Completes the rollout of the silent Connect app-launch path to its last two surfaces: starting a job from the opportunity intro screen, and the launch that runs right after an app finishes downloading/installing. These now open the app directly behind the single progress dialog — no LoginActivity / SeatAppActivity flashing through — and backing out of the app returns to the originating Connect screen rather than the transient setup screen. Backing out past the opportunity list then exits the app instead of re-opening the launched app. After repeated launch failures (two retries), the failure dialog now shows a dismiss-only "try again later" message instead of offering endless retries.

Here's a video of the retry prompt dialog:

Screen_Recording_20260617_142435_CommCare.Debug.mp4

Here's a video of the download and job intro pages. Note that I show two different cases: 1) the app was pre-installed and thus the download page is skipped, 2) the app was not pre-installed and we show the download page:

Screen_Recording_20260617_133207_CommCare.Debug.mp4

Technical Summary

  • Migrates ConnectJobIntroFragment and ConnectDownloadingFragment onto ConnectAppLaunchController, the shared silent-launch orchestration already used by the other Connect surfaces.
  • Reproduces the back navigation the old DispatchActivity path used to provide, but without DispatchActivity: ConnectActivity remembers a Connect-initiated launch and exits the page when the user backs out of the opportunity list, and an already-running app is brought forward with FLAG_ACTIVITY_REORDER_TO_FRONT.
  • Removes the now-dead IS_LAUNCH_FROM_CONNECT flag, the auto-login branch in LoginActivity, and the CONNECT_MANAGED_LOGIN / redirectToConnectHome back-out path in DispatchActivity — none have remaining writers once these two surfaces stop using the legacy path.
  • Hardens the launcher: bails if the fragment view is already gone; soundly detects an existing session for the target app; defers the back-stack pop until Home is in front to avoid a blank-frame flash; and reports the terminal outcome to the calling fragment (onLaunchSucceeded / onLaunchFailed) while keeping ownership of the failure dialogs.
  • LaunchOutcomeRouter escalates a retryable failure to a dismiss-only message once MAX_LAUNCH_ATTEMPTS (3) is reached.

Safety Assurance

Safety story

What gives me confidence:

  • I tested on a device: launched apps from both the job-intro Start button and the post-download surface, including launching several not-yet-installed apps back-to-back, landing on Home with no login/app-setup screens flashing through.
  • I device-tested the back navigation: backing out of a launched app returns to the Connect screen, backing out past the opportunity list exits the app, and re-launching an already-open app reuses its Home without the back stack collapsing.
  • I reproduced the back-to-back NoSuchElementException crash before the session-shortcut fix and confirmed it no longer occurs after.
  • I exercised the new post-2-retries escalated dialog on a device by temporarily forcing the launch to always error.
  • The routing/escalation logic is covered by LaunchOutcomeRouter and ConnectAppLauncher unit tests, and the change is scoped to the Connect launch path.
  • The removed flag and branches are dead code — I verified there are no remaining writers/readers once the two surfaces are migrated.

Risks to review:

  • Touches the churn-prone LoginActivity / DispatchActivity by deleting the auto-login and CONNECT_MANAGED_LOGIN / redirectToConnectHome back-out paths. This is behavior-preserving (those paths were only reachable via the now-removed flag) but worth a careful read.
  • The dismiss-only failure dialog and the controller / ConnectActivity back-stack handling (the exit-on-back and REORDER_TO_FRONT launch) are Android-bound and not unit-tested; they were verified manually on-device.
  • The Hausa and Tigrinya translations for the new strings are best-effort and should get a native-speaker review.

Automated test coverage

LaunchOutcomeRouterTest covers the retry → escalation threshold (below the limit prompts retry; at the limit shows the persistent error). The existing ConnectAppLauncherTest covers the launcher's seat / login / outcome seams. The fragment-side dialog and lifecycle code is not unit-covered.

conroy-ricketts and others added 9 commits June 16, 2026 14:49
[AI] Migrated the job-intro and downloading screens to the silent Connect launch path and removed the now-dead IS_LAUNCH_FROM_CONNECT flag and its LoginActivity/DispatchActivity auto-login branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Guarded the Connect app-launch controller against a destroyed fragment view so an in-flight launch fired from an async callback can no longer crash with IllegalStateException.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Collapsed the redundant nested seated-app login check into a single condition and trimmed the login-engine doc to describe only the current Connect launch behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Made the job-intro and download launch surfaces drop themselves from the Connect back stack after a successful launch, deferring the pop until Home is in front so no blank frame flashes on the way to Home.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Fixed a crash when launching freshly-installed Connect apps back-to-back: the already-logged-in shortcut now confirms the active session belongs to the seated app, so an install that re-seats a different app while a prior session is alive no longer skips re-login.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Stopped offering endless retries on a failing Connect app launch: after the worker retries twice, the dialog now shows a dismiss-only "check your connection and try again later" message instead of another Retry prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Added QA notes for the job-intro and post-download Connect launch surfaces, back-to-back launch stability, and the after-2-retries persistent-error dialog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@conroy-ricketts conroy-ricketts self-assigned this Jun 17, 2026
@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

Suggested Review Order

  • app/src/org/commcare/connect/ConnectAppUtils.kt — start with the deleted launchApp (the legacy path being replaced)
  • app/src/org/commcare/connect/ConnectConstants.java, app/src/org/commcare/utils/FirebaseMessagingUtil.java — removal of the dead IS_LAUNCH_FROM_CONNECT / CONNECT_MANAGED_LOGIN constants and import
  • app/src/org/commcare/activities/LoginActivity.java — removal of the now-unreachable appLaunchedFromConnect auto-login branch
  • app/src/org/commcare/activities/DispatchActivity.java — removal of the matching CONNECT_MANAGED_LOGIN / redirectToConnectHome back-out path
  • app/src/org/commcare/connect/LaunchOutcomeRouter.kt — the LaunchActions seam + new retry-escalation decision
  • app/src/org/commcare/connect/ConnectAppLauncher.kt — silent launcher; note the sound "already logged in" check
  • app/src/org/commcare/connect/ConnectAppLaunchController.kt — fragment orchestration: view guard, deferred back-stack pop, persistent-error dialog, failure counting
  • app/src/org/commcare/fragments/connect/ConnectJobIntroFragment.java, app/src/org/commcare/fragments/connect/ConnectDownloadingFragment.java — the two migrated call sites
  • app/unit-tests/src/org/commcare/connect/LaunchOutcomeRouterTest.kt — escalation-threshold coverage
  • app/res/values/strings.xml (+ 9 locale files) — new dialog strings and translations
  • docs/commcare/login-engine.md — updated behavior docs

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors the Connect app launch flow by removing legacy "launched from Connect" state tracking from DispatchActivity and LoginActivity, deleting the IS_LAUNCH_FROM_CONNECT constant and ConnectAppUtils.launchApp() function, and renaming CONNECT_MANAGED_LOGIN to PERSONALID_MANAGED_LOGIN. All fragment-side launch call sites (ConnectDownloadingFragment, ConnectJobIntroFragment) are migrated to ConnectAppLaunchController. The controller gains retry-attempt tracking, a popLaunchingScreenOnSuccess flag that pops the Navigation back stack after Home launches, and a persistent-error dialog. LaunchOutcomeRouter introduces MAX_LAUNCH_ATTEMPTS=3 to escalate from a retry prompt to a dismiss-only error dialog. ConnectAppLauncher adds UserKeyRecord validation to its session check. Persistent-error dialog strings are added across ten locales.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dimagi/commcare-android#3738: Modifies LoginActivity to change how Connect-managed login behavior is determined, directly intersecting with the Connect-launched state removal in this PR.
  • dimagi/commcare-android#3743: Modifies DispatchActivity's Connect/login managed-login flow and sets connectManagedLogin, which this PR removes.
  • dimagi/commcare-android#3753: Modifies LaunchOutcomeRouter.kt dispatch routing logic for Connect launch outcomes, directly overlapping with the failedAttempts escalation added here.

Suggested labels

skip-integration-tests

Suggested reviewers

  • OrangeAndGreen
  • shubham1g5
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: migrating app launches from job-intro and download screens to the silent launch path.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description matches the required template with product, technical, and safety sections filled in; only the checklist section is omitted.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch CCCT-2441-new-login-path-for-intro-and-download

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@conroy-ricketts conroy-ricketts marked this pull request as ready for review June 17, 2026 18:47
@conroy-ricketts conroy-ricketts requested review from a team, Jignesh-dimagi, OrangeAndGreen and shubham1g5 and removed request for a team June 17, 2026 18:47
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 25.95%. Comparing base (3b43ed1) to head (172d3f6).
⚠️ Report is 49 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3765      +/-   ##
============================================
- Coverage     26.01%   25.95%   -0.07%     
- Complexity     4420     4431      +11     
============================================
  Files           953      961       +8     
  Lines         57383    57618     +235     
  Branches       6829     6869      +40     
============================================
+ Hits          14930    14956      +26     
- Misses        40615    40826     +211     
+ Partials       1838     1836       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@OrangeAndGreen OrangeAndGreen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[FIXED] The code changes look great but I'm seeing a functionality issue. Here are steps to reproduce:

  • Go to the opportunity list and press Resume on an opp where the learn/deliver app needs to be downloaded
  • Wait while the Download page appears, the app downloads, and logs in
  • Once on App Home, navigate back (i.e. swipe)

The code should navigate to the opportunity list, but instead it navigates to the Download page (showing complete progress)

@OrangeAndGreen

OrangeAndGreen commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

[FIXED] I'm also getting a repeatable crash that seems like it might be related to these changes.

Steps:

  • Go to opportunity list, Resume on a job that requires download
  • Once that completes, navigate back to opportunity list (through the download page as described by the previous bug)
  • Now resume a different opp that requires download
  • Wait for the download to complete and login to succeed
  • The app crashes loading App Home

Here's the exception:
java.util.NoSuchElementException: No record in table user_key_records for ID 1
at org.commcare.models.database.SqlStorage.readBytes(SqlStorage.java:455)
at org.commcare.models.database.SqlStorage.read(SqlStorage.java:446)
at org.commcare.services.CommCareSessionService.getUserKeyRecord(CommCareSessionService.java:476)
at org.commcare.CommCareApplication.getRecordForCurrentUser(CommCareApplication.java:1000)
at org.commcare.activities.StandardHomeActivity.preparePinMenu(StandardHomeActivity.java:182)
at org.commcare.activities.StandardHomeActivity.onPrepareOptionsMenu(StandardHomeActivity.java:172)
at android.app.Activity.onPreparePanel(Activity.java:4480)
at androidx.activity.ComponentActivity.onPreparePanel(ComponentActivity.java:511)
at androidx.appcompat.view.WindowCallbackWrapper.onPreparePanel(WindowCallbackWrapper.java:99)
at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.onPreparePanel(AppCompatDelegateImpl.java:3097)
at androidx.appcompat.view.WindowCallbackWrapper.onPreparePanel(WindowCallbackWrapper.java:99)
at androidx.appcompat.app.ToolbarActionBar$ToolbarCallbackWrapper.onPreparePanel(ToolbarActionBar.java:523)
at androidx.appcompat.app.ToolbarActionBar.populateOptionsMenu(ToolbarActionBar.java:457)
at androidx.appcompat.app.ToolbarActionBar$1.run(ToolbarActionBar.java:57)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
at android.view.Choreographer.doCallbacks(Choreographer.java:952)
at android.view.Choreographer.doFrame(Choreographer.java:878)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

Comment thread app/src/org/commcare/activities/DispatchActivity.java
Comment thread app/src/org/commcare/connect/ConnectAppLaunchController.kt
Comment thread app/src/org/commcare/connect/ConnectAppLaunchController.kt Outdated
Comment thread app/src/org/commcare/connect/ConnectAppLaunchController.kt Outdated
Comment thread app/src/org/commcare/fragments/connect/ConnectJobIntroFragment.java Outdated
Comment thread app/src/org/commcare/connect/ConnectAppLauncher.kt Outdated
Comment thread app/src/org/commcare/connect/ConnectAppLaunchController.kt Outdated
Comment thread app/src/org/commcare/connect/ConnectAppLaunchController.kt Outdated
@Jignesh-dimagi

Copy link
Copy Markdown
Contributor

@conroy-ricketts
I am facing another issue, which may be unrelated to these specific changes and likely from a previous PR. The navigation flow is as follows: Connect Opportunity List -> click on resume → App Home Screen → press back → Connect Opportunity List (again) → press back → the App Home Screen opens again.

@OrangeAndGreen

Copy link
Copy Markdown
Contributor

Noting the two issues I reported are resolved with the latest code

conroy-ricketts and others added 3 commits June 18, 2026 13:26
[AI] Moved the post-launch back-stack pop out of ConnectAppLaunchController: the controller now reports success via an onLaunched callback and the transient fragments remove themselves via BaseConnectFragment.popSelfOnceHidden(), keeping fragment-stack management with the fragments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Invoked the post-launch onLaunched callback before starting Home so the caller's lifecycle observer is registered ahead of the resulting onStop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Tightened the already-logged-in session check to confirm the active session belongs to the current Connect user, not just any user with a key record in the seated app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

I am facing another issue, which may be unrelated to these specific changes and likely from a previous PR. The navigation flow is as follows: Connect Opportunity List -> click on resume → App Home Screen → press back → Connect Opportunity List (again) → press back → the App Home Screen opens again.

This is a great catch

@Jignesh-dimagi @OrangeAndGreen @shubham1g5

I'm torn on the best approach here because this does not seem to be a simple fix (please correct me if I'm wrong).

The crux of the issue is that we are finishing the ConnectActivity, but DispatchActivity is re-launching the CC app.

Thoughts on this?

Unfortunately, I think we may need to re-introduce some sort of "launched from Connect" flag inside DispatchActivity so that we can set the shouldFinish flag to true and exit DispatchActivity before it routes to another screen

@OrangeAndGreen

Copy link
Copy Markdown
Contributor

DispatchActivity is re-launching the CC app

I think if we close the app session (i.e. logout of the app) then DispatchActivity will route to LoginActivity instead of opening StandardHomeActivity again.

@conroy-ricketts

conroy-ricketts commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@OrangeAndGreen

Are we fine with closing the app session regardless of what the user was doing before they landed on the jobs list?

i.e. do we care to preserve this behavior (user logs in to an app → opens job list but does not launch another app → user backs out → user sees the same app they were already logged in to)?:

Screen_Recording_20260618_155948_CommCare.Debug.mp4

@Jignesh-dimagi

Copy link
Copy Markdown
Contributor

@OrangeAndGreen

Are we fine with closing the app session regardless of what the user was doing before they landed on the jobs list?

i.e. do we care to preserve this behavior (user logs in to an app → opens job list but does not launch another app → user backs out → user sees the same app they were already logged in to)?:

Screen_Recording_20260618_155948_CommCare.Debug.mp4

I was unable to view your video, as it stops at 2 seconds for some reason. However, I see two possible solutions here:

  • Whenever a user lands on the opportunity list, we should assume they want to log into an app, and the code should close the previous session at this point.
  • Pressing the back button on StandHomeActivity should be treated as a logout (for Connect users only), and the session should be closed there. However, this should not apply to traditional CommCare users, as their login needs to be preserved.

I personally prefer the second option, but I am not sure if it is feasible to distinguish a Connect user while on StandHomeActivity.

@shubham1g5

Copy link
Copy Markdown
Contributor

I think if we close the app session (i.e. logout of the app) then DispatchActivity will route to LoginActivity instead of opening StandardHomeActivity again.

We should not be closing app sessions unless user logs into another app, the reason is that there are several periodic tasks on CC Appside to sync data, heartbeat, app update etc that depends on user being logged into the app and we would want to keep the session alive as per normal CommCare session timing rules (we log out the user automatically every 24 hours) for both Connect and CommCare apps.

Unfortunately, I think we may need to re-introduce some sort of "launched from Connect" flag inside DispatchActivity so that we can set the shouldFinish flag to true and exit DispatchActivity before it routes to another screen

Think that's a decent solution for time-being and I presume this flow to undergo changes with the upcoming re-design which would be a better time to address this issue.

@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

Thanks for the input everyone!

I think I'll try to find the best way of re-introducing a "launched from Connect" flag to fix this for now

conroy-ricketts and others added 3 commits June 19, 2026 11:31
[AI] Moved the "session belongs to the current Connect user for this app" check out of ConnectAppLauncher into PersonalIdManager so it can be reused outside the launcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Anchored Connect-launched sessions back to LoginActivity on back-out and warm reopen via a session-scoped flag, leaving the session alive; renamed the launch controller's success callback to onLaunchSucceeded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

@OrangeAndGreen @Jignesh-dimagi @shubham1g5

I chose to keep the session open and added a session-scoped flag (sessionLaunchedFromConnect) to be used inside DispatchActivity.dispatch()

This fixes two issues:

  1. The user backing out of the jobs list and incorrectly seeing the live CC app home screen (the issue that Jignesh originally pointed out).
  2. The user backing out again and again until the entire mobile app closes, and then not seeing the Login screen when they open the mobile app again (another bug I discovered while dev testing).

Moreover, I explored the option of reporting both launch successes and failures to the fragment, and I think it's not necessary because all failures that can occur while launching a CC app is already handled by the launch controller before the user lands on the fragment. So I kept the success callback only and renamed it to onLaunchSucceeded to be more explicit.

I kept the "pop backstack" logic as-is for now to prevent the screen from flashing for the user when they back out of the CC app home screen.

Commit - 392c0b1

I think that covers everything remaining that's been said so far. Please let me know if I missed anything or if there are any strong opinions

@Jignesh-dimagi

Copy link
Copy Markdown
Contributor

Moreover, I explored the option of reporting both launch successes and failures to the fragment, and I think it's not necessary because all failures that can occur while launching a CC app is already handled by the launch controller before the user lands on the fragment. So I kept the success callback only and renamed it to onLaunchSucceeded to be more explicit.

This is the current requirement. However, as we move forward with the latest design changes, we need to inform the calling fragment of both success and failure. Please see the example below, where launch controller error processing is not required, yet the calling fragments still need to update the UI based on the error. Additionally, as previously discussed, we need to reduce the responsibility of the launch controller and avoid handling the fragment lifecycle within it.

OpportunityHome_New Opportunity 1 2

I kept the "pop backstack" logic as-is for now to prevent the screen from flashing for the user when they back out of the CC app home screen.

I think if launch controller starts the home activity first and then informs the calling fragment, where it will pop back and should not give flashing or UI jerk.

Comment thread app/src/org/commcare/activities/DispatchActivity.java Outdated
conroy-ricketts and others added 3 commits June 25, 2026 14:35
[AI] Reworked back navigation for apps launched from Connect so back returns to the opportunities list and exits the app cleanly instead of collapsing the stack or looping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Added an onLaunchFailed callback so a launching fragment is notified of terminal launch failures (retries exhausted, retry cancelled, or seat failure) without moving failure handling out of the controller.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@conroy-ricketts

conroy-ricketts commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@Jignesh-dimagi @shubham1g5 @OrangeAndGreen

This is ready for another round of review. I proceeded with solution A as described in this doc. I believe the cons are worth it given that the app navigation behavior will change significantly with the Connect Redesign. I dev tested this to make sure the edge cases match the desired behavior. Commit - d49fb11.

However, as we move forward with the latest design changes, we need to inform the calling fragment of both success and failure.

@Jignesh-dimagi I implemented a minimal version of this for now (to be used later). Moving ownership of failure handling to the fragment (and reducing the responsibility of the launch controller) will be a decent scope increase and I think that's worth a new ticket. Commit - 2353f1c.

[AI] Added QA notes covering exit-on-back from the opportunities list and the already-signed-in relaunch back behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* it on back; deferring to onStop keeps the pop from briefly flashing the destination beneath.
*/
protected fun popSelfOnceHidden() {
lifecycle.addObserver(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@conroy-ricketts Why its required to add the lifecycle just for popping the fragment off the back stack. Just NavHostFragment.findNavController(this).popBackStack() should be sufficient.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is to avoid the screen flashing on the user launching an app. Here's a quick video demonstrating this:

Screen_Recording_20260626_102539_CommCare.Debug.mp4

Notice how the Job List page flashes in the video when the app is launched.

If we simply use NavHostFragment.findNavController(this).popBackStack(), the screen flashes because the immediate pop happens while the ConnectActivity is still visible and in the foreground. So, popping the fragment at onStop guarantees that it happens while it's already hidden

@Jignesh-dimagi

Copy link
Copy Markdown
Contributor

@Jignesh-dimagi @shubham1g5 @OrangeAndGreen

This is ready for another round of review. I proceeded with solution A as described in this doc. I believe the cons are worth it given that the app navigation behavior will change significantly with the Connect Redesign. I dev tested this to make sure the edge cases match the desired behavior. Commit - d49fb11.

However, as we move forward with the latest design changes, we need to inform the calling fragment of both success and failure.

@Jignesh-dimagi I implemented a minimal version of this for now (to be used later). Moving ownership of failure handling to the fragment (and reducing the responsibility of the launch controller) will be a decent scope increase and I think that's worth a new ticket. Commit - 2353f1c.

I think a simple interface between the controller and the fragment could work here, but I will leave it up to you and accept your final call.

Comment thread RELEASES.md Outdated
- Launching via a job's **Start** button (opportunity intro screen) and right after an **app download/install** finishes both open directly behind the single progress dialog (no login/app-setup flash); backing out of the app home returns to the opportunities list, not the intro/download screen.
- Launch several **not-yet-installed** apps back-to-back (open one, back out, open the next, repeat) and confirm none crash.
- After repeated launch failures, on the **third consecutive failure** (i.e. after retrying twice) the dialog shows a single **OK** "couldn't open the app after several tries — check your connection and try again later" message instead of another Retry prompt.
- After launching an app from Connect and backing out to the opportunities list, press back once more: verify the app exits cleanly rather than re-opening the launched app's home.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What does the phrase verify the app exits cleanly mean? I'm not sure I understand the exact context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

By that I mean exact the entire mobile app, I'll tweak the wording here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +139 to +141
if (alreadyLoggedIn) {
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does something goes wrong if we always add FLAG_ACTIVITY_REORDER_TO_FRONT irrespective of alreadyLoggedIn ? Asking as FLAG_ACTIVITY_REORDER_TO_FRONT should be a no-op if there is no instance of activity in the task stack.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, doing this will break edge case 5 from the doc.

You're right about the no-op, but this is to avoid reusing a previous app's stale Home page due to our app-seating logic (the Connect Job Tile will show the stale information from the previous app)

@shubham1g5 shubham1g5 Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see, think instead of trying to wire in alreadyLoggedIn in via callbacks, we can just calculate it directly here in ConnectAppLaunchController.kt ?

Also trying to understand the behaviour when alreadyLoggedIn is false here. In that case is the back behaviour different from when alreadyLoggedIn is true given we only apply this flag in one case and not other ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see, think instead of trying to wire in alreadyLoggedIn in via callbacks, we can just calculate it directly here in ConnectAppLaunchController.kt ?

The goal here is to know if the user was already logged in before the app was launched. If we wait until this point in the code, a new login would have already happened and it would be difficult to distinguish between the user was "already logged in" vs "just now logged in"

Also trying to understand the behaviour when alreadyLoggedIn is false here. In that case is the back behaviour different from when alreadyLoggedIn is true given we only apply this flag in one case and not other

The back behavior would be the same in either case

@shubham1g5 shubham1g5 Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The goal here is to know if the user was already logged in before the app was launched. If we wait until this point in the code, a new login would have already happened and it would be difficult to distinguish between the user was "already logged in" vs "just now logged in"

yeah, I was thinking of calculating it in one of the launch methods itself, but keeping this as optional feedback.

One minor request to close this thread out - can we add a small code comment here on why we are adding the intent flag only when alreadyLoggedIn is set.

Comment on lines +240 to +245
if (appLaunchedFromConnect && isAtJobsList()) {
finishAffinity();
return;
}
super.onBackPressed();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not yet convinced that this is the best solution here and trying to understand the issue more, putting a few questions to clarify my understanding around this below -

  • Seems like what we are trying to do here is - if user come on Jobs List after launching a home page and clicks back, exit the App ?

  • I am curious why does that need to happen only when appLaunchedFromConnect is true ?

  • Separately Are you able to specify the exact use case this is trying to solve for from the doc and what's the behaviour without making these changes for those use cases ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Seems like what we are trying to do here is - if user come on Jobs List after launching a home page and clicks back, exit the App ?

Yes that's correct. User comes on Jobs List → Launches an app from Jobs List → Clicks back to land on Jobs List again → Clicks back again → Exit the mobile app.

I am curious why does that need to happen only when appLaunchedFromConnect is true ?

The appLaunchedFromConnect flag distinguishes between "the user launched an app from the Jobs List" and "the user is just browsing the Jobs List (i.e. did not launch an app from it)"

Separately Are you able to specify the exact use case this is trying to solve for from the doc and what's the behaviour without making these changes for those use cases ?

This solves for both edge case 2 and 3 from the doc. If we were to do a simple super.onBackPressed() instead, then this activity would finsh and fall through to the base screen below it (e.g. LoginActivity or DispatchActivity), which brings us back to the undesired "looping" state mentioned in the doc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The appLaunchedFromConnect flag distinguishes between "the user launched an app from the Jobs List" and "the user is just browsing the Jobs List (i.e. did not launch an app from it)"

Yeah I understand what the flag does but not why the finishAffinity call is behind this flag i.e. why wouldn't we want to exit if there was no app launched from Opp List ?

Also one suggestion on isAtJobsList check. Think instead of isAtJobsList we want to check whether the user is at the start destination for a navigation path, so tomorrow if we make some other page the start in nav graph, this behaviour doesn't need to be changed.

@conroy-ricketts conroy-ricketts Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah I understand what the flag does but not why the finishAffinity call is behind this flag i.e. why wouldn't we want to exit if there was no app launched from Opp List ?

Because if no app was launched, we'd want to return the user to the previous screen rather than completely exit the mobile app.

So, for example, if a user logs into a CommCare app from the Login page directly, then opens the Opp List from the side drawer, and then presses back, then we should return the user back to the CommCare app. I think exiting the mobile app in this scenario would be a bit jarring.

Another good example: Say the user opens the mobile app on their device (assuming PersonalID is already registered) and the first page they see is the Login page. If they open the Opp List page and immediately hit back, we should return to the Login page rather than exit the mobile app IMO.

Also one suggestion on isAtJobsList check. Think instead of isAtJobsList we want to check whether the user is at the start destination for a navigation path, so tomorrow if we make some other page the start in nav graph, this behaviour doesn't need to be changed.

Agreed!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@shubham1g5 shubham1g5 Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think whatever back behaviour we keep here needs to be consistent across flows, It sounds more Jarring to me if sometimes back press from Opp List results in exiting the app while sometime not just based on what screen they launched from Opp List and not the history of user navigation.

Think a mental model we can adapt here is to treat the screens launched on clicking a navigation drawer menu item as Top level screens pressing back from which exits the app i.e. treating each of these screens as the first in the app task stack and then any navigation from these screens should place the launched activity on top of it. The drawer is more like changing tabs on the same page and similar behaviour is demonstrated by Gmail/ Gphotos as well which seems like a standard practice.

Listing out various scenarios based on that mental model -

  1. Top Level Navigation From drawer:

User navigates as Opp List -> Messaging(drawer) -> Work History (drawer) , on clicking back from "Work history" we exit the app instead of tracing back the flow given all items were accessed from drawer.

  1. Cross Feature flows but not from drawer:

User navigates as `Opp List -> Resume -> App Home" , on clicking abck from "App Home" we should go back to "Opp List" to trace back user navigation history correctly.

Also FYI @OrangeAndGreen @Jignesh-dimagi

@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

I think a simple interface between the controller and the fragment could work here, but I will leave it up to you and accept your final call.

@Jignesh-dimagi I'll create a new ticket for this today and make a note of this in the new ticket

[AI] Clarified the Connect launch back-out QA note to specify the entire mobile app closes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

I think a simple interface between the controller and the fragment could work here, but I will leave it up to you and accept your final call.

@Jignesh-dimagi I'll create a new ticket for this today and make a note of this in the new ticket

@Jignesh-dimagi Ticket created here: https://dimagi.atlassian.net/browse/CCCT-2600

cc @OrangeAndGreen @shubham1g5 for visibility

[AI] Exited from the Connect nav start destination rather than the hardcoded jobs-list fragment, so the back-out behavior follows the nav graph's start destination (covering notification/redirect entries too).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

4 participants