Skip to content

ADFA-3007 | Add proactive low storage check and dialog#1011

Merged
jatezzz merged 3 commits intostagefrom
fix/ADFA-3007-add-low-storage-dialog
Mar 2, 2026
Merged

ADFA-3007 | Add proactive low storage check and dialog#1011
jatezzz merged 3 commits intostagefrom
fix/ADFA-3007-add-low-storage-dialog

Conversation

@jatezzz
Copy link
Collaborator

@jatezzz jatezzz commented Feb 24, 2026

Description

This PR fixes a critical issue where the application crashes with SQLiteFullException and ENOSPC errors when a user opens the app while their device storage is completely full. Instead of allowing background libraries (like WorkManager, Firebase, Room) and internal file extractors to fail in a cascading manner, we now proactively check for available storage at the earliest entry point (SplashActivity).

If the device does not meet the minimum required storage (4GB for stable, 6GB for experimental), a non-cancelable AlertDialog is shown to the user, preventing further heavy I/O operations and providing a direct action to open the device's storage settings to free up space.

Details

  • Created StorageUtils.kt to calculate the available device storage.
  • Refactored InstallationViewModel to use the centralized getMinimumStorageNeeded() method.
  • Added a reusable Activity.showLowStorageDialog() extension function in StorageDialog.kt.
  • Added necessary string placeholders in strings.xml to support future localization and prevent hardcoded texts.
  • Validated via Logcat that SplashActivity now finishes cleanly without memory leaks when the user is blocked by the dialog.

Before changes

Screen.Recording.2026-02-24.at.5.13.17.PM.mov
image

After changes

Screen.Recording.2026-02-24.at.5.05.06.PM.mov

Ticket

ADFA-3007

Also fixes:
ADFA-3018

Observation

The minimum storage threshold logic was also successfully decoupled so it can be reused across the app.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Release Notes - Low Storage Check and Prevention Dialog (ADFA-3007)

New Features

  • Proactive Storage Validation: Implements automatic storage availability check at app startup (SplashActivity) to prevent crashes from SQLiteFullException and ENOSPC errors
  • Low Storage Dialog: Displays non-cancelable AlertDialog when available storage falls below thresholds (4GB for stable builds, 6GB for experimental)
  • User Remediation Options: Dialog provides direct action to open device storage settings to free up space, with fallback support for devices without internal storage settings API
  • Centralized Storage Threshold Logic: getMinimumStorageNeeded() utility function enables consistent storage requirement checks across the app

Technical Changes

  • New StorageUtils.kt: Provides hasEnoughStorageAvailable() and getMinimumStorageNeeded() functions for storage validation
  • New StorageDialog.kt: Extension function Activity.showLowStorageDialog() for consistent dialog presentation
  • SplashActivity.kt: Early storage check prevents further initialization if storage is insufficient
  • InstallationViewModel.kt: Refactored to use centralized getMinimumStorageNeeded() instead of duplicate local constants
  • UI Localization: Added four new string resources (err_insufficient_storage_title, err_insufficient_storage_msg, action_close_app, action_free_up_space)

⚠️ Risks and Best Practices Concerns

  • Aggressive Exception Suppression: hasEnoughStorageAvailable() silently catches all exceptions and returns false, which may mask underlying OS/permission errors and provide unclear feedback to developers
  • Non-Cancelable Dialog Restriction: Dialog cannot be dismissed by user, forcing them to either close the app or navigate to storage settings—may cause frustration if storage cannot be freed
  • High Storage Thresholds: 4GB/6GB minimum requirements are substantial and may block legitimate use on devices with limited storage capacity; consider if lower thresholds are acceptable for your target market
  • Aggressive App Termination: Using finishAndRemoveTask() and finishAffinity() immediately closes the app without allowing graceful shutdown or data persistence
  • Limited Error Context: Users see generic dialog without specific information about which component/database filled the storage

Verification Notes

  • Verified via Logcat that SplashActivity completes cleanly without memory leaks when dialog blocks user
  • Includes before/after screenshots demonstrating issue resolution
  • Feature-flagged thresholds allow different handling for experimental vs. stable builds

Walkthrough

Adds a pre-check for available device storage in SplashActivity that shows a non-cancelable low-storage dialog and aborts initialization when insufficient; centralizes storage threshold logic into new utilities and updates InstallationViewModel to use that helper. New strings and dialog UI added.

Changes

Cohort / File(s) Summary
Storage Utilities
app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt
New helpers: hasEnoughStorageAvailable() checks device free bytes via StatFs, and getMinimumStorageNeeded() returns 4GB or 6GB based on feature flag.
Low-Storage Dialog UI
app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt
New Activity.showLowStorageDialog() extension showing a non-cancelable AlertDialog with actions to close app or open internal storage settings.
Splash Activity
app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt
Added early call to hasEnoughStorageAvailable() in onCreate(); shows low-storage dialog and returns if insufficient, preserving existing downstream logic otherwise.
ViewModel update
app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt
Replaced internal minimum-storage constants/logic with call to getMinimumStorageNeeded() when computing required storage bytes.
Resources
resources/src/main/res/values/strings.xml
Added strings: err_insufficient_storage_title, err_insufficient_storage_msg, action_close_app, action_free_up_space used by the dialog.

Sequence Diagram

sequenceDiagram
    participant SA as SplashActivity
    participant SU as StorageUtils
    participant OS as OS/Environment
    participant SD as StorageDialog
    participant Sys as System

    SA->>SU: hasEnoughStorageAvailable()
    SU->>OS: Query data dir via StatFs
    OS-->>SU: availableBlocksLong & blockSizeLong
    SU->>SU: calculate availableBytes
    SU->>SU: getMinimumStorageNeeded()
    SU-->>SA: return Boolean (enough? )
    alt Insufficient storage
        SA->>SD: showLowStorageDialog()
        SD->>Sys: display AlertDialog (non-cancelable)
        Sys-->>SD: user action
        alt Close app
            SD->>SA: finishAndRemoveTask()
        else Open storage settings
            SD->>Sys: Intent ACTION_INTERNAL_STORAGE_SETTINGS (fallback ACTION_SETTINGS)
            SD->>SA: finishAffinity()
        end
    else Sufficient storage
        SA->>SA: continue initialization
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • dara-abijo-adfa
  • Daniel-ADFA
  • itsaky-adfa

Poem

🐰
I hopped in to check the burrow's floor,
Four gigs or six — we count once more.
If crumbs are few and space is low,
I tap the door and say "time to go!"
Hop safe, clear space, and onward we flow.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ADFA-3007 | Add proactive low storage check and dialog' directly and clearly describes the main change: adding a proactive storage check and dialog to prevent crashes.
Description check ✅ Passed The description comprehensively explains the changeset, including the problem being solved (SQLiteFullException crashes), the solution approach (proactive storage check at SplashActivity), and implementation details across multiple files.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ADFA-3007-add-low-storage-dialog

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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
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: 2

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

15-23: Semicolons and simplification opportunity in getMinimumStorageNeeded.

Lines 20 and 22 use Java-style trailing semicolons, which are not idiomatic Kotlin. The whole function can be expressed more concisely.

♻️ Proposed refactor
-fun getMinimumStorageNeeded(): Long {
-    val minimumStorageStableGB = 4L
-    val minimumStorageExperimentalGB = 6L
-
-    if (FeatureFlags.isExperimentsEnabled) {
-        return minimumStorageExperimentalGB;
-    }
-    return minimumStorageStableGB;
-}
+fun getMinimumStorageNeeded(): Long =
+    if (FeatureFlags.isExperimentsEnabled) 6L else 4L
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt` around lines
15 - 23, The function getMinimumStorageNeeded has Java-style trailing semicolons
and can be simplified; remove the semicolons after the return statements and
convert the block to a concise expression body that returns either
minimumStorageExperimentalGB or minimumStorageStableGB based on
FeatureFlags.isExperimentsEnabled (you can inline the constants or keep the
existing minimumStorageStableGB/minimumStorageExperimentalGB names), so replace
the current multiline function with a single-expression function using an if
expression referencing getMinimumStorageNeeded, minimumStorageStableGB,
minimumStorageExperimentalGB, and FeatureFlags.isExperimentsEnabled.
app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt (1)

4-4: Prefer androidx.appcompat.app.AlertDialog over android.app.AlertDialog.

The platform AlertDialog does not respect your app's Material/AppCompat theme on all API levels. Using the AppCompat variant ensures consistent styling.

♻️ Proposed change
-import android.app.AlertDialog
+import androidx.appcompat.app.AlertDialog
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt` at line 4,
Replace the platform AlertDialog import with the AppCompat variant to ensure
consistent theming: change the import of android.app.AlertDialog to
androidx.appcompat.app.AlertDialog and ensure any uses in StorageDialog (e.g.,
AlertDialog.Builder(...) or return types/fields named AlertDialog) reference the
AppCompat class; if you construct dialogs from a Context that may not be
AppCompatActivity, use the AppCompat-compatible builder pattern (or pass an
AppCompatContext) so the androidx.appcompat.app.AlertDialog is used throughout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt`:
- Around line 22-25: In StorageDialog.kt inside the setNegativeButton lambda
(the code block that currently calls
startActivity(Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS)) and
finishAffinity()), wrap the startActivity call in a try-catch that catches
android.content.ActivityNotFoundException; on failure construct and launch a
fallback Intent (e.g., Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS or
Settings.ACTION_SETTINGS) and only then call finishAffinity(); ensure both the
primary and fallback intents use startActivity safely and that the catch does
not suppress the finishAffinity() call.

In `@app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt`:
- Around line 6-13: The current hasEnoughStorageAvailable() function swallows
StatFs/I/O exceptions and returns true, which is unsafe; change the exception
handling in hasEnoughStorageAvailable to treat any exception as "not enough
storage" by returning false (and optionally log the exception for diagnostics)
instead of true, while keeping use of Environment.getDataDirectory(),
StatFs(dataDir.path), availableBlocksLong/blockSizeLong and
getMinimumStorageNeeded().gigabytesToBytes() intact so callers correctly avoid
heavy I/O when storage checks fail.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt`:
- Line 4: Replace the platform AlertDialog import with the AppCompat variant to
ensure consistent theming: change the import of android.app.AlertDialog to
androidx.appcompat.app.AlertDialog and ensure any uses in StorageDialog (e.g.,
AlertDialog.Builder(...) or return types/fields named AlertDialog) reference the
AppCompat class; if you construct dialogs from a Context that may not be
AppCompatActivity, use the AppCompat-compatible builder pattern (or pass an
AppCompatContext) so the androidx.appcompat.app.AlertDialog is used throughout.

In `@app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt`:
- Around line 15-23: The function getMinimumStorageNeeded has Java-style
trailing semicolons and can be simplified; remove the semicolons after the
return statements and convert the block to a concise expression body that
returns either minimumStorageExperimentalGB or minimumStorageStableGB based on
FeatureFlags.isExperimentsEnabled (you can inline the constants or keep the
existing minimumStorageStableGB/minimumStorageExperimentalGB names), so replace
the current multiline function with a single-expression function using an if
expression referencing getMinimumStorageNeeded, minimumStorageStableGB,
minimumStorageExperimentalGB, and FeatureFlags.isExperimentsEnabled.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3b54d7 and 4bb126b.

📒 Files selected for processing (5)
  • app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt
  • app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt
  • app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt
  • resources/src/main/res/values/strings.xml

@jatezzz jatezzz requested a review from a team February 25, 2026 13:07
Shows a blocking dialog in SplashActivity if the device storage is full.
… fix the exception handling in `hasEnoughStorageAvailable` method
@jatezzz jatezzz force-pushed the fix/ADFA-3007-add-low-storage-dialog branch from 4bb126b to a58b488 Compare February 25, 2026 13:35
Copy link
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/viewmodel/InstallationViewModel.kt (1)

135-148: ⚠️ Potential issue | 🟠 Major

StatFs in getStorageInfo is unguarded — can crash when called from startIdeSetup.

StatFs(internalStoragePath) can throw (e.g., IllegalArgumentException if filesDir doesn't exist yet, or an I/O error). getStorageInfo has no try/catch, and its caller checkStorageAndNotify is invoked at line 63 of startIdeSetup before the viewModelScope.launch { try { … } catch { … } } block, so the exception propagates completely uncaught. This is the same class of crash the PR aims to prevent, and it's inconsistent with hasEnoughStorageAvailable() in StorageUtils.kt which wraps its own StatFs call.

🛡️ Proposed fix
 private fun getStorageInfo(context: Context): StorageInfo {
     val internalStoragePath = context.filesDir.path
-    val stat = StatFs(internalStoragePath)
-
-    val availableStorageInBytes = stat.availableBlocksLong * stat.blockSizeLong
+    val availableStorageInBytes = try {
+        val stat = StatFs(internalStoragePath)
+        stat.availableBlocksLong * stat.blockSizeLong
+    } catch (_: Exception) {
+        0L   // treat unreadable storage as 0 bytes available
+    }
     val requiredStorageInBytes = getMinimumStorageNeeded().gigabytesToBytes()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt`
around lines 135 - 148, getStorageInfo currently constructs
StatFs(internalStoragePath) without protection, which can throw and crash
callers like startIdeSetup->checkStorageAndNotify; wrap the StatFs construction
and subsequent reads in a try/catch (catch
IllegalArgumentException/IOException/RuntimeException or a general Exception)
inside getStorageInfo and on error log the exception and return a safe fallback
StorageInfo indicating low storage (e.g., isLowStorage = true,
availableStorageInBytes = 0L, additionalBytesNeeded =
getMinimumStorageNeeded().gigabytesToBytes()); keep references to
getStorageInfo, checkStorageAndNotify, startIdeSetup and compare behavior to
StorageUtils.hasEnoughStorageAvailable when implementing the guard.
🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt (1)

15-22: Optional: remove spurious semicolons and simplify to expression function.

Line 20 has a trailing semicolon (non-idiomatic Kotlin), and the whole body can collapse to a single expression.

♻️ Suggested simplification
-fun getMinimumStorageNeeded(): Long {
-	val minimumStorageStableGB = 4L
-	val minimumStorageExperimentalGB = 6L
-
-	if (FeatureFlags.isExperimentsEnabled) {
-		return minimumStorageExperimentalGB;
-	}
-	return minimumStorageStableGB;
-}
+private const val MINIMUM_STORAGE_STABLE_GB = 4L
+private const val MINIMUM_STORAGE_EXPERIMENTAL_GB = 6L
+
+fun getMinimumStorageNeeded(): Long =
+    if (FeatureFlags.isExperimentsEnabled) MINIMUM_STORAGE_EXPERIMENTAL_GB else MINIMUM_STORAGE_STABLE_GB
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt` around lines
15 - 22, The function getMinimumStorageNeeded contains non-idiomatic trailing
semicolons and a multi-statement body; simplify it to a single-expression
function and remove the semicolons. Replace the if/return block with a concise
expression that returns either minimumStorageExperimentalGB or
minimumStorageStableGB based on FeatureFlags.isExperimentsEnabled (keeping the
local constants minimumStorageStableGB and minimumStorageExperimentalGB or
inlining them if preferred) and ensure no trailing semicolons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt`:
- Around line 135-148: getStorageInfo currently constructs
StatFs(internalStoragePath) without protection, which can throw and crash
callers like startIdeSetup->checkStorageAndNotify; wrap the StatFs construction
and subsequent reads in a try/catch (catch
IllegalArgumentException/IOException/RuntimeException or a general Exception)
inside getStorageInfo and on error log the exception and return a safe fallback
StorageInfo indicating low storage (e.g., isLowStorage = true,
availableStorageInBytes = 0L, additionalBytesNeeded =
getMinimumStorageNeeded().gigabytesToBytes()); keep references to
getStorageInfo, checkStorageAndNotify, startIdeSetup and compare behavior to
StorageUtils.hasEnoughStorageAvailable when implementing the guard.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt`:
- Around line 15-22: The function getMinimumStorageNeeded contains non-idiomatic
trailing semicolons and a multi-statement body; simplify it to a
single-expression function and remove the semicolons. Replace the if/return
block with a concise expression that returns either minimumStorageExperimentalGB
or minimumStorageStableGB based on FeatureFlags.isExperimentsEnabled (keeping
the local constants minimumStorageStableGB and minimumStorageExperimentalGB or
inlining them if preferred) and ensure no trailing semicolons remain.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb126b and a58b488.

📒 Files selected for processing (5)
  • app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt
  • app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt
  • app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt
  • resources/src/main/res/values/strings.xml

@jatezzz jatezzz requested a review from Daniel-ADFA February 25, 2026 13:56
@jatezzz jatezzz merged commit a9e4903 into stage Mar 2, 2026
2 checks passed
@jatezzz jatezzz deleted the fix/ADFA-3007-add-low-storage-dialog branch March 2, 2026 19:56
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