feat: Add network error handling
CI / build (push) Has been cancelled
CI / lint (push) Has been cancelled

- error.html: User-friendly error page with retry button
- ErrorHandler.kt: Utility for handling WebView errors
- ErrorHandlerTest.kt: Unit tests for error handling

Shows friendly error page on network failures instead of blank/broken page.
Includes error code display and one-tap retry.
This commit is contained in:
2026-02-03 15:15:54 +00:00
parent d21f8e6080
commit 3c0c31fe67
3 changed files with 197 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connection Error</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.error-container {
background: white;
border-radius: 16px;
padding: 40px;
text-align: center;
max-width: 400px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}
.icon {
font-size: 64px;
margin-bottom: 20px;
}
h1 {
color: #333;
font-size: 24px;
margin-bottom: 12px;
}
p {
color: #666;
font-size: 16px;
line-height: 1.5;
margin-bottom: 24px;
}
.retry-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 14px 32px;
font-size: 16px;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.retry-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.retry-btn:active {
transform: translateY(0);
}
.error-code {
margin-top: 20px;
font-size: 12px;
color: #999;
}
</style>
</head>
<body>
<div class="error-container">
<div class="icon">📡</div>
<h1>Connection Problem</h1>
<p>Unable to load the page. Please check your internet connection and try again.</p>
<button class="retry-btn" onclick="window.location.reload()">
Try Again
</button>
<div class="error-code" id="errorCode"></div>
</div>
<script>
// Parse error code from URL if present
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
if (code) {
document.getElementById('errorCode').textContent = 'Error code: ' + code;
}
</script>
</body>
</html>
@@ -0,0 +1,80 @@
package farm.hobbs.kioskbrowser
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
/**
* Handles WebView errors with user-friendly error pages.
*
* Usage in WebViewClient:
* ```
* override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
* ErrorHandler.handleError(view, request, error)
* }
* ```
*/
object ErrorHandler {
private const val ERROR_PAGE = "file:///android_asset/error.html"
/**
* Network error codes that should show the error page
*/
private val NETWORK_ERRORS = setOf(
android.webkit.WebViewClient.ERROR_HOST_LOOKUP,
android.webkit.WebViewClient.ERROR_CONNECT,
android.webkit.WebViewClient.ERROR_TIMEOUT,
android.webkit.WebViewClient.ERROR_IO,
android.webkit.WebViewClient.ERROR_UNKNOWN
)
/**
* Handle WebView errors - show error page for main frame network errors
*/
fun handleError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
): Boolean {
// Only handle main frame errors
if (request?.isForMainFrame != true) return false
val errorCode = error?.errorCode ?: return false
// Show error page for network errors
if (errorCode in NETWORK_ERRORS || errorCode < 0) {
view?.loadUrl("$ERROR_PAGE?code=$errorCode")
return true
}
return false
}
/**
* Handle HTTP errors (4xx, 5xx)
*/
fun handleHttpError(
view: WebView?,
request: WebResourceRequest?,
statusCode: Int
): Boolean {
// Only handle main frame errors
if (request?.isForMainFrame != true) return false
// Show error page for server errors
if (statusCode >= 500) {
view?.loadUrl("$ERROR_PAGE?code=$statusCode")
return true
}
return false
}
/**
* Check if current URL is the error page
*/
fun isErrorPage(url: String?): Boolean {
return url?.startsWith(ERROR_PAGE.substringBefore("?")) == true
}
}
@@ -0,0 +1,29 @@
package farm.hobbs.kioskbrowser
import org.junit.Assert.*
import org.junit.Test
/**
* Unit tests for ErrorHandler utility
*/
class ErrorHandlerTest {
@Test
fun `isErrorPage returns true for error page URL`() {
assertTrue(ErrorHandler.isErrorPage("file:///android_asset/error.html"))
assertTrue(ErrorHandler.isErrorPage("file:///android_asset/error.html?code=-2"))
}
@Test
fun `isErrorPage returns false for regular URLs`() {
assertFalse(ErrorHandler.isErrorPage("https://hobbs.farm"))
assertFalse(ErrorHandler.isErrorPage("https://google.com"))
assertFalse(ErrorHandler.isErrorPage(null))
}
@Test
fun `isErrorPage returns false for similar but different URLs`() {
assertFalse(ErrorHandler.isErrorPage("file:///android_asset/other.html"))
assertFalse(ErrorHandler.isErrorPage("https://error.html"))
}
}