Skip to content

Aj 656 joined study edits - #262

Merged
dz4va merged 4 commits into
developfrom
AJ-656-joined_study_edits
Jun 2, 2026
Merged

Aj 656 joined study edits#262
dz4va merged 4 commits into
developfrom
AJ-656-joined_study_edits

Conversation

@dz4va

@dz4va dz4va commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Changes

  • Preserve the original cr_user_id while storing study_user_id separately.
  • Make study enrollment write-once so later app links do not reopen the modal or overwrite the saved phone-number ID.
  • Fix Play Store deferred deeplink parsing so language, source, and campaign ID are extracted independently.

How to test

  • Through internal testing

Ref:

Summary by CodeRabbit

  • New Features

    • Dialog messages are now scrollable for improved readability.
    • Enhanced enrollment flow with improved language selection behavior.
  • Bug Fixes

    • Fixed deep-link enrollment handling to prevent duplicate processing.
    • Improved enrollment success dialog stability and auto-dismiss behavior.
  • Chores

    • Updated app version to 2.34.10.

Review Change Stack

dz4va added 4 commits May 28, 2026 15:25
…nk language handling

- Preserve the original cr_user_id while storing study_user_id separately.
- Make study enrollment write-once so later app links do not reopen the modal or overwrite the saved phone-number ID.
- Fix Play Store deferred deeplink parsing so language, source, and campaign ID are extracted independently.
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR coordinates version metadata updates (versionCode 73→83, versionName 2.34.3→2.34.10), manifest launch mode configuration, MainActivity deep-link enrollment flow refinements with state gating and intent clearing, enrollment success dialog lifecycle management, confirmation dialog scrolling UI, analytics event payload adjustments, and referrer parameter extraction logic.

Changes

Study Enrollment and Dialog Flow

Layer / File(s) Summary
App versioning and manifest launch mode configuration
app/build.gradle, app/src/main/AndroidManifest.xml
Version bump updates versionCode and versionName; MainActivity now declares singleTop launch mode and documentLaunchMode set to never.
Study enrollment deep-link handling with gating and state management
app/src/main/java/org/curiouslearning/container/MainActivity.java
MainActivity imports ScrollingMovementMethod, introduces isShowingEnrollmentSuccess state flag, adds handledStudyEnrollmentLink tracking to prevent duplicate enrollment flows; study_user_id intent parsing now gates against existing stored IDs, dismisses language popup on ID confirmation start, truncates long messages, clears intent data after handling, and updates language popup gating logic.
Enrollment success dialog lifecycle and auto-dismiss behavior
app/src/main/java/org/curiouslearning/container/MainActivity.java
showSuccessDialog method reworked to set/reset isShowingEnrollmentSuccess state, enforce non-cancelable behavior, dispatch onDismissAction at most once via guarded listener, and auto-dismiss after ~2 seconds.
Confirmation dialog scrolling UI and button interaction updates
app/src/main/res/layout/dialog_confirm_id.xml, app/src/main/java/org/curiouslearning/container/MainActivity.java
Dialog message TextView configured with maxHeight, vertical scrollbars, and overscroll behavior; MainActivity applies ScrollingMovementMethod to confirmation dialog message and updates confirm button handler to disable button, cancel pulse animations, and add early return check.
Analytics event payload and test updates
app/src/main/java/org/curiouslearning/container/firebase/AnalyticsUtils.java, app/src/test/java/org/curiouslearning/container/AnalyticsUtilsCustomEventsTest.java
logJoinedStudyEvent now sanitizes only studyUserId (not crUserId) and removes app_info.version parameter; test assertion updated to expect unsanitized cr_user_id.
Referrer parameter extraction and language parsing
app/src/main/java/org/curiouslearning/container/installreferrer/InstallReferrerManager.java
extractReferrerParameters replaces string-contains logic with URI parsing to extract language query parameter from deferred_deeplink.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • curiouslearning/CRcontainer#254: Heavily modifies MainActivity's deep-link and ID confirmation dialog flow with overlapping code-level changes to study_user_id handling and dialog_confirm_id.xml.
  • curiouslearning/CRcontainer#258: Overlaps with enrollment success dialog lifecycle changes including isShowingEnrollmentSuccess state tracking and custom success dialog behavior.
  • curiouslearning/CRcontainer#261: Directly related through MainActivity study enrollment and analytics cr_user_id sanitization/payload handling changes.

