Skip to content

ADFA-3472: Guard BuildOutputFragment access when detached#1415

Open
fryanpan wants to merge 3 commits into
stagefrom
ADFA-3472-buildoutputfragment-not-attached
Open

ADFA-3472: Guard BuildOutputFragment access when detached#1415
fryanpan wants to merge 3 commits into
stagefrom
ADFA-3472-buildoutputfragment-not-attached

Conversation

@fryanpan

@fryanpan fryanpan commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

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

Question

Looks like this file is part of AndroidIDE -- do we make changes here or upstream?

Reproduction Details

BuildOutputFragment holds buildOutputViewModel via by activityViewModels(). When clearOutput() / getShareableContent() run on a detached fragment, forcing that lazy delegate calls requireActivity(), which throws IllegalStateException: … not attached to an activity.

Stack Trace

IllegalStateException: Fragment BuildOutputFragment{…} not attached to an activity.
  at androidx.fragment.app.Fragment.requireActivity(Fragment.java:1005)
  at …BuildOutputFragment$special$$inlined$activityViewModels$default$1.invoke(FragmentViewModelLazy.kt:176)
  at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:49)
  (entered via clearOutput() / getShareableContent())

User Steps

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

  • A build is running; the user rapidly switches/swipes between the bottom output tabs (Build Output → App Log → IDE Log → Diagnostics → Debugger → Git). BuildOutputFragment goes through save-state → stopped → view destroyed → detached, and an incoming build-output update then calls clearOutput() on the now-detached fragment → crash.

Was able to reproduce in a unit test?

Yes.
BuildOutputFragmentDetachedTest (:app, Robolectric) constructs a never-attached fragment (isAdded == false) and calls each method. Baseline: both FAIL with IllegalStateException at requireActivity via the activityViewModels delegate; branch: both pass.

What Was Fixed

if (!isAdded || activity == null) return guard in clearOutput() / getShareableContent() (returns "" for the latter) before touching the activity-scoped view model.

Testing

:app:testV8DebugUnitTest → 2/2 green (red on baseline). Local CodeRabbit review: no findings. Reviewer note (local): the activity == null clause is redundant with !isAdded, and EditorBottomSheet's takeIf { it.isAdded } is now belt-and-suspenders — harmless. viewLifecycleOwner is NOT the right alternative here (the precondition is isAdded, which it doesn't capture).


Fixes APPDEVFORALL-N4

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

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@fryanpan, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 24 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c0d577e1-a650-410e-95d0-60f3317bdbce

📥 Commits

Reviewing files that changed from the base of the PR and between ff679f4 and e6941b4.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/fragments/output/BuildOutputFragment.kt
  • app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt
  • app/src/test/java/com/itsaky/androidide/fragments/output/BuildOutputFragmentDetachedTest.kt
📝 Walkthrough

Walkthrough

BuildOutputFragment.clearOutput() and getShareableContent() now return early when the fragment is detached (!isAdded || activity == null). EditorBottomSheet.clearBuildOutput() adds an isAdded check before invoking clearOutput(). A Robolectric regression test suite is added to cover these detached-state paths.

Changes

Detached Fragment Crash Fix

Layer / File(s) Summary
Detachment guards in BuildOutputFragment and EditorBottomSheet
app/src/main/java/com/itsaky/androidide/fragments/output/BuildOutputFragment.kt, app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt
clearOutput() and getShareableContent() bail out early when !isAdded || activity == null. EditorBottomSheet.clearBuildOutput() additionally checks isAdded before delegating to clearOutput().
Robolectric regression tests
app/src/test/java/com/itsaky/androidide/fragments/output/BuildOutputFragmentDetachedTest.kt
New test class BuildOutputFragmentDetachedTest constructs a detached BuildOutputFragment and asserts that clearOutput() does not throw and getShareableContent() returns an empty string.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Suggested reviewers

  • jatezzz
  • jomen-adfa
  • Daniel-ADFA

Poem

🐇 Hippity-hoppity, detached and alone,
A fragment unbound, no activity home.
But now I check first before I proceed—
isAdded or bust, I return at full speed!
No crash, no fuss, just an empty string found,
The rabbit guards wisely on null-safety ground. 🌿

🚥 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 directly and accurately describes the main change: adding guards in BuildOutputFragment to handle detached fragment states.
Description check ✅ Passed The description is comprehensive and well-related to the changeset, including reproduction details, stack trace, fix explanation, and test verification.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-3472-buildoutputfragment-not-attached

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.

fryanpan and others added 3 commits June 19, 2026 09:58
Sentry APPDEVFORALL-N4. Skip activityViewModels access when the fragment
is detached; guard the caller with isAdded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reproduces the Sentry IllegalStateException ('not attached to an activity')
thrown when clearOutput()/getShareableContent() force the activityViewModels
lazy delegate (-> requireActivity()) on a detached fragment. RED on the
pre-fix baseline (4d9b100), GREEN with the isAdded/activity guard.

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-3472-buildoutputfragment-not-attached branch from 64ac5df to e6941b4 Compare June 19, 2026 16:58
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