I Shipped an Play Store App That Updates Itself, Because There's Nothing in It to Update

작성자

카테고리:

← 피드로
DEV Community · Suyash Vashishtha · 2026-09-08 개발(SW)

com.myvitals.app, installed from the Play Store, icon and splash screen and everything, has almost no native UI. Open the source: a splash screen, an error screen, one WebView pointed at https://myvitals.co.in. That’s the app.

export const WEBVIEW_URL = 'https://myvitals.co.in';

Enter fullscreen mode Exit fullscreen mode

That string is functionally the entire product. Ship a fix to the web frontend — a normal deploy — and every installed app gets it next open. No review queue, no staged rollout, no “update available” nag. The native shell itself changes maybe a few times a year (a permission, an icon); everything users experience as “the app” ships at web speed, to 100% of installs, at once.

The trade

  1. No true offline bundle — if the WebView can’t reach the internet on first paint, there’s nothing local to fall back to. (The actual offline story lives entirely on the web side — service worker + IndexedDB, covered separately.)
  2. You inherit WebView quirksenv(safe-area-inset-*) often resolves to 0 even with viewport-fit=cover set. Fix: stack a fixed minimum padding on top of the CSS value instead of trusting it alone.

“Finished loading” ≠ “actually works”

onLoadEnd means “navigation finished,” not “a human would call this working” — for a React SPA, the shell can be blank while JS boots. So there’s a second signal: a 15-second timer, and if still loading when it fires, that’s a failure regardless of what the WebView reported.

The real gotcha: onLoadEnd fires after onError too — it’s “did-finish,” not success-only. Wire it naively and a failed load flashes an error screen then un-fails itself a moment later. Fix is one ref, set the instant an error fires, checked before allowing success to override it.

Retry doesn’t call .reload() — it bumps a key prop and lets React fully remount the WebView, after checking real connectivity first. A stale WebView instance can wedge itself into states a soft reload won’t clear.

Bridging the back button

Android’s hardware back button should navigate the website’s own history. Track canGoBack from WebView nav state; on back-press, call goBack() if there’s history — and explicitly do nothing (let the OS exit the app) if there isn’t. Swallowing back-press unconditionally traps users on a screen with a dead back button, which is worse than not intercepting it.

Making a website stop feeling clickable

Injected JS disables text selection and long-press callouts globally (except real inputs), suppresses the copy/share/lookup context menu, kills link previews, and turns off bounce/overscroll. Same instinct on the web side via -webkit-tap-highlight-color: transparent and overscroll-behavior-y: none — belt and suspenders across the native/web boundary.

Camera permission for a plain HTML input

The upload flow is a bare <input type="file"> — the web app has no idea it’s in a WebView. But camera capture from a web file picker needs an OS permission that behaves inconsistently if only requested lazily mid-flow. So the shell proactively requests camera/photo permissions once the page loads, ahead of the user ever tapping upload.

The one screen the theming audit never reached

Small confession: the error screen’s retry button is a hardcoded #2E7D5B green. Brand teal everywhere else is #0d9488. Not the same color — and this is the one screen a 21-page design consolidation pass never touched, because it’s native code that only renders when something’s already gone wrong.

👉 Try MyVitals now — same app, same day, web or home screen icon.

원문에서 계속 ↗