Skip to content

ADFA-4326: Guard uninitialized projectPath on process-death recreation#1414

Merged
Daniel-ADFA merged 4 commits into
stagefrom
ADFA-4326-editoractivity-uninitialized-projectpath
Jun 25, 2026
Merged

ADFA-4326: Guard uninitialized projectPath on process-death recreation#1414
Daniel-ADFA merged 4 commits into
stagefrom
ADFA-4326-editoractivity-uninitialized-projectpath

Conversation

@fryanpan

@fryanpan fryanpan commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Jira Ticket: https://appdevforall.atlassian.net/browse/ADFA-4326
Sentry Issue: https://appdevforall-inc-9p.sentry.io/issues/APPDEVFORALL-QZ

Reproduction Details

After process death the OS can recreate EditorActivity without routing through MainActivity, leaving ProjectManagerImpl's lateinit projectPath unset. EditorActivity reads projectDirPath during onCreate (via EditorViewModel.getProjectName → projectDir → projectDirPath); the getter delegated straight to the lateinit and threw UninitializedPropertyAccessException, failing activity start.

Stack Trace

RuntimeException: Unable to start activity ComponentInfo{….EditorActivityKt}:
  kotlin.UninitializedPropertyAccessException: lateinit property projectPath has not been initialized
    at com.itsaky.androidide.projects.ProjectManagerImpl.getProjectPath(SourceFile:7)
    at com.itsaky.androidide.projects.ProjectManagerImpl.getProjectDirPath(SourceFile)
    at com.itsaky.androidide.viewmodel.EditorViewModel.getProjectName(SourceFile:6)
    at com.itsaky.androidide.activities.editor.BaseEditorActivity.onCreate(SourceFile:159)
    at com.itsaky.androidide.activities.editor.ProjectHandlerActivity.onCreate(...)
    at com.itsaky.androidide.activities.editor.EditorHandlerActivity.onCreate(SourceFile:8)

User Steps

User steps leading up to crash, based on Sentry breadcrumbs:

  • The process was recreated while the device was still locked (breadcrumb: BaseApplication: Creating safe context because user is not unlocked → direct-boot / credential-protected storage), then EditorActivity/EditorSidebarFragment are recreated with no project context → crash on first projectPath access.

Was able to reproduce in a unit test?

Yes.
ProjectManagerImplUninitializedPathTest (:subprojects:projects, Robolectric) constructs a fresh ProjectManagerImpl (projectPath never set = the process-death state) and reads projectDirPath/projectDir. Baseline: 2/3 cases FAIL with UninitializedPropertyAccessException (the 3rd, initialized, passes — not a tautology). Branch: 3/3 pass.

What Was Fixed

Guarded getter — get() = if (this::projectPath.isInitialized) projectPath else "" — and EditorViewModel/BaseEditorActivity route back to MainActivity instead of crashing when there's no project context.

Testing

:subprojects:projects:testV8DebugUnitTest → 3/3 green (red on baseline). Local CodeRabbit review: no findings. Reviewer notes (local): the finish()+return only exits BaseEditorActivity.onCreate — subclass onCreate (services/LSP/initializeProject) still runs on the finishing activity; consider short-circuiting earlier / isFinishing guards. The EditorViewModel blank-path guard is redundant with the getter fix. Recommend an android-qa smoke of the process-death → editor-recreation path before close.


Fixes APPDEVFORALL-QZ

@fryanpan fryanpan marked this pull request as ready for review June 19, 2026 11:21
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough
  • Fixed a crash in EditorActivity after process-death recreation by preventing ProjectManagerImpl.projectDirPath from throwing when projectPath was never initialized.
  • BaseEditorActivity now attempts to restore the project path from saved state, the launch intent, or the last opened project, and safely routes users back to MainActivity if no valid project context is available.
  • EditorViewModel now handles missing project context by returning an empty project name instead of dereferencing an unset project directory.
  • Added Robolectric coverage for the uninitialized-path case and confirmed the initialized-path behavior still works.

Risks / best-practice notes:

  • Returning an empty string for an uninitialized project path may hide configuration issues if callers do not explicitly handle the “no project loaded” state.
  • Redirecting to MainActivity on missing context improves stability, but it may surprise users if process recreation happens unexpectedly and their editor session cannot be restored.

Walkthrough

The PR makes project-path restoration resilient during editor recreation, avoids uninitialized project-path crashes in project accessors, and updates editor name lookup to return an empty string when no project path is available.

Changes

Project path recovery

Layer / File(s) Summary
Project path access and coverage
subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt, subprojects/projects/src/test/java/com/itsaky/androidide/projects/ProjectManagerImplUninitializedPathTest.kt
projectDirPath returns "" until projectPath is initialized, and the new Robolectric test checks blank and initialized reads.
Editor startup restoration and name lookup
app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt, app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt
BaseEditorActivity restores the project path from saved state, intent, or GeneralPreferences, then finishes through MainActivity when projectDirPath is blank; EditorViewModel.getProjectName() returns "" for the blank-path case.

Sequence Diagram(s)

sequenceDiagram
  participant BaseEditorActivity
  participant GeneralPreferences
  participant ProjectManagerImpl
  participant MainActivity
  BaseEditorActivity->>GeneralPreferences: read lastOpenedProject when saved state and intent are blank
  BaseEditorActivity->>ProjectManagerImpl: assign restored projectPath
  BaseEditorActivity->>ProjectManagerImpl: read projectDirPath after super.onCreate
  alt projectDirPath is blank
    BaseEditorActivity->>MainActivity: navigate back
    BaseEditorActivity->>BaseEditorActivity: finish()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • jatezzz
  • Daniel-ADFA

Poem

A bunny hopped through saved-state hay,
and found the project path today.
If the burrow’s blank, we turn around—
hop home to MainActivity ground.
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 clearly matches the main fix: guarding an uninitialized projectPath during process-death recreation.
Description check ✅ Passed The description directly explains the crash, fix, and added regression test for the same issue.
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 ADFA-4326-editoractivity-uninitialized-projectpath

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.

fryanpan and others added 3 commits June 19, 2026 09:58
Sentry APPDEVFORALL-QZ. Restore ProjectManagerImpl.projectPath from
savedInstanceState -> intent PROJECT_PATH extra -> lastOpenedProject;
guard projectDirPath getter; route back to MainActivity if still unset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds ProjectManagerImplUninitializedPathTest covering the root cause of the
EditorActivity crash after process-death recreation: reading
ProjectManagerImpl.projectDirPath while the lateinit projectPath is unset.

Pre-fix the getter delegated directly to the lateinit (get() = projectPath),
throwing UninitializedPropertyAccessException; the fix guards with
isInitialized and returns "" so the caller can route back to MainActivity.

The test fails (UninitializedPropertyAccessException) on the pre-fix baseline
and passes on the fix branch. A third case asserts the getter still reflects an
assigned path, guarding against an always-blank regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fryanpan fryanpan force-pushed the ADFA-4326-editoractivity-uninitialized-projectpath branch from c2b5184 to ea4b254 Compare June 19, 2026 16:58

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt (1)

645-652: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the "PROJECT_PATH" intent-extra key into a shared constant.

This literal is the producer/consumer contract with MainActivity (putExtra("PROJECT_PATH", ...) at MainActivity.kt:427). Duplicating the raw string in both places risks silent drift if either side is renamed. Consider a single const val referenced by both.

🤖 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/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt`
around lines 645 - 652, The PROJECT_PATH intent extra is duplicated as a raw
string in the editor restore flow, which can drift from the sender in
MainActivity. Introduce a shared constant for the intent-extra key and update
BaseEditorActivity’s restoredProjectPath lookup and MainActivity’s putExtra call
to use that symbol consistently, so the producer/consumer contract stays
centralized and easy to rename safely.
🤖 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/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt`:
- Around line 645-652: The PROJECT_PATH intent extra is duplicated as a raw
string in the editor restore flow, which can drift from the sender in
MainActivity. Introduce a shared constant for the intent-extra key and update
BaseEditorActivity’s restoredProjectPath lookup and MainActivity’s putExtra call
to use that symbol consistently, so the producer/consumer contract stays
centralized and easy to rename safely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 248653b1-f8b7-4793-92e8-c7b478044b1c

📥 Commits

Reviewing files that changed from the base of the PR and between 97e5c06 and 9f8ab42.

📒 Files selected for processing (4)
  • app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt
  • subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt
  • subprojects/projects/src/test/java/com/itsaky/androidide/projects/ProjectManagerImplUninitializedPathTest.kt

@Daniel-ADFA Daniel-ADFA merged commit 0d4c831 into stage Jun 25, 2026
2 checks passed
@Daniel-ADFA Daniel-ADFA deleted the ADFA-4326-editoractivity-uninitialized-projectpath branch June 25, 2026 15:25
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.

3 participants