Suggested reviewers

  • amitsinghsutara
  • Rajesh1041

🐰 A deep link now hops with grace,
One dialog at a time in its place,
State flags guide the way,
Auto-dismiss ends the day,
Enrollment flows at a measured pace!

🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Tests & Lint & Coverage ⚠️ Warning No new tests added for major MainActivity enrollment/deeplink handling changes; no project linting found; test coverage cannot be verified (no report provided/70% threshold not configured). Add unit tests for MainActivity.handleIncomingIntent(), showConfirmIdDialog(), enrollment success behavior, and InstallReferrerManager deeplink parsing; configure lint; run and verify 70% coverage.
Title check ❓ Inconclusive The title 'Aj 656 joined study edits' is vague and lacks specificity about the actual changes; while it references a JIRA-like key (AJ-656), it does not clearly convey what 'joined study edits' means or describe the main technical changes. Improve the title to be more descriptive of the core changes, such as 'AJ-656: Preserve cr_user_id and make study enrollment write-once' to better reflect the key objectives of the PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ 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 AJ-656-joined_study_edits

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 and usage tips.

@dz4va
dz4va requested a review from amitsinghsutara May 29, 2026 08:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/test/java/org/curiouslearning/container/AnalyticsUtilsCustomEventsTest.java (1)

177-177: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stale assertion will fail: app_info.version was removed from the joined_study payload.

logJoinedStudyEvent no longer puts app_info.version into the bundle, so b.getString("app_info.version") returns null and this assertion fails. Remove it to match the updated payload.

💚 Proposed fix
             assertEquals("Nepali", b.getString("cr_language"));
-            assertEquals("2.34.3", b.getString("app_info.version"));
             assertEquals("12345", b.getString("cr_user_id"));
🤖 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/test/java/org/curiouslearning/container/AnalyticsUtilsCustomEventsTest.java`
at line 177, The test contains a stale assertion expecting app_info.version in
the joined_study payload; remove the assertEquals("2.34.3",
b.getString("app_info.version")) from AnalyticsUtilsCustomEventsTest (the test
that calls logJoinedStudyEvent / inspects the joined_study bundle) so the test
matches the updated payload that no longer includes app_info.version.
app/src/main/java/org/curiouslearning/container/MainActivity.java (1)

508-543: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix dialog lifecycle cleanup in MainActivity to avoid window leaks.

  • showSuccessDialog() creates a local Dialog and schedules a 2s Handler.postDelayed dismissal; onPause() currently only pauses animations/removes overlay callbacks and never dismisses/cancels this dialog, so the window can leak if the activity is destroyed during the delay.
  • showConfirmIdDialog() is also activity-scoped and never dismissed from onPause/onDestroy (no onDestroy override present), so it can stay open indefinitely.
  • Apply the same lifecycle-style cleanup used in showLanguagePopup() (guarding with isFinishing()/isDestroyed() for showing) and add explicit dismissal + cancel the pending delayed runnable(s) in onPause (or onStop).
🤖 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/main/java/org/curiouslearning/container/MainActivity.java` around
lines 508 - 543, The showSuccessDialog and showConfirmIdDialog create local
Dialogs and delayed Runnables that can leak windows; refactor by promoting the
local variables to activity fields (e.g., successDialog, successHandler,
successDismissRunnable, confirmIdDialog) so they can be accessed from lifecycle
methods, guard any show calls with isFinishing()/isDestroyed() like
showLanguagePopup does, and in onPause()/onStop()/onDestroy explicitly dismiss()
and cancel() those dialogs and call
successHandler.removeCallbacks(successDismissRunnable) (and similar for
confirmIdDialog) and null out the references and update
isShowingEnrollmentSuccess/dismissActionDelivered accordingly to prevent
double-callbacks and leaks.
🧹 Nitpick comments (1)
app/src/main/java/org/curiouslearning/container/installreferrer/InstallReferrerManager.java (1)

303-310: 💤 Low value

Consider parsing the deferred deeplink once and reusing the Uri object.

The deeplink is parsed at line 305 and again at line 318. Parsing once and reusing the Uri object would eliminate the duplicate work and improve efficiency slightly.

♻️ Proposed refactor to eliminate duplicate parsing
-        String deferredLanguage = "";
-        if (!TextUtils.isEmpty(deeplink)) {
-            Uri deeplinkUri = Uri.parse(deeplink);
-            String language = deeplinkUri.getQueryParameter("language");
-            if (!TextUtils.isEmpty(language)) {
-                deferredLanguage = language;
-            }
-        }
-        callback.onReferrerReceived(deferredLanguage, referrerUrl);
+        String deferredLanguage = "";
+        Uri deeplinkUri = null;
+        if (!TextUtils.isEmpty(deeplink)) {
+            deeplinkUri = Uri.parse(deeplink);
+            String language = deeplinkUri.getQueryParameter("language");
+            if (!TextUtils.isEmpty(language)) {
+                deferredLanguage = language;
+            }
+        }
+        callback.onReferrerReceived(deferredLanguage, referrerUrl);
         
         String source = null;
         String campaign_id = null;
         
         // First, try to extract source and campaign_id from deferred_deeplink (highest priority)
         if (deeplink != null && !deeplink.isEmpty()) {
-            Uri deeplinkUri = Uri.parse(deeplink);
+            if (deeplinkUri == null) {
+                deeplinkUri = Uri.parse(deeplink);
+            }
             source = deeplinkUri.getQueryParameter("source");
🤖 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/main/java/org/curiouslearning/container/installreferrer/InstallReferrerManager.java`
around lines 303 - 310, In InstallReferrerManager, avoid parsing the same
deeplink string twice: after confirming deeplink is not empty, parse it once
into a local Uri (e.g., deeplinkUri) and reuse that Uri when extracting the
"language" query parameter and anywhere else the code currently reparses
deeplink; ensure you keep the TextUtils.isEmpty checks and null-safety around
deeplinkUri.getQueryParameter calls so deferredLanguage assignment and
subsequent logic use the single parsed Uri instance.
🤖 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.

Inline comments:
In `@app/src/main/AndroidManifest.xml`:
- Around line 52-54: The manifest should prevent creating a second MainActivity
during enrollment deep-links: change android:launchMode from "singleTop" to
"singleTask" for MainActivity so incoming study_user_id intents always route to
the existing activity (triggering onNewIntent and handleIncomingIntent) instead
of creating a new instance that can re-run onCreate and bypass the instance
guards (isHandlingIdConfirmation / isShowingEnrollmentSuccess) before
prefs[AnalyticsUtils.STUDY_USER_ID] is persisted; keep
android:documentLaunchMode="never" and ensure handleIncomingIntent remains
invoked from onNewIntent.

---

Outside diff comments:
In `@app/src/main/java/org/curiouslearning/container/MainActivity.java`:
- Around line 508-543: The showSuccessDialog and showConfirmIdDialog create
local Dialogs and delayed Runnables that can leak windows; refactor by promoting
the local variables to activity fields (e.g., successDialog, successHandler,
successDismissRunnable, confirmIdDialog) so they can be accessed from lifecycle
methods, guard any show calls with isFinishing()/isDestroyed() like
showLanguagePopup does, and in onPause()/onStop()/onDestroy explicitly dismiss()
and cancel() those dialogs and call
successHandler.removeCallbacks(successDismissRunnable) (and similar for
confirmIdDialog) and null out the references and update
isShowingEnrollmentSuccess/dismissActionDelivered accordingly to prevent
double-callbacks and leaks.

In
`@app/src/test/java/org/curiouslearning/container/AnalyticsUtilsCustomEventsTest.java`:
- Line 177: The test contains a stale assertion expecting app_info.version in
the joined_study payload; remove the assertEquals("2.34.3",
b.getString("app_info.version")) from AnalyticsUtilsCustomEventsTest (the test
that calls logJoinedStudyEvent / inspects the joined_study bundle) so the test
matches the updated payload that no longer includes app_info.version.

---

Nitpick comments:
In
`@app/src/main/java/org/curiouslearning/container/installreferrer/InstallReferrerManager.java`:
- Around line 303-310: In InstallReferrerManager, avoid parsing the same
deeplink string twice: after confirming deeplink is not empty, parse it once
into a local Uri (e.g., deeplinkUri) and reuse that Uri when extracting the
"language" query parameter and anywhere else the code currently reparses
deeplink; ensure you keep the TextUtils.isEmpty checks and null-safety around
deeplinkUri.getQueryParameter calls so deferredLanguage assignment and
subsequent logic use the single parsed Uri instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f29ec28-6d78-4880-af06-cb0e8574f9e6

📥 Commits

Reviewing files that changed from the base of the PR and between 2c6dfcf and a950639.

📒 Files selected for processing (7)
  • app/build.gradle
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/org/curiouslearning/container/MainActivity.java
  • app/src/main/java/org/curiouslearning/container/firebase/AnalyticsUtils.java
  • app/src/main/java/org/curiouslearning/container/installreferrer/InstallReferrerManager.java
  • app/src/main/res/layout/dialog_confirm_id.xml
  • app/src/test/java/org/curiouslearning/container/AnalyticsUtilsCustomEventsTest.java

Comment on lines +52 to 54
android:launchMode="singleTop"
android:documentLaunchMode="never"
android:exported="true">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect MainActivity onCreate/onNewIntent and how enrollment state (handledStudyEnrollmentLink,
# isHandlingIdConfirmation, stored study_user_id) is read/written to confirm cross-instance safety.
fd MainActivity.java --exec rg -nP -C3 '(onCreate|onNewIntent|handleIncomingIntent|getIntent\(\)|isHandlingIdConfirmation|isShowingEnrollmentSuccess)' {}
# Check how WebApp is launched relative to MainActivity (task affinity / flags).
fd WebApp.java --exec rg -nP -C3 '(startActivity|Intent|FLAG_ACTIVITY)' {}

Repository: curiouslearning/CRcontainer

Length of output: 7322


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect AndroidManifest activity declarations around the reported lines and identify MainActivity/WebApp attributes.
MANIFEST="app/src/main/AndroidManifest.xml"
echo "== Manifest: Main/WebApp declarations around lines 35-120 =="
nl -ba "$MANIFEST" | sed -n '35,120p'

echo "== Manifest: find MainActivity and WebApp activity blocks =="
rg -n "activity|MainActivity|WebApp" "$MANIFEST"

# 2) Dump full handleIncomingIntent for cross-instance reasoning.
MAIN="app/src/main/java"
# Find MainActivity.java path(s)
echo "== Locating MainActivity.java =="
fd -a "MainActivity.java" "$MAIN" || true
MAIN_FILE="$(fd -a "MainActivity.java" "$MAIN" | head -n 1)"
echo "Using: $MAIN_FILE"

echo "== MainActivity.handleIncomingIntent (expanded context) =="
# Print from handleIncomingIntent declaration through nearby logic.
python3 - <<'PY'
import re,sys,subprocess,os, pathlib, json, textwrap
main_file = subprocess.check_output(["bash","-lc", "fd -a 'MainActivity.java' app/src/main/java | head -n 1"], text=True).strip()
print("Main file:", main_file)
# Read file
with open(main_file,'r',encoding='utf-8') as f:
    lines = f.readlines()
# find handleIncomingIntent method bounds by matching signature and braces depth
start=None
sig_pat=re.compile(r'\bvoid\s+handleIncomingIntent\s*\(\s*Intent\s+\w*\s*\)')
for i,l in enumerate(lines):
    if 'handleIncomingIntent' in l and re.search(r'handleIncomingIntent\s*\(', l):
        if start is None and 'Intent' in l:
            start=i
            break
if start is None:
    # fallback: locate exact method name
    for i,l in enumerate(lines):
        if re.search(r'\bhandleIncomingIntent\s*\(', l):
            start=i
            break
if start is None:
    print("Could not locate handleIncomingIntent")
    sys.exit(0)

# Find braces balance from first '{' after start
j=start
while j < len(lines) and '{' not in lines[j]:
    j+=1
if j>=len(lines):
    print("Could not find opening brace")
    sys.exit(0)
brace=0
end=None
for k in range(j, len(lines)):
    brace += lines[k].count('{')
    brace -= lines[k].count('}')
    if brace==0:
        end=k
        break
print(f"Method lines: {start+1}-{end+1}")
print("".join(lines[start:end+1]))
PY

# 3) Find where STUDY_USER_ID is written (to evaluate write-once gating across instances).
echo "== Where STUDY_USER_ID is stored/updated =="
rg -n "STUDY_USER_ID|STUDY_USER|study_user_id" app/src/main/java

# 4) Inspect WebApp start usage (at least where MainActivity launches it) to see flags/task behavior.
echo "== WebApp start usage (MainActivity and others) =="
rg -n "class WebApp|startActivity\\(|WebApp" app/src/main/java

Repository: curiouslearning/CRcontainer

Length of output: 193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

MANIFEST="app/src/main/AndroidManifest.xml"

echo "== Tools =="
command -v awk >/dev/null && echo "awk ok"
command -v sed >/dev/null && echo "sed ok"
command -v rg >/dev/null && echo "rg ok"
command -v fd >/dev/null && echo "fd ok"
command -v python3 >/dev/null && echo "python3 ok"

echo
echo "== Manifest lines around 45-70 (to include launchMode/documentLaunchMode/exported) =="
awk 'NR>=45 && NR<=70 {printf "%d:%s\n", NR, $0}' "$MANIFEST"

echo
echo "== Manifest: find MainActivity and WebApp activity blocks =="
rg -n "activity|MainActivity|WebApp|launchMode|documentLaunchMode|exported" "$MANIFEST"

echo
echo "== Locate MainActivity.java under app/src/main/java =="
fd -a -t f "MainActivity.java" app/src/main/java || true
MAIN_FILE="$(fd -a -t f "MainActivity.java" app/src/main/java | head -n 1 || true)"
echo "Main file: ${MAIN_FILE:-<not found>}"

echo
echo "== MainActivity.handleIncomingIntent implementation (full method) =="
python3 - <<'PY'
import re,sys,subprocess,os, pathlib

# Find MainActivity.java
main_files = subprocess.check_output(["bash","-lc", "fd -a -t f 'MainActivity.java' app/src/main/java | head -n 1"], text=True).strip().splitlines()
main_file = main_files[0] if main_files and main_files[0] else None
if not main_file:
    print("MainActivity.java not found")
    sys.exit(0)

p = pathlib.Path(main_file)
text = p.read_text(encoding="utf-8", errors="ignore")
lines = text.splitlines(True)

# Find method signature line
sig_re = re.compile(r'\bvoid\s+handleIncomingIntent\s*\(\s*Intent\b')
start = None
for i,l in enumerate(lines):
    if sig_re.search(l):
        start = i
        break
if start is None:
    # fallback: any 'handleIncomingIntent('
    for i,l in enumerate(lines):
        if 'handleIncomingIntent' in l and '(' in l:
            start = i
            break

if start is None:
    print("handleIncomingIntent not found")
    sys.exit(0)

# find opening brace
j = start
while j < len(lines) and '{' not in lines[j]:
    j += 1
if j >= len(lines):
    print("Opening brace not found")
    sys.exit(0)

brace = 0
end = None
for k in range(j, len(lines)):
    brace += lines[k].count('{')
    brace -= lines[k].count('}')
    if brace == 0:
        end = k
        break

print(f"File: {main_file}")
print(f"Method lines: {start+1}-{end+1}")
print(''.join(lines[start:end+1]))
PY

echo
echo "== Where study_user_id / STUDY_USER_ID is read/written in MainActivity.java (and related prefs keys) =="
if [ -n "${MAIN_FILE:-}" ] && [ -f "$MAIN_FILE" ]; then
  rg -n "STUDY_USER_ID|study_user_id|STUDY_USER|handledStudyEnrollmentLink|isHandlingIdConfirmation|isShowingEnrollmentSuccess" "$MAIN_FILE"
fi

echo
echo "== WebApp startActivity usage / intent flags in Java =="
rg -n "startActivity\\(|new Intent\\(|WebApp" app/src/main/java

Repository: curiouslearning/CRcontainer

Length of output: 29240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

MAIN="app/src/main/java/org/curiouslearning/container/MainActivity.java"

echo "== Show MainActivity onCreate/onNewIntent (for intent handling entrypoints) =="
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("app/src/main/java/org/curiouslearning/container/MainActivity.java")
text = p.read_text(encoding="utf-8", errors="ignore").splitlines(True)

def print_method(name):
    # locate signature like 'protected void onCreate(' or 'protected void onNewIntent('
    for i,l in enumerate(text):
        if re.search(r'\b%s\s*\(' % re.escape(name), l):
            # locate method body braces balance
            start=i
            # find first '{' after start
            j=start
            while j < len(text) and '{' not in text[j]:
                j+=1
            brace=0
            end=None
            for k in range(j, len(text)):
                brace += text[k].count('{')
                brace -= text[k].count('}')
                if brace==0:
                    end=k
                    break
            print(f"\n--- {name} lines {start+1}-{end+1} ---")
            print(''.join(text[start:end+1]))
            return True
    return False

print_method("onCreate")
print_method("onNewIntent")
PY

echo
echo "== Show showConfirmIdDialog around prefs write (editor.putString) =="
# print a focused window around the editor.putString and related gating flags
rg -n "editor\\.putString\\(AnalyticsUtils\\.STUDY_USER_ID" -n "$MAIN"
LINE=$(rg -n "editor\\.putString\\(AnalyticsUtils\\.STUDY_USER_ID" "$MAIN" | head -n1 | cut -d: -f1)
START=$((LINE-40))
END=$((LINE+40))
awk -v s=$START -v e=$END 'NR>=s && NR<=e {print NR ":" $0}' "$MAIN"

echo
echo "== showConfirmIdDialog full gating checks (storedStudyUserId + isHandlingIdConfirmation) =="
# show from method start to end by printing region around 'showConfirmIdDialog('
# first find method signature line
METHOD_LINE=$(rg -n "showConfirmIdDialog\\(" "$MAIN" | head -n1 | cut -d: -f1)
START=$((METHOD_LINE-10))
END=$((METHOD_LINE+140))
awk -v s=$START -v e=$END 'NR>=s && NR<=e {print NR ":" $0}' "$MAIN"

echo
echo "== DeepLinkHelper: how it starts MainActivity (flags/task-affinity) =="
rg -n "class DeepLinkHelper|new Intent\\(activity, MainActivity\\.class\\)|startActivity\\(mainIntent\\)" app/src/main/java/org/curiouslearning/container/utilities/DeepLinkHelper.java
awk 'NR>=1 && NR<=120 {print NR ":" $0}' app/src/main/java/org/curiouslearning/container/utilities/DeepLinkHelper.java

echo
echo "== AndroidManifest: show WebApp activity block (launchMode/task flags if any) =="
awk 'NR>=20 && NR<=60 {print NR ":" $0}' app/src/main/AndroidManifest.xml

Repository: curiouslearning/CRcontainer

Length of output: 25574


Fix enrollment deep-link gating: singleTop doesn’t prevent a second MainActivity instance from showing the confirm dialog while WebApp is foreground.

android:launchMode="singleTop"
android:documentLaunchMode="never"
android:exported="true">

study_user_id deep links are handled from onCreate and onNewIntent via handleIncomingIntent. With launchMode="singleTop", onNewIntent only fires when MainActivity is already the top activity; if .WebApp is in the foreground, a new MainActivity instance can be created and onCreate will re-run the enrollment handling.

The “in-flight” guards (isHandlingIdConfirmation / isShowingEnrollmentSuccess) are instance fields, while the durable write-once check (prefs[AnalyticsUtils.STUDY_USER_ID]) is only written after the user taps confirm in showConfirmIdDialog. So there’s a window where a second instance can show the confirmation dialog again before the first instance persists STUDY_USER_ID.

documentLaunchMode="never" looks correct.

🤖 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/main/AndroidManifest.xml` around lines 52 - 54, The manifest should
prevent creating a second MainActivity during enrollment deep-links: change
android:launchMode from "singleTop" to "singleTask" for MainActivity so incoming
study_user_id intents always route to the existing activity (triggering
onNewIntent and handleIncomingIntent) instead of creating a new instance that
can re-run onCreate and bypass the instance guards (isHandlingIdConfirmation /
isShowingEnrollmentSuccess) before prefs[AnalyticsUtils.STUDY_USER_ID] is
persisted; keep android:documentLaunchMode="never" and ensure
handleIncomingIntent remains invoked from onNewIntent.

@dz4va
dz4va merged commit 8d2eeb3 into develop Jun 2, 2026
1 check passed
@amitsinghsutara
amitsinghsutara deleted the AJ-656-joined_study_edits branch June 18, 2026 04:20
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.

2 participants