Skip to content

fix: prevent splash screen from hanging on slow DB#4061

Merged
johnnyluo merged 2 commits into
mainfrom
roman/splash-vault-query-timeout
Apr 15, 2026
Merged

fix: prevent splash screen from hanging on slow DB#4061
johnnyluo merged 2 commits into
mainfrom
roman/splash-vault-query-timeout

Conversation

@rkokhatskyi
Copy link
Copy Markdown
Collaborator

@rkokhatskyi rkokhatskyi commented Apr 14, 2026

Devs are reporting the splash screen hanging on cold-start for 20+ seconds, sometimes resolving after force-closing and reopening a few times. Release builds strip Timber so there's no app-side signal, but from ADB logs the process is alive and the main thread is responsive — the only thing holding the splash is MainViewModel's init coroutine waiting on vaultRepository.hasVaults(), which transitively opens the Room DB and can stall for any number of reasons (contended storage, keystore daemon, etc.).

Wrap that call in withTimeout(5.seconds) and treat timeout/exception as "no vaults" so the splash always dismisses into Route.AddVault instead of hanging. Route the init block and the openUri deeplink entry through safeLaunch so a thrown exception can no longer leave _isLoading = true forever.

MainViewModel's init gated the splash-dismiss signal on
vaultRepository.hasVaults(), which on a cold DB open also runs the full
Room migration chain. On slow storage or after multiple beta updates
some users report the splash hanging indefinitely, since there is no
timeout on that call and any thrown exception leaves _isLoading = true
forever.

Wrap the query in withTimeoutOrNull(5s), fall back to Route.AddVault on
timeout or exception, and route the init block through safeLaunch so
non-CancellationException throws no longer crash the process.
Unit tests cover the four reachable paths (true, false, timeout, throw).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

MainViewModel now includes timeout protection for vault existence checks, replacing unsafe coroutine launches with safer alternatives using viewModelScope.safeLaunch. A new hasAnyVault() helper wraps the repository call in a 5-second timeout with fallback error handling. Comprehensive unit tests validate state transitions across success, timeout, and exception scenarios.

Changes

Cohort / File(s) Summary
MainViewModel Implementation
app/src/main/java/com/vultisig/wallet/app/activity/MainViewModel.kt
Replaced direct viewModelScope.launch calls with viewModelScope.safeLaunch. Added hasAnyVault() suspend function that wraps vaultRepository.hasVaults() in a 5-second timeout via withTimeoutOrNull(). Non-cancellation exceptions are caught and logged; timeouts fall back to false. Added module-level constant SPLASH_VAULT_QUERY_TIMEOUT = 5.seconds.
MainViewModel Tests
app/src/test/java/com/vultisig/wallet/app/activity/MainViewModelTest.kt
New comprehensive test suite covering vault existence resolution. Tests validate state transitions when vaults exist (Route.Home), when they don't (Route.AddVault), when the query times out, and when exceptions occur. Includes tests for immediate loading state upon creation and proper dispatcher cleanup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A timeout arrives with gentleness grace,
Five seconds to check for vaults in their place,
When silence descends, we gracefully fall,
To safety and tests that now cover it all! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: prevent splash screen from hanging on slow DB' directly and clearly summarizes the main change: adding a timeout to prevent the splash screen from hanging when database queries are slow.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 roman/splash-vault-query-timeout

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

🧹 Nitpick comments (1)
app/src/test/java/com/vultisig/wallet/app/activity/MainViewModelTest.kt (1)

20-22: Match the app test assertion stack.

The new file uses kotlin.test assertions. If Kotest is already available in the app module, switching these imports would keep the new tests aligned with the rest of the app test suite.

As per coding guidelines, "app/src/test/**/*.kt: Use JUnit 5 (Jupiter) test platform for unit tests with Mockk for mocking and Kotest for assertions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/com/vultisig/wallet/app/activity/MainViewModelTest.kt`
around lines 20 - 22, Tests in MainViewModelTest import kotlin.test assertions
(assertEquals/assertFalse/assertTrue); switch to Kotest matchers to match the
project convention: replace the kotlin.test imports with Kotest imports (e.g.,
io.kotest.matchers.shouldBe and
io.kotest.matchers.booleans.shouldBeTrue/shouldBeFalse) and change assertion
calls — replace assertEquals(expected, actual) with actual shouldBe expected,
assertTrue(cond) with cond.shouldBeTrue(), and assertFalse(cond) with
cond.shouldBeFalse(); update usages in the test methods accordingly.
🤖 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/vultisig/wallet/app/activity/MainViewModel.kt`:
- Around line 211-216: In openUri(), replace the plain viewModelScope.launch
usage with safeLaunch and replace the direct vaultRepository.hasVaults() call
with the hasAnyVault() helper so the deeplink path gets the same
SPLASH_VAULT_QUERY_TIMEOUT protection as resolveStartDestination();
specifically, change the coroutine builder at the openUri() entry (currently
calling viewModelScope.launch) to use safeLaunch and use hasAnyVault() where
vaultRepository.hasVaults() is invoked so Room initialization is guarded by the
existing timeout logic.

---

Nitpick comments:
In `@app/src/test/java/com/vultisig/wallet/app/activity/MainViewModelTest.kt`:
- Around line 20-22: Tests in MainViewModelTest import kotlin.test assertions
(assertEquals/assertFalse/assertTrue); switch to Kotest matchers to match the
project convention: replace the kotlin.test imports with Kotest imports (e.g.,
io.kotest.matchers.shouldBe and
io.kotest.matchers.booleans.shouldBeTrue/shouldBeFalse) and change assertion
calls — replace assertEquals(expected, actual) with actual shouldBe expected,
assertTrue(cond) with cond.shouldBeTrue(), and assertFalse(cond) with
cond.shouldBeFalse(); update usages in the test methods accordingly.
🪄 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: 778ef082-084d-44d6-a7e6-05754191f1f6

📥 Commits

Reviewing files that changed from the base of the PR and between 940c88f and 7bf7436.

📒 Files selected for processing (2)
  • app/src/main/java/com/vultisig/wallet/app/activity/MainViewModel.kt
  • app/src/test/java/com/vultisig/wallet/app/activity/MainViewModelTest.kt

Comment thread app/src/main/java/com/vultisig/wallet/app/activity/MainViewModel.kt
openUri runs after splash dismiss, but the underlying hasVaults()
call hits the same code path. If DataStore or Room is still warming
up when a deep link opens the app, the deeplink also hangs. Route
through hasAnyVault() and safeLaunch so it inherits the same
3-second timeout and crash-safety.
Copy link
Copy Markdown
Contributor

@johnnyluo johnnyluo left a comment

Choose a reason for hiding this comment

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

LGTM

@johnnyluo johnnyluo added this pull request to the merge queue Apr 15, 2026
Merged via the queue into main with commit a39a9d4 Apr 15, 2026
2 checks passed
@johnnyluo johnnyluo deleted the roman/splash-vault-query-timeout branch April 15, 2026 02:10
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