Skip to content

ADFA- 3701 Protect against project name collisions during project rename action#1272

Merged
hal-eisen-adfa merged 10 commits intostagefrom
ADFA-3701-crash-when-rename-collision
May 6, 2026
Merged

ADFA- 3701 Protect against project name collisions during project rename action#1272
hal-eisen-adfa merged 10 commits intostagefrom
ADFA-3701-crash-when-rename-collision

Conversation

@hal-eisen-adfa
Copy link
Copy Markdown
Collaborator

validateProjectName now also rejects names matching another project
Disables the Rename button and shows an error message (which is in strings.xml)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Release Notes - ADFA-3701: Project Name Collision Prevention

Features & Improvements

  • Project Name Collision Protection: Project rename validation now prevents renaming a project to a name that already exists in the project list
  • Enhanced Input Validation: Rename dialog validation now rejects names containing:
    • Path separators (/, \)
    • Directory traversal patterns (., ..)
    • Empty names
  • Improved User Feedback: Rename button is disabled when validation fails, preventing invalid rename attempts
  • Rename Status Monitoring: New renameStatus SharedFlow in RecentProjectsViewModel to track rename operation success/failure with automatic error handling and project list reload on failure
  • Filesystem Rollback Support: Database update failures now trigger automatic filesystem rollback to restore the project to its original path

Known Risks & Best Practice Violations

  • Race Condition - Name Collision Window: The name existence check occurs at validation time, but the actual filesystem rename happens later when the user clicks the button. Another user could create a project with the same name between validation and actual rename execution.

  • Thread Safety Issue: allProjects list is not thread-safe. It's accessed from the UI thread in projectNameExists() validation while being updated from Dispatchers.IO in loadProjects(). Consider adding @Volatile annotation or using Mutex for thread-safe access.

  • Incomplete Rollback Coverage: If the filesystem rename fails, the database is correctly left unchanged. However, if the filesystem rename succeeds but the database update fails, a rollback is attempted but not guaranteed to succeed, risking filesystem/database inconsistency.

  • Stale In-Memory Cache: The projectNameExists() check relies on allProjects which is only refreshed via loadProjects(). If the list hasn't been recently loaded, it may not reflect current project state, potentially allowing duplicate names.

  • Missing Unit Tests: No test coverage mentioned for the new rename collision prevention or validation logic.

Walkthrough

Added a project rename flow: adapter accepts a name-existence checker and validates against the old name; RenamedFile includes oldPath; Fragment observes renameStatus; ViewModel exposes renameStatus, projectNameExists, and a 4-arg updateProject that updates DB and attempts filesystem rollback on failure.

Changes

Project Rename Flow

Layer / File(s) Summary
Data Shape
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
RenamedFile constructor changed to (oldName, newName, oldPath, newPath).
Validation / UI
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
Rename dialog validation now accepts oldName, calls nameExists(newName) only when newName != oldName, and toggles positive button; TextWatcher and initial validation pass oldName.
Adapter Wiring
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
Adapter constructor adds nameExists: (String) -> Boolean (private property); rename flow computes oldPath/newPath and invokes callback with RenamedFile(oldName,newName,oldPath,newPath).
Fragment Integration
app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
Adapter instantiation adds nameExists = viewModel::projectNameExists; observeRenameStatus() is registered in onViewCreated to collect rename outcomes and reload on failure.
ViewModel Core
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt
Added _renameStatus / renameStatus: SharedFlow<Boolean>, projectNameExists(name: String): Boolean, updateProject(renamedFile: RecentProjectsAdapter.RenamedFile) wrapper, and a 4‑arg updateProject(oldName, newName, oldLocation, newLocation) that updates DB, renames filesystem, and attempts rollback + emits rename status.
Observers / Error Handling
app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
New observeRenameStatus() collects renameStatus, flashes an error and triggers viewModel.loadProjects() when rename fails.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as "User"
    participant UI as "RecentProjectsFragment\n(Dialog via Adapter)"
    participant Adapter as "RecentProjectsAdapter"
    participant VM as "RecentProjectsViewModel"
    participant DB as "Database"
    participant FS as "Filesystem"

    User->>UI: Trigger rename (open dialog)
    UI->>Adapter: Show dialog, provide nameExists callback
    Adapter->>Adapter: Validate input (calls nameExists via VM bound callback)
    User->>Adapter: Submit new name
    Adapter->>VM: updateProject(RenamedFile(oldName,newName,oldPath,newPath))
    VM->>DB: update project record (oldName -> newName, oldLocation -> newLocation)
    DB-->>VM: success
    VM->>FS: rename filesystem path (oldPath -> newPath)
    FS-->>VM: success
    VM-->>UI: emit renameStatus(true)
    Note over VM,UI: On DB failure, VM attempts FS rollback and emits renameStatus(false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • appdevforall/CodeOnTheGo#1111: Touches recent-projects logic and ViewModel recents handling — similar areas (rename/status and persistence changes).

Suggested reviewers

  • itsaky-adfa
  • Daniel-ADFA
  • jomen-adfa

Poem

"I hopped through folders, brave and neat,
I nudged old names to make them sweet.
If paths go sideways, I mend the track —
A twitch, a hop, I bounce it back. 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: protecting against project name collisions during rename operations, which matches the core objective of the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining that validateProjectName rejects duplicate names, disables the Rename button, and shows an error message.
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-3701-crash-when-rename-collision

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt`:
- Around line 289-293: The duplicate-name check in RecentProjectsAdapter (the
conditional using projects.any { it.name == newName } that sets
inputLayout.error and disables positiveButton) only inspects the filtered
adapter list so a hidden project can be missed; update the validation to check
the unfiltered/full project set instead (e.g., use the ViewModel's unfiltered
collection such as viewModel.allProjects.any { it.name == newName } or call a
DAO/repository existence method like projectDao.existsByName(newName)) and keep
the rest of the UI logic (setting inputLayout.error and
positiveButton.isEnabled) the same.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78efbec4-d668-4eb1-afa7-4a4e59a5edb2

📥 Commits

Reviewing files that changed from the base of the PR and between a4c3b56 and c882778.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt

Comment thread app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (1)
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt (1)

230-235: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Trim newName before validating and renaming.

The text from the input is consumed verbatim:

  • Line 230 / Line 271: binding.textinputEdittext.text.toString()
  • Line 285 newName.isEmpty()" " passes this check
  • Line 290 nameExists(newName)"MyProj " (trailing space) does not collide with existing "MyProj"
  • Line 231 builds newPath directly from newName, so the on-disk directory ends up with leading/trailing whitespace, which is likely to cause surprises later (lookups, comparisons, shell tooling).

Trim once at the source and reuse the trimmed value for both validation and the rename.

🛡️ Suggested change
         binding.textinputEdittext.addTextChangedListener(object : TextWatcher {
             override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
             override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
             override fun afterTextChanged(s: Editable?) {
-                validateProjectName(binding.textinputLayout, s.toString(), oldName, dialog)
+                validateProjectName(binding.textinputLayout, s.toString().trim(), oldName, dialog)
             }
         })

         validateProjectName(
             binding.textinputLayout,
-            binding.textinputEdittext.text.toString(),
+            binding.textinputEdittext.text.toString().trim(),
             oldName,
             dialog
         )

And at line 230:

-            val newName = binding.textinputEdittext.text.toString()
+            val newName = binding.textinputEdittext.text.toString().trim()

Also applies to: 265-274, 285-294

🤖 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/adapters/RecentProjectsAdapter.kt`
around lines 230 - 235, Trim the user input once when reading it (replace
binding.textinputEdittext.text.toString() with a trimmed value) and use that
trimmed value for all subsequent checks and operations: validations like
newName.isEmpty(), nameExists(newName), path assembly (used in
project.rename(newPath)), and onFileRenamed(RenamedFile(...)). This ensures
whitespace is removed for comparisons and the filesystem rename; update all
occurrences (including the blocks around the current newName creation, the
validation paths, and the nameExists checks) to reference the single trimmed
variable.
🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt (1)

58-59: ⚡ Quick win

Consider matching _deletionStatus' replay buffer.

_deletionStatus is declared with replay = 1 (Line 55) so a result emitted while the fragment is being recreated (e.g. configuration change) is still delivered to the new collector. _renameStatus uses the default replay = 0, so a rename failure that races with a recreation would be lost and the user would not see the error toast. Aligning the two flows would make rename feedback as resilient as deletion feedback.

♻️ Suggested change
-    private val _renameStatus = MutableSharedFlow<Boolean>()
+    private val _renameStatus = MutableSharedFlow<Boolean>(replay = 1)
     val renameStatus = _renameStatus.asSharedFlow()
🤖 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/viewmodel/RecentProjectsViewModel.kt`
around lines 58 - 59, The _renameStatus flow uses a MutableSharedFlow with the
default replay=0, so rename results can be lost across fragment recreation;
change the declaration of _renameStatus to match _deletionStatus by constructing
it with replay = 1 (keeping renameStatus = _renameStatus.asSharedFlow()) so a
rename result emitted during configuration changes is replayed to new
collectors; update any related comments if present.
🤖 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/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Around line 201-219: The DB update can fail after the disk rename, leaving DB
and filesystem out of sync; modify updateProject to accept the original path
(oldLocation) in addition to newName/newLocation (or change the caller to pass
old path), and in the catch block attempt to revert the filesystem rename by
calling File(newLocation).renameTo(File(oldLocation)) on Dispatchers.IO, log
whether the rollback succeeded or failed, then emit false; alternatively, move
the disk rename into updateProject so the VM performs the filesystem rename and
can roll it back if DAO calls throw. Ensure you reference updateProject,
recentProjectDao.updateNameAndLocation, recentProjectDao.updateLastModified and
the catch block for the rollback logic.

---

Outside diff comments:
In `@app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt`:
- Around line 230-235: Trim the user input once when reading it (replace
binding.textinputEdittext.text.toString() with a trimmed value) and use that
trimmed value for all subsequent checks and operations: validations like
newName.isEmpty(), nameExists(newName), path assembly (used in
project.rename(newPath)), and onFileRenamed(RenamedFile(...)). This ensures
whitespace is removed for comparisons and the filesystem rename; update all
occurrences (including the blocks around the current newName creation, the
validation paths, and the nameExists checks) to reference the single trimmed
variable.

---

Nitpick comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Around line 58-59: The _renameStatus flow uses a MutableSharedFlow with the
default replay=0, so rename results can be lost across fragment recreation;
change the declaration of _renameStatus to match _deletionStatus by constructing
it with replay = 1 (keeping renameStatus = _renameStatus.asSharedFlow()) so a
rename result emitted during configuration changes is replayed to new
collectors; update any related comments if present.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2806c22e-15f0-48b4-9f07-7011e3edd403

📥 Commits

Reviewing files that changed from the base of the PR and between c882778 and 01c882d.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt

Comment thread app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (1)
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt (1)

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

Reject path separators in rename validation.

newName is still treated as a filesystem path segment later in Line 231-Line 236, but this validator only checks emptiness and collisions. Names like ../OtherProject or nested/dir will pass here and change the rename target instead of just renaming the current project directory.

Suggested fix
     when {
-        newName.isEmpty() -> {
+        newName.isBlank() -> {
             inputLayout.error = dialog.context.getString(R.string.msg_cannnot_empty)
             positiveButton.isEnabled = false
         }
 
+        newName.contains('/') || newName == "." || newName == ".." -> {
+            inputLayout.error = dialog.context.getString(R.string.msg_invalid_project_name)
+            positiveButton.isEnabled = false
+        }
+
         newName != oldName && nameExists(newName) -> {
             inputLayout.error =
                 dialog.context.getString(R.string.msg_current_name_unavailable)
             positiveButton.isEnabled = false
         }
🤖 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/adapters/RecentProjectsAdapter.kt`
around lines 278 - 299, validateProjectName currently allows path separators and
traversal like "../" or "nested/dir"; update it (in validateProjectName) to
reject names that contain file-separator characters or traversal segments before
checking nameExists. Specifically, check newName for characters/segments such as
'/' or '\' (use File.separatorChar or both chars) and for ".." path traversal,
set inputLayout.error to an appropriate "invalid project name" message from
resources and disable positiveButton, otherwise continue with the existing
empty/collision checks (references: validateProjectName, newName, oldName,
nameExists, inputLayout, dialog, positiveButton).
🤖 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/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Around line 58-59: The _renameStatus flow currently only emits failure (false)
so callers can't await a successful DB update; update the rename-handling code
(the MutableSharedFlow _renameStatus and the rename coroutine logic used in
functions like onFileRenamed / any rename handler between lines ~206-235) so
that after both DAO writes complete successfully you emit true (e.g.,
_renameStatus.emit(true)) and on any rollback or exception emit false; ensure
the success emission happens only after both database updates return/complete,
and keep the existing failure emission path for errors so callers can react to
the final outcome.

---

Outside diff comments:
In `@app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt`:
- Around line 278-299: validateProjectName currently allows path separators and
traversal like "../" or "nested/dir"; update it (in validateProjectName) to
reject names that contain file-separator characters or traversal segments before
checking nameExists. Specifically, check newName for characters/segments such as
'/' or '\' (use File.separatorChar or both chars) and for ".." path traversal,
set inputLayout.error to an appropriate "invalid project name" message from
resources and disable positiveButton, otherwise continue with the existing
empty/collision checks (references: validateProjectName, newName, oldName,
nameExists, inputLayout, dialog, positiveButton).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba2d270d-b8be-429e-bbc2-fd2fe5d48806

📥 Commits

Reviewing files that changed from the base of the PR and between 01c882d and 15790f9.

📒 Files selected for processing (2)
  • app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt

hal-eisen-adfa and others added 2 commits May 5, 2026 16:09
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt (1)

224-225: ⚡ Quick win

Await loadProjects() before signalling success.

loadProjects() returns a Job that runs on its own viewModelScope.launch(Dispatchers.IO) (see Line 66-74), so it is not awaited here — _renameStatus.emit(true) can fire before allProjects and _projects reflect the renamed entry. Any observer that reacts to renameStatus == true by reading the current project list (or by re-running validation via projectNameExists) may briefly see stale data.

♻️ Proposed fix
-                loadProjects()
-                _renameStatus.emit(true)
+                loadProjects().join()
+                _renameStatus.emit(true)
🤖 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/viewmodel/RecentProjectsViewModel.kt`
around lines 224 - 225, loadProjects() is launched asynchronously and returns a
Job, so _renameStatus.emit(true) can fire before projects are updated; capture
the Job returned by loadProjects() and wait for it to complete (e.g., call
job.join() or make loadProjects() a suspend function and await it) before
calling _renameStatus.emit(true); ensure this waiting happens inside a coroutine
(e.g., viewModelScope) so the UI observers read the updated
allProjects/_projects when renameStatus becomes true.
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt (1)

230-232: 💤 Low value

Optional: derive newPath via the File API.

oldPath.substringBeforeLast("/") + "/" + newName works on Android paths but is fragile if oldPath ever contains a trailing separator or no separator at all (in which case substringBeforeLast returns the whole string). Using File is more idiomatic and self-documenting.

♻️ Proposed refactor
-            val newName = binding.textinputEdittext.text.toString().trim()
-            val oldPath = project.path
-            val newPath = oldPath.substringBeforeLast("/") + "/" + newName
+            val newName = binding.textinputEdittext.text.toString().trim()
+            val oldFile = File(project.path)
+            val oldPath = oldFile.absolutePath
+            val newPath = File(oldFile.parentFile, newName).absolutePath
🤖 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/adapters/RecentProjectsAdapter.kt`
around lines 230 - 232, newPath is built by string manipulation from
project.path which breaks on trailing or missing separators; replace that logic
by using the File API: construct a File from project.path (reference:
project.path), get its parent directory (File(project.path).parentFile) and
create a new File(parent, newName) (reference: newName from
binding.textinputEdittext), falling back to newName or File(newName).path if
parent is null, then use that File's path as newPath so path joining is robust
and platform-correct.
🤖 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/adapters/RecentProjectsAdapter.kt`:
- Around line 230-232: newPath is built by string manipulation from project.path
which breaks on trailing or missing separators; replace that logic by using the
File API: construct a File from project.path (reference: project.path), get its
parent directory (File(project.path).parentFile) and create a new File(parent,
newName) (reference: newName from binding.textinputEdittext), falling back to
newName or File(newName).path if parent is null, then use that File's path as
newPath so path joining is robust and platform-correct.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt`:
- Around line 224-225: loadProjects() is launched asynchronously and returns a
Job, so _renameStatus.emit(true) can fire before projects are updated; capture
the Job returned by loadProjects() and wait for it to complete (e.g., call
job.join() or make loadProjects() a suspend function and await it) before
calling _renameStatus.emit(true); ensure this waiting happens inside a coroutine
(e.g., viewModelScope) so the UI observers read the updated
allProjects/_projects when renameStatus becomes true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a70163d7-abb9-48e4-99c6-328007f00dd6

📥 Commits

Reviewing files that changed from the base of the PR and between 15790f9 and 157ad89.

📒 Files selected for processing (2)
  • app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/RecentProjectsViewModel.kt

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt (1)

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

Delay success feedback until the rename is fully committed.

The project.rename(newPath) call (ProjectFile.kt line 47) silently fails—it doesn't check the return value of File.renameTo() or throw an exception—so flashSuccess(R.string.renamed) fires even if the filesystem rename failed. Additionally, flashSuccess() runs before onFileRenamed(...) and the ViewModel's database update, which can emit false and show an error later, creating confusing UX where success is followed by failure. The broad catch (e: Exception) also masks callback/UI errors occurring after a successful filesystem rename.

Limit the try/catch to the actual project.rename(...) call, catch the concrete exception type (or add verification after the rename), and surface success only after the ViewModel's renameStatus flow confirms persistence succeeded (see RecentProjectsViewModel.kt:225–234 for the proper pattern with rollback).

🤖 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/adapters/RecentProjectsAdapter.kt`
around lines 229 - 240, The rename flow currently shows success before the
filesystem and DB persistence are confirmed; narrow the try/catch to only the
actual filesystem rename (project.rename(newPath)) and either handle its boolean
result or throw on failure so you don't emit success prematurely, then subscribe
to the ViewModel's renameStatus (RecentProjectsViewModel.renameStatus) and only
call flashSuccess(R.string.renamed), onFileRenamed(RenamedFile(...)) and
notifyItemChanged(position) after the ViewModel reports persistence succeeded;
catch/handle concrete errors from project.rename (or verify file
existence/rename result) and perform rollback/flashError when persistence fails
rather than using a broad catch around the whole UI callback sequence.
🤖 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.

Outside diff comments:
In `@app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt`:
- Around line 229-240: The rename flow currently shows success before the
filesystem and DB persistence are confirmed; narrow the try/catch to only the
actual filesystem rename (project.rename(newPath)) and either handle its boolean
result or throw on failure so you don't emit success prematurely, then subscribe
to the ViewModel's renameStatus (RecentProjectsViewModel.renameStatus) and only
call flashSuccess(R.string.renamed), onFileRenamed(RenamedFile(...)) and
notifyItemChanged(position) after the ViewModel reports persistence succeeded;
catch/handle concrete errors from project.rename (or verify file
existence/rename result) and perform rollback/flashError when persistence fails
rather than using a broad catch around the whole UI callback sequence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c7d317e-5dae-406c-b9ec-d34cdbd8ecae

📥 Commits

Reviewing files that changed from the base of the PR and between 157ad89 and 8700712.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/adapters/RecentProjectsAdapter.kt

@hal-eisen-adfa hal-eisen-adfa merged commit 582aa03 into stage May 6, 2026
2 checks passed
@hal-eisen-adfa hal-eisen-adfa deleted the ADFA-3701-crash-when-rename-collision branch May 6, 2026 14:45
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