Skip to content

Comments

Add server reachability check before loading WebView#34

Merged
3rob3 merged 4 commits intoimmichFrame:mainfrom
kimsey0:webview-server-reachability-check
Dec 18, 2025
Merged

Add server reachability check before loading WebView#34
3rob3 merged 4 commits intoimmichFrame:mainfrom
kimsey0:webview-server-reachability-check

Conversation

@kimsey0
Copy link
Contributor

@kimsey0 kimsey0 commented Dec 17, 2025

Check if the ImmichFrame server is reachable before loading the WebView. If not reachable, show a toast and retry every 5 seconds (up to 36 attempts), same as when WebView is disabled.

Fixes #33. Tested on my Frameo device.

Disclosure: I used Claude Code to debug this problem and code this pull request.

Summary by CodeRabbit

  • New Features
    • Added automatic retry mechanism with server connectivity checks before loading content
    • Displays user-friendly status notifications during connection attempts and retries
    • Improves reliability by attempting to reload if the server is temporarily unavailable

✏️ Tip: You can customize this high-level summary in your review settings.

Check if the ImmichFrame server is reachable before loading the WebView.
If not reachable, show a toast and retry every 5 seconds (up to 36 attempts).

This fixes the black screen issue when WiFi is slow to connect after reboot,
while also working for VPN users where Android's network validation may
report "no internet" even when the server is actually reachable.
@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Walkthrough

The changes add server reachability verification before WebView content loading in MainActivity and ScreenSaverService, implementing a retry mechanism with 5-second intervals (up to 36 attempts) to handle delayed network connectivity after device boot.

Changes

Cohort / File(s) Summary
Reachability Helper
app/src/main/java/com/immichframe/immichframe/Helpers.kt
Added private reachabilityClient OkHttpClient with 5s connect/read timeouts and new public function isServerReachable(url: String): Boolean that performs HEAD requests to verify server availability before WebView loading attempts.
Activity & Service Retry Logic
app/src/main/java/com/immichframe/immichframe/MainActivity.kt, app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt
Replaced direct loadUrl() calls with loadWebViewWithRetry() implementations. Added coroutine-based retry mechanisms that check server reachability on IO dispatcher before loading URLs, retry with 5-second delays and progress toasts on failure, and exhaust retries (default 36 attempts) before loading anyway. Manages lifecycle with lifecycleScope (MainActivity) and dedicated webViewRetryScope (ScreenSaverService).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant App as MainActivity/<br/>ScreenSaverService
    participant Coroutine as Coroutine<br/>(IO Dispatcher)
    participant Helper as Helpers
    participant OkHttp as OkHttpClient
    participant Server as ImmichFrame<br/>Server
    participant UI as UI Thread
    
    User->>App: Trigger URL Load
    App->>App: loadWebViewWithRetry()
    
    loop Retry Loop (attempt ≤ maxAttempts)
        App->>Coroutine: Launch on IO Dispatcher
        Coroutine->>Helper: isServerReachable(url)
        Helper->>OkHttp: HEAD Request (5s timeout)
        OkHttp->>Server: Check Connection
        
        alt Server Reachable
            Server-->>OkHttp: HTTP Response
            OkHttp-->>Helper: Success
            Helper-->>Coroutine: true
            Coroutine->>UI: Switch to Main Dispatcher
            UI->>App: loadUrl() on WebView
            App-->>User: Content Loads
            Note over App: ✓ Success
        else Server Unreachable
            Server--xOkHttp: No Response / Exception
            OkHttp-->>Helper: Exception
            Helper-->>Coroutine: false
            
            alt Attempts Remaining
                Coroutine->>UI: Show "Connecting..." Toast
                Coroutine->>Coroutine: Delay 5 seconds
                Note over Coroutine: Retry...
            else Max Attempts Exceeded
                Coroutine->>UI: Show Failure Toast
                UI->>App: loadUrl() Anyway
                App-->>User: Load Attempted
                Note over App: ✗ Max Retries
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Async coordination complexity: Review coroutine lifecycle management (lifecycleScope in MainActivity vs. dedicated webViewRetryScope in ScreenSaverService) for potential leaks or race conditions
  • Reachability logic: Verify that isServerReachable() HEAD request handling covers all edge cases (HTTP redirects, timeouts, exceptions) and doesn't create unintended side effects
  • Retry mechanism consistency: Ensure retry parameters (5s delay, 36 max attempts) and toast messaging are consistent and appropriate across both Activities and Services
  • Thread dispatcher correctness: Validate that I/O operations run on Dispatchers.IO and UI updates dispatch back to Dispatchers.Main
  • Error handling gaps: Check whether exceptions during reachability checks are properly caught and don't crash the app or leave WebView in an invalid state

Poem

🐰 Hops to the server with a HEAD held high,
Five seconds to check if the connection won't die,
Retry, retry, through slow WiFi haze,
Till the ImmichFrame glows through the reboot malaise,
No more black screens, just patience that pays!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding a server reachability check before WebView loads, which directly addresses the core functionality added across all three modified files.
Linked Issues check ✅ Passed The code changes fully implement the proposed solution from issue #33: isServerReachable() checks actual server reachability with timeouts, retry logic repeats every 5 seconds up to 36 attempts with toast feedback, and the implementation works for both slow WiFi and VPN scenarios.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to implementing server reachability checks and retry mechanisms in the three relevant files, with no unrelated refactoring, dependencies, or feature additions outside the issue #33 scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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: 0

🧹 Nitpick comments (3)
app/src/main/java/com/immichframe/immichframe/Helpers.kt (1)

194-206: Consider adding debug logging for troubleshooting network issues.

The function correctly returns a boolean reachability status and treats any HTTP response as reachable. However, silently swallowing exceptions makes it harder to diagnose connectivity problems in production.

🔎 View suggested enhancement
 fun isServerReachable(url: String): Boolean {
     return try {
         val request = Request.Builder()
             .url(url)
             .head()
             .build()
         reachabilityClient.newCall(request).execute().use {
             true // any HTTP response = reachable
         }
     } catch (e: Exception) {
+        Log.d("Helpers", "Server reachability check failed for $url: ${e.message}")
         false
     }
 }
app/src/main/java/com/immichframe/immichframe/MainActivity.kt (1)

861-892: LGTM! The retry mechanism correctly implements the PR objectives.

The implementation:

  • Performs reachability checks on the IO dispatcher to avoid blocking the UI
  • Provides user feedback via toasts with attempt counts
  • Retries every 5 seconds for up to 36 attempts (3 minutes total), matching the behavior for non-WebView mode
  • Falls back to loading the URL after max attempts, ensuring the WebView eventually loads (consistent with the existing error handler at line 571-586)
  • Uses lifecycleScope to automatically cancel retries if the activity is destroyed

Note: This function is duplicated in ScreenSaverService.kt (lines 624-655). While the author chose this approach for consistency with existing patterns, consider extracting the retry logic to a shared helper function in a future refactor to improve maintainability.

app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt (1)

624-655: LGTM! The retry mechanism mirrors MainActivity's implementation.

The implementation correctly:

  • Performs reachability checks on the IO dispatcher
  • Provides user feedback via toasts
  • Retries every 5 seconds for up to 36 attempts
  • Falls back to loading the URL after exhausting retries
  • Uses the custom webViewRetryScope for proper lifecycle management in DreamService

Note: As mentioned in the MainActivity review, this function is duplicated. The duplication is acceptable given the different lifecycle management requirements (lifecycleScope vs. custom scope), but could be refactored to a shared helper in future iterations.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a3de7f and e53f695.

📒 Files selected for processing (3)
  • app/src/main/java/com/immichframe/immichframe/Helpers.kt (2 hunks)
  • app/src/main/java/com/immichframe/immichframe/MainActivity.kt (3 hunks)
  • app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/immichframe/immichframe/MainActivity.kt (1)
app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt (1)
  • loadWebViewWithRetry (624-655)
app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt (1)
app/src/main/java/com/immichframe/immichframe/MainActivity.kt (1)
  • loadWebViewWithRetry (861-892)
🪛 detekt (1.23.8)
app/src/main/java/com/immichframe/immichframe/Helpers.kt

[warning] 203-203: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (9)
app/src/main/java/com/immichframe/immichframe/Helpers.kt (2)

13-13: LGTM!

The necessary imports for the reachability check are correctly added.

Also applies to: 16-16


189-192: LGTM!

The reusable reachabilityClient with 5-second timeouts prevents client recreation on each retry and ensures timely failure detection.

app/src/main/java/com/immichframe/immichframe/MainActivity.kt (2)

46-46: LGTM!

The lifecycleScope import enables lifecycle-aware coroutine management for the retry mechanism.


592-592: LGTM!

Replacing direct WebView loading with retry-enabled loading addresses the core issue of silent failures when network connectivity is delayed at boot.

app/src/main/java/com/immichframe/immichframe/ScreenSaverService.kt (5)

30-32: LGTM!

The additional coroutine imports support the custom scope management required for the retry mechanism in DreamService.


47-47: LGTM!

The nullable webViewRetryScope field enables proper lifecycle management for the retry coroutines, since DreamService doesn't provide a built-in lifecycle scope like Activity.


90-90: LGTM!

Initializing the retry scope when the dream starts ensures coroutines are launched in a properly managed context.


113-114: LGTM!

Properly canceling and nulling the scope in onDreamingStopped() prevents memory leaks and ensures retry coroutines are cleaned up when the screen saver stops.


528-528: LGTM!

Replacing direct WebView loading with retry-enabled loading ensures consistent behavior with MainActivity and addresses delayed network connectivity at boot.

@3rob3
Copy link
Contributor

3rob3 commented Dec 18, 2025

LGTM, thanks!

@3rob3 3rob3 merged commit 6b6f517 into immichFrame:main Dec 18, 2025
1 check passed
@kimsey0 kimsey0 deleted the webview-server-reachability-check branch December 18, 2025 19:34
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.

WebView fails to load after reboot if WiFi is slow to connect

2 participants