<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Giovani Fouz</title>
    <description>The latest articles on Forem by Giovani Fouz (@gfouz).</description>
    <link>https://forem.com/gfouz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F893736%2F6ca75795-4154-4233-887b-fd6f5cb23bf7.jpg</url>
      <title>Forem: Giovani Fouz</title>
      <link>https://forem.com/gfouz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gfouz"/>
    <language>en</language>
    <item>
      <title>I Served My React SPA from Android Assets Like a Professional Web Server</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sun, 03 May 2026 18:50:28 +0000</pubDate>
      <link>https://forem.com/gfouz/i-served-my-react-spa-from-android-assets-like-a-professional-web-server-bl6</link>
      <guid>https://forem.com/gfouz/i-served-my-react-spa-from-android-assets-like-a-professional-web-server-bl6</guid>
      <description>&lt;p&gt;🚀 I Served My React SPA from Android Assets Like a Professional Web Server — Here's What Happened&lt;/p&gt;

&lt;p&gt;First load: 77ms. Reload: 2ms. 38x faster with LRU cache. No server, no permissions, no dependencies.&lt;/p&gt;




&lt;p&gt;🤔 The Problem Every React Dev Faces&lt;/p&gt;

&lt;p&gt;You've got your SPA running perfectly on localhost:5173. React, TypeScript, TailwindCSS, React Router, lazy loading... everything works beautifully.&lt;/p&gt;

&lt;p&gt;Now you need to take it to Android.&lt;/p&gt;

&lt;p&gt;Your traditional options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Option 1: Capacitor — 30MB runtime, complex config&lt;/span&gt;
&lt;span class="c1"&gt;// Option 2: Cordova — 15MB, outdated plugins&lt;/span&gt;
&lt;span class="c1"&gt;// Option 3: file:// protocol — broken CORS, SPA routes don't work&lt;/span&gt;
&lt;span class="c1"&gt;// Option 4: 50-line homemade script — fragile, no cache, no security&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of them feel right. You want something lightweight, fast, secure, and respectful of your architecture.&lt;/p&gt;




&lt;p&gt;✨ The Solution: WebVirt Engine&lt;/p&gt;

&lt;p&gt;An Android library of ~600 lines that simulates a virtual web server inside the WebView. Your SPA thinks it's at &lt;a href="https://app.local" rel="noopener noreferrer"&gt;https://app.local&lt;/a&gt;, but everything comes from assets/.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 5 lines. That's it.&lt;/span&gt;
&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://app.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all. Your React app running. SPA routes intact. No weird configuration.&lt;/p&gt;




&lt;p&gt;🔬 But Don't Take My Word for It. Look at the Real Data.&lt;/p&gt;

&lt;p&gt;To validate that WebVirt Engine was as fast as promised, I needed real metrics. Not synthetic benchmarks. Not "it feels fast." Cold, hard data.&lt;/p&gt;

&lt;p&gt;The Secret Weapon: WebVirtMetrics&lt;/p&gt;

&lt;p&gt;WebVirt Engine includes an optional metrics module that captures every asset load in real time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Enable only in debug. Zero overhead in production.&lt;/span&gt;
&lt;span class="nc"&gt;WebVirtMetrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BuildConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;WebVirtMetrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startSession&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Every asset WebVirt loads gets recorded:&lt;/span&gt;
&lt;span class="c1"&gt;// - File path&lt;/span&gt;
&lt;span class="c1"&gt;// - Load time in milliseconds&lt;/span&gt;
&lt;span class="c1"&gt;// - Whether it came from cache or disk&lt;/span&gt;
&lt;span class="c1"&gt;// - Size in bytes&lt;/span&gt;
&lt;span class="c1"&gt;// - MIME type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Metrics are automatically persisted using LoggingUtil, which writes a log file to the device storage without requiring any permissions.&lt;/p&gt;




&lt;p&gt;📊 The Results (Real Financial App)&lt;/p&gt;

&lt;p&gt;Stack: React 18 + TypeScript + TailwindCSS + Vite + React Router&lt;br&gt;
Assets: 1.4MB (3 main files + 13 lazy chunks)&lt;br&gt;
Device: Physical Android, mid-range&lt;/p&gt;

&lt;p&gt;First Load (Assets from Disk)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔══════════════════════════════════════════════════╗
║     WEBVIRT ENGINE - PERFORMANCE REPORT          ║
╠══════════════════════════════════════════════════╣
║ Session duration:         4214 ms              ║
║ Total assets loaded:         3                  ║
║ Total load time:            77 ms              ║
║ Avg load time:              25 ms              ║
║ Min load time:              10 ms              ║
║ Max load time:              49 ms              ║
╠══════════════════════════════════════════════════╣
║ Cache hits:                  0                  ║
║ Cache misses:                3                  ║
║ Cache hit rate:           0.0%                 ║
║ Bytes from cache:            0 bytes           ║
║ Total bytes loaded:    1426251 bytes           ║
╠══════════════════════════════════════════════════╣
║ HTTP errors:                 0                  ║
║ SPA fallbacks:               1                  ║
║ Range requests:              0                  ║
╠══════════════════════════════════════════════════╣
║ BY MIME TYPE:                                    ║
║   HTML                 x1    avg   10ms           ║
║   CSS                  x1    avg   18ms           ║
║   JavaScript           x1    avg   49ms           ║
╠══════════════════════════════════════════════════╣
║ RECENT LOADS (last 5):                           ║
║   📄 /index.html                      10ms║
║   📄 /assets/index-DGe01YXs.css       18ms║
║   📄 /assets/index-B3g6t1vt.js        49ms║
╚══════════════════════════════════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 assets. 77ms total. Zero errors.&lt;/p&gt;

&lt;p&gt;The 4214ms "session" includes: app startup, welcome animation, and the user tapping the "Start" button. WebVirt only took 77ms.&lt;/p&gt;




&lt;p&gt;Second Load (LRU Cache in RAM)&lt;/p&gt;

&lt;p&gt;By long-pressing the WebView (a hidden debug gesture), I forced a reload to measure cache performance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔══════════════════════════════════════════════════╗
║     WEBVIRT ENGINE - PERFORMANCE REPORT          ║
╠══════════════════════════════════════════════════╣
║ Session duration:          513 ms              ║
║ Total assets loaded:         3                  ║
║ Total load time:             2 ms              ║
║ Avg load time:               0 ms              ║
║ Min load time:               0 ms              ║
║ Max load time:               1 ms              ║
╠══════════════════════════════════════════════════╣
║ Cache hits:                  3                  ║
║ Cache misses:                0                  ║
║ Cache hit rate:         100.0%                 ║
║ Bytes from cache:      1426251 bytes           ║
║ Total bytes loaded:    1426251 bytes           ║
╠══════════════════════════════════════════════════╣
║ RECENT LOADS (last 5):                           ║
║   💾 /index.html                      1ms║
║   💾 /assets/index-B3g6t1vt.js        0ms║
║   💾 /assets/index-DGe01YXs.css       1ms║
╚══════════════════════════════════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 assets. 2ms total. 100% cache hit rate.&lt;/p&gt;

&lt;p&gt;Notice the emoji: 💾 = served from cache. The JS bundle took 0ms (less than 1ms, rounded down). HTML took 1ms. CSS took 1ms.&lt;/p&gt;




&lt;p&gt;📈 The Side-by-Side Comparison&lt;/p&gt;

&lt;p&gt;Metric First Load Reload (Cache) Improvement&lt;br&gt;
Total load time 77ms 2ms 38.5x faster&lt;br&gt;
Average time 25ms 0ms Instant&lt;br&gt;
Slowest asset 49ms (JS) 1ms (CSS) 49x faster&lt;br&gt;
Cache hit rate 0% 100% Perfect&lt;br&gt;
Bytes transferred 1.4MB 0 All from RAM&lt;br&gt;
HTTP errors 0 0 Perfect&lt;/p&gt;



&lt;p&gt;🧠 Why Is It So Fast?&lt;/p&gt;

&lt;p&gt;WebVirt Engine uses an in-memory LRU cache with SHA-1 ETags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First load:
  assets/index-B3g6t1vt.js → read from APK → cached in RAM → ETag generated

Second load:
  assets/index-B3g6t1vt.js → ETag match? → Yes → 304 Not Modified → 0ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;· No asset decoding (Android stores them compressed in the APK)&lt;br&gt;
· No disk I/O on reloads (everything in RAM)&lt;br&gt;
· No real HTTP header parsing (everything is local)&lt;br&gt;
· LruCache with memory awareness that cleans up on onTrimMemory()&lt;/p&gt;



&lt;p&gt;🔒 Security That Doesn't Sacrifice Speed&lt;/p&gt;

&lt;p&gt;Every response includes automatic security headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: default-src 'self'; script-src 'self'...
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Access-Control-Allow-Origin: *
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And CSP is fully configurable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cspPolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"default-src 'self'; script-src 'self' https://api.external.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;🤝 Plays Beautifully with Nexus&lt;/p&gt;

&lt;p&gt;Need native APIs? Nexus is a JavaScript ↔ Android bridge that doesn't interfere with WebVirt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WebVirt: serves the SPA&lt;/span&gt;
&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Nexus: export, import, PDF, camera, whatever you need&lt;/span&gt;
&lt;span class="nc"&gt;Nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExportAdapter&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImportAdapter&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pdf"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PdfAdapter&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFilePicker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attachToWebViewLifecycle&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Doesn't break WebVirt&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://app.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebVirt doesn't know Nexus exists. Nexus doesn't know WebVirt exists. They collaborate without coupling. This is real architecture.&lt;/p&gt;




&lt;p&gt;🏗️ The Architecture That Makes This Possible&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebView
  ├── WebViewClient → WebVirt (owner)
  │     └── shouldInterceptRequest() → assets/
  │
  ├── WebViewLifecycleObserver → Nexus (decorator)
  │     └── Wraps WebVirt's client without breaking it
  │
  └── JavascriptInterface → Nexus (parallel channel)
        └── window.__nexus.call("export", data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three layers that don't compete. Decorator Pattern for lifecycle. Builder Pattern for fluent configuration. Strategy Pattern for PathHandlers.&lt;/p&gt;




&lt;p&gt;📦 Production Proven&lt;/p&gt;

&lt;p&gt;This isn't a "hello world" library. It's running in production in a real financial app with:&lt;/p&gt;

&lt;p&gt;· ⚛️ React 18 + TypeScript + TailwindCSS&lt;br&gt;
· 📦 5MB of assets (1.4MB main bundle)&lt;br&gt;
· 🔀 React Router with lazy loading&lt;br&gt;
· 📤 Native JSON export&lt;br&gt;
· 📥 Native JSON import with FilePicker (no permissions required)&lt;br&gt;
· 📄 Native PDF export&lt;br&gt;
· 🔒 Restrictive CSP&lt;br&gt;
· ⚡ 77ms first load, 2ms reloads&lt;/p&gt;



&lt;p&gt;🚀 Coming Soon to GitHub &amp;amp; JitPack&lt;/p&gt;

&lt;p&gt;WebVirt Engine v3.1.1&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s1"&gt;'https://jitpack.io'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.github.fouzstack:webvirt-engine:3.1.1'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nexus v2.0.0&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.github.fouzstack:nexus:2.0.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;🎯 Is This for You?&lt;/p&gt;

&lt;p&gt;✅ Use WebVirt Engine if you:&lt;/p&gt;

&lt;p&gt;· Have an SPA in React/Vue/Svelte&lt;br&gt;
· Want full control without heavy dependencies&lt;br&gt;
· Need maximum offline performance&lt;br&gt;
· Value clean architecture and real decoupling&lt;/p&gt;

&lt;p&gt;❌ Not for you if you:&lt;/p&gt;

&lt;p&gt;· Need hot reload during development (for now)&lt;br&gt;
· Your company is already committed to Capacitor/Cordova&lt;br&gt;
· Your app is purely native with no web content&lt;/p&gt;




&lt;p&gt;🙏 Acknowledgments&lt;/p&gt;

&lt;p&gt;To Fouzstack for creating and maintaining both WebVirt and Nexus.&lt;br&gt;
To the GoF design patterns that still hold up 30 years later.&lt;br&gt;
To WebVirtMetrics and LoggingUtil for making it possible to collect this data without extra permissions.&lt;br&gt;
And to you, for reading this far.&lt;/p&gt;




&lt;p&gt;Questions? Ideas? Want to contribute? The repos will be open for issues and PRs as soon as they go live.&lt;/p&gt;

&lt;p&gt;Drop a comment: Which metric surprised you most? The 77ms first load or the 2ms cached reload?&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>performance</category>
      <category>react</category>
    </item>
    <item>
      <title>WebVirt + Nexus: Run Your React/Vue/Svelte SPA Inside an Android WebView</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sun, 03 May 2026 15:40:34 +0000</pubDate>
      <link>https://forem.com/gfouz/webvirt-nexus-run-your-reactvuesvelte-spa-inside-an-android-webview-409i</link>
      <guid>https://forem.com/gfouz/webvirt-nexus-run-your-reactvuesvelte-spa-inside-an-android-webview-409i</guid>
      <description>&lt;h1&gt;
  
  
  WebVirt + Nexus: Run Your React/Vue/Svelte SPA Inside an Android WebView — No Capacitor, No Server, No Permissions
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Two decoupled Android libraries: one serves your SPA from APK assets like a real web server, the other bridges JavaScript to native code. Used together in a production app serving local businesses. Open source, JitPack-ready. Engine v3 and Nexus v2 coming soon.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;You built a great SPA in React + Vite + TypeScript. It runs beautifully at &lt;code&gt;localhost:5173&lt;/code&gt;. Now your client wants it packaged as an Android app.&lt;/p&gt;

&lt;p&gt;Your options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Capacitor / Cordova&lt;/td&gt;
&lt;td&gt;+25MB runtime, opinionated structure, black-box WebView&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;file://&lt;/code&gt; protocol&lt;/td&gt;
&lt;td&gt;CORS broken, SPA routes 404, APIs blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded HTTP server (NanoHTTPD)&lt;/td&gt;
&lt;td&gt;Threads, ports, network permissions, overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw WebView&lt;/td&gt;
&lt;td&gt;You write everything from scratch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What you actually want: your SPA thinks it's on &lt;code&gt;https://app.local&lt;/code&gt;, everything comes from &lt;code&gt;assets/&lt;/code&gt;, zero server, zero permissions, zero overhead.&lt;/p&gt;

&lt;p&gt;That's exactly what WebVirt does.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is WebVirt?
&lt;/h2&gt;

&lt;p&gt;WebVirt is a Java Android library that intercepts &lt;code&gt;WebViewClient.shouldInterceptRequest()&lt;/code&gt; and serves your SPA assets directly from the APK — no actual server involved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://app.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your React app loads. Routes work. CORS works. No permissions needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Already published:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/fouzstack/fouzstack-webvirt" rel="noopener noreferrer"&gt;github.com/fouzstack/fouzstack-webvirt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JitPack: &lt;code&gt;implementation 'com.github.fouzstack:fouzstack-webvirt:1.0.0'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Real Production Use Case
&lt;/h2&gt;

&lt;p&gt;WebVirt v1 + Nexus are currently running in a financial management app used by small local businesses. The tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚛️ React + Vite + TypeScript + Tailwind CSS v4&lt;/li&gt;
&lt;li&gt;📦 ~5MB of assets served entirely from APK&lt;/li&gt;
&lt;li&gt;📋 Product lists of 90+ items — fast scrolling, no lag&lt;/li&gt;
&lt;li&gt;📤 JSON export (native)&lt;/li&gt;
&lt;li&gt;📥 JSON import with file picker (no permissions)&lt;/li&gt;
&lt;li&gt;📄 PDF export (native)&lt;/li&gt;
&lt;li&gt;🔒 Restrictive CSP&lt;/li&gt;
&lt;li&gt;⚡ Near-instant load time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No internet required. No server running. Just assets + native bridges.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming: WebVirt Engine v3 + Nexus v2
&lt;/h2&gt;

&lt;p&gt;The production experience revealed what was missing. Two new libraries are in the works:&lt;/p&gt;

&lt;h3&gt;
  
  
  WebVirt Engine — The Production-Grade Upgrade
&lt;/h3&gt;

&lt;p&gt;Built on the same &lt;code&gt;shouldInterceptRequest()&lt;/code&gt; approach but adds what real apps need:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart caching with LRU + ETags&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First load:  index.html → read → ETag generated → cached (LRU)
Second load: index.html → ETag match → 304 Not Modified → 0 bytes transferred
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real Range Requests for video streaming&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Client:  GET /video.mp4   Range: bytes=1048576-2097151
Server:  206 Partial Content
         Content-Range: bytes 1048576-2097151/52428800
         [only the requested bytes — file never fully loaded into RAM]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security headers on every response&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: default-src 'self'; script-src 'self'...
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Virtual API routes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/config"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\"version\": \"1.0\"}"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cspPolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"default-src 'self'; connect-src https://api.example.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture uses Chain of Responsibility for request routing and Strategy for path handlers — the same GoF patterns you'd use in a backend framework, applied to Android's WebView layer.&lt;/p&gt;




&lt;h3&gt;
  
  
  Nexus — The Native Bridge
&lt;/h3&gt;

&lt;p&gt;Nexus is a completely separate library for JavaScript-to-native communication. It doesn't know WebVirt exists. WebVirt doesn't know Nexus exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Nexus&lt;/span&gt; &lt;span class="n"&gt;nexus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withDebugMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withGlobalTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30_000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExportHandlerAdapter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImportHandlerAdapter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFilePicker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attachToWebViewLifecycle&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__nexus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What makes Nexus interesting:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart re-injection&lt;/strong&gt; — The JS runtime re-injects only on real page navigations, not on React Router hash changes (&lt;code&gt;#/products&lt;/code&gt;, &lt;code&gt;#/settings&lt;/code&gt;). This was a real pain point discovered in production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From the actual source&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;indexOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; 
    &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousBaseUrl&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;previousBaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;notifyPageLoaded&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Only re-inject here&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FilePicker without permissions&lt;/strong&gt; — Uses Android's Storage Access Framework (SAF). No &lt;code&gt;READ_EXTERNAL_STORAGE&lt;/code&gt; in your manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFilePicker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// That's it. No permissions.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Interceptor chain&lt;/strong&gt; — Add cross-cutting concerns (logging, auth, rate limiting) without touching your handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuthInterceptor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerInterceptor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LoggingInterceptor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExportHandler&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Event emission from native to JS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;emitEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"syncComplete"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"records"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__nexus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;syncComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Synced &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; records`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Architecture: Two Libraries That Don't Know Each Other
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│                      WebView                        │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │         WebVirt Engine                       │   │
│  │  shouldInterceptRequest() → assets/          │   │
│  │  "app.local/index.html" → real index.html    │   │
│  │  CSP, ETags, Range Requests, LRU Cache      │   │
│  └─────────────────────────────────────────────┘   │
│                    ▲                                │
│                    │ Decorates (doesn't override)   │
│  ┌─────────────────────────────────────────────┐   │
│  │         Nexus WebViewLifecycleObserver       │   │
│  │  Observes onPageFinished, onPageStarted      │   │
│  │  Delegates everything else to WebVirt        │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │         Nexus JSInterface (parallel channel) │   │
│  │  window.__nexus.call("export", data)         │   │
│  │  Does NOT go through shouldInterceptRequest  │   │
│  └─────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design decision: &lt;code&gt;WebViewLifecycleObserver&lt;/code&gt; wraps WebVirt's client using the &lt;strong&gt;Decorator pattern&lt;/strong&gt;. It adds lifecycle hooks without overriding or replacing WebVirt's request handling.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;WebVirt doesn't know...&lt;/th&gt;
&lt;th&gt;Nexus doesn't know...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;That Nexus exists&lt;/td&gt;
&lt;td&gt;That WebVirt exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;There's a JS bridge&lt;/td&gt;
&lt;td&gt;How assets are served&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What handlers are registered&lt;/td&gt;
&lt;td&gt;What virtual host is used&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is actual decoupling, not just marketing copy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;WebVirt Engine&lt;/th&gt;
&lt;th&gt;Capacitor&lt;/th&gt;
&lt;th&gt;Raw WebView&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime size&lt;/td&gt;
&lt;td&gt;0KB overhead&lt;/td&gt;
&lt;td&gt;~25MB&lt;/td&gt;
&lt;td&gt;0KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smart cache (LRU + ETags)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range Requests (video)&lt;/td&gt;
&lt;td&gt;✅ Real&lt;/td&gt;
&lt;td&gt;Delegates to system&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configurable CSP&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPA route fallback&lt;/td&gt;
&lt;td&gt;✅ Automatic&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline mode&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot reload (dev)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native JS bridge&lt;/td&gt;
&lt;td&gt;With Nexus&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No permissions needed&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interceptor chain&lt;/td&gt;
&lt;td&gt;✅ (Nexus)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;~5 min&lt;/td&gt;
&lt;td&gt;~2 hours&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Who Should Use This
&lt;/h2&gt;

&lt;p&gt;✅ React/Vue/Svelte developers who need Android without Capacitor's complexity&lt;br&gt;&lt;br&gt;
✅ Small teams who want full control with minimal dependencies&lt;br&gt;&lt;br&gt;
✅ Offline-first apps (no internet required after install)&lt;br&gt;&lt;br&gt;
✅ Apps where clean architecture matters  &lt;/p&gt;

&lt;p&gt;❌ If you need live hot reload during development&lt;br&gt;&lt;br&gt;
❌ If your team is already deep in the Capacitor ecosystem  &lt;/p&gt;




&lt;h2&gt;
  
  
  What's Available Now vs. Coming Soon
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WebVirt v1.0.0&lt;/td&gt;
&lt;td&gt;✅ GitHub + JitPack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nexus (production)&lt;/td&gt;
&lt;td&gt;✅ Running in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebVirt Engine v3.1.1&lt;/td&gt;
&lt;td&gt;🔜 Coming soon — github.com/fouzstack/webvirt-engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nexus v2.0.0&lt;/td&gt;
&lt;td&gt;🔜 Coming soon — github.com/fouzstack/nexus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin DSL (-ktx modules)&lt;/td&gt;
&lt;td&gt;🔜 Planned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests + benchmarks&lt;/td&gt;
&lt;td&gt;🔜 Planned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When Engine and Nexus launch, I'll post a follow-up with concrete benchmarks: cold start times, cache hit rates, memory footprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try WebVirt Now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="c1"&gt;// settings.gradle&lt;/span&gt;
&lt;span class="n"&gt;dependencyResolutionManagement&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s1"&gt;'https://jitpack.io'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// build.gradle (app)&lt;/span&gt;
&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.github.fouzstack:fouzstack-webvirt:1.0.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://app.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put your React/Vite build output in &lt;code&gt;assets/&lt;/code&gt; and you're done.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions, feedback, or contributions welcome.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What native handler would you find most useful with Nexus — camera access, biometrics, Bluetooth, local database? Let me know in the comments, it'll help prioritize what gets documented first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://github.com/fouzstack" rel="noopener noreferrer"&gt;@fouzstack&lt;/a&gt; — open source Android libraries for the WebView ecosystem.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>WebVirt Engine + Nexus: La dupla perfecta para SPAs en Android</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sun, 03 May 2026 15:12:15 +0000</pubDate>
      <link>https://forem.com/gfouz/webvirt-engine-nexus-la-dupla-perfecta-para-spas-en-android-3ajf</link>
      <guid>https://forem.com/gfouz/webvirt-engine-nexus-la-dupla-perfecta-para-spas-en-android-3ajf</guid>
      <description>&lt;p&gt;🚀 WebVirt Engine + Nexus: La dupla perfecta para SPAs en Android que sí respeta tu arquitectura&lt;/p&gt;

&lt;p&gt;Sirve tu SPA desde assets como un servidor web profesional + puente nativo JavaScript sin fricción. La arquitectura que Capacitor no puede ofrecerte.&lt;/p&gt;




&lt;p&gt;🤔 El problema que todos enfrentamos&lt;/p&gt;

&lt;p&gt;Tienes una SPA hermosa en React, Vue o Svelte. Corre perfecto en localhost:3000. Ahora necesitas llevarla a Android.&lt;/p&gt;

&lt;p&gt;Tus opciones tradicionales:&lt;/p&gt;

&lt;p&gt;Solución Resultado&lt;br&gt;
Capacitor / Cordova +30MB de runtime, configuración compleja, WebView propio&lt;br&gt;
file:// protocol CORS roto, rutas SPA no funcionan, APIs bloqueadas&lt;br&gt;
WebView + servidor embebido Overkill, hilos, puertos, problemas de red&lt;br&gt;
Script casero de 50 líneas Funciona... hasta que agregas video, PDF, o edge cases&lt;/p&gt;

&lt;p&gt;Lo que realmente quieres: Que tu SPA crea que está en &lt;a href="https://app.local" rel="noopener noreferrer"&gt;https://app.local&lt;/a&gt;, pero todo venga de assets/. Sin servidor. Sin permisos. Sin overhead.&lt;/p&gt;



&lt;p&gt;✨ La solución: WebVirt Engine + Nexus&lt;/p&gt;

&lt;p&gt;Dos librerías diseñadas para coexistir sin conocerse. Como hermanos que comparten cuarto sin pelearse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│                    WebView                          │
│                                                     │
│  ┌───────────────────────────────────────────────┐ │
│  │         WebVirt Engine (dueño del cliente)    │ │
│  │  shouldInterceptRequest() → assets/           │ │
│  │  "app.local/index.html" → index.html real     │ │
│  │  CSP, ETags, Range Requests, Caché LRU       │ │
│  └───────────────────────────────────────────────┘ │
│                    ▲                                │
│                    │ Decora (no sobrescribe)         │
│  ┌───────────────────────────────────────────────┐ │
│  │         Nexus WebViewLifecycleObserver        │ │
│  │  Observa onPageFinished, onPageStarted        │ │
│  │  Delega todo lo demás a WebVirt               │ │
│  └───────────────────────────────────────────────┘ │
│                                                     │
│  ┌───────────────────────────────────────────────┐ │
│  │         Nexus JSInterface (canal paralelo)    │ │
│  │  window.__nexus.call("export", data)          │ │
│  │  NO pasa por shouldInterceptRequest           │ │
│  └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;🎯 5 líneas y tu SPA está viva&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. WebVirt: Sirve tu SPA desde assets&lt;/span&gt;
&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Nexus: Puente nativo&lt;/span&gt;
&lt;span class="nc"&gt;Nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExportAdapter&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImportAdapter&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFilePicker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Nexus observa sin romper&lt;/span&gt;
&lt;span class="n"&gt;nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attachToWebViewLifecycle&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Tu SPA carga como si estuviera en un servidor real&lt;/span&gt;
&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://app.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo. Tu React app funcionando, rutas SPA intactas, APIs nativas disponibles. Sin configuración extraña.&lt;/p&gt;




&lt;p&gt;🏗️ Arquitectura: El secreto está en el desacoplamiento&lt;/p&gt;

&lt;p&gt;A diferencia de frameworks monolíticos, WebVirt Engine y Nexus usan capas que no compiten:&lt;/p&gt;

&lt;p&gt;Capa 1: Red Virtual → WebVirt Engine&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebViewClient.shouldInterceptRequest()
  ├── ¿Es app.local? → Sirve desde assets/
  ├── ¿Ruta SPA? → index.html (fallback)
  ├── ¿API virtual? → RouteHandler (respuesta JSON falsa)
  └── ¿Externo permitido? → Deja pasar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patrones: Builder, Chain of Responsibility, Strategy (PathHandlers)&lt;/p&gt;

&lt;p&gt;Capa 2: Puente Nativo → Nexus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebView.addJavascriptInterface(nexusInterface, "__nexus")
  └── window.__nexus.call("export", params, callbackId)
        ├── Thread pool para handlers
        ├── Timeout automático
        ├── Serialización JSON
        └── Callback al WebView vía evaluateJavascript()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patrones: Observer, Command, Mediator, Callback&lt;/p&gt;

&lt;p&gt;Capa 3: Ciclo de Vida → Nexus (Decorator)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebViewLifecycleObserver envuelve al cliente de WebVirt
  ├── shouldInterceptRequest() → delega a WebVirt ✅
  ├── onPageFinished() → WebVirt + notifica a Nexus ✅
  └── Todos los demás métodos → delega a WebVirt ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patrón: Decorator — El más elegante del sistema&lt;/p&gt;

&lt;p&gt;¿Por qué funciona tan bien?&lt;/p&gt;

&lt;p&gt;WebVirt NO sabe Nexus NO sabe&lt;br&gt;
Que Nexus existe Que WebVirt existe&lt;br&gt;
Que hay un bridge JS Cómo se sirven los assets&lt;br&gt;
Qué handlers hay registrados Qué host virtual se usa&lt;/p&gt;

&lt;p&gt;Esto es desacoplamiento real. No es marketing.&lt;/p&gt;



&lt;p&gt;🔒 Seguridad de grado producción&lt;/p&gt;

&lt;p&gt;WebVirt Engine hereda el enfoque de WebVirt original pero lo lleva a nivel empresarial:&lt;/p&gt;

&lt;p&gt;Defensa en profundidad&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capa 1: Path Traversal Protection
  ├── Normalización de rutas
  ├── Bloqueo de ".."
  └── Validación de esquema

Capa 2: Content Security Policy
  ├── CSP configurable por el desarrollador
  ├── Default restrictivo
  └── Headers de seguridad automáticos

Capa 3: Offline Mode
  ├── Bloquea TODAS las peticiones externas
  └── Whitelist de dominios explícita

Capa 4: Extensiones permitidas
  └── Solo sirve archivos con extensiones seguras
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers de seguridad automáticos&lt;/p&gt;

&lt;p&gt;Cada respuesta de WebVirt Engine incluye:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: default-src 'self'; script-src 'self'...
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Access-Control-Allow-Origin: *
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y tú puedes personalizar el CSP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cspPolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"default-src 'self'; script-src 'self' https://api.externa.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;🎬 Streaming de video sin cargar en RAM&lt;/p&gt;

&lt;p&gt;¿Tu SPA tiene videos? WebVirt Engine soporta Range Requests reales:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cliente: GET /video.mp4
         Range: bytes=1048576-2097151

Servidor: 206 Partial Content
          Content-Range: bytes 1048576-2097151/52428800
          [solo envía los bytes solicitados]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El archivo nunca se carga completo en memoria. Streaming real desde InputStream.&lt;/p&gt;




&lt;p&gt;⚡ Rendimiento: Caché que aprende&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Primera carga:
  index.html → leído → ETag generado → cacheado (LRU)

Segunda carga:
  index.html → ETag coincide → 304 Not Modified → 0 bytes transferidos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;· LruCache con control de memoria real&lt;br&gt;
· ETags SHA-1 para validación&lt;br&gt;
· If-Modified-Since para archivos&lt;br&gt;
· Se limpia automáticamente con onTrimMemory()&lt;/p&gt;



&lt;p&gt;🧩 ¿Juntos o separados?&lt;/p&gt;

&lt;p&gt;Solo WebVirt Engine&lt;/p&gt;

&lt;p&gt;Perfecto para SPAs que no necesitan APIs nativas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/config"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{...}"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;// API falsa&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solo Nexus&lt;/p&gt;

&lt;p&gt;Perfecto para WebViews tradicionales que necesitan bridge nativo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"camera"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CameraHandler&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebVirt Engine + Nexus&lt;/p&gt;

&lt;p&gt;La combinación completa. SPA servida profesionalmente + APIs nativas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exportAdapter&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;importAdapter&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"camera"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cameraAdapter&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFilePicker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;nexus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attachToWebViewLifecycle&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;📊 Comparación honesta&lt;/p&gt;

&lt;p&gt;Característica WebVirt Engine Capacitor Cordova WebView puro&lt;br&gt;
Tamaño runtime 0KB (solo tu código) ~25MB ~15MB 0KB&lt;br&gt;
Caché inteligente ✅ LRU + ETags ❌ ❌ ❌&lt;br&gt;
Range Requests ✅ Streaming real ✅ ✅ ❌&lt;br&gt;
CSP personalizable ✅ ❌ ✅ ❌&lt;br&gt;
Rutas SPA ✅ Automático ✅ ✅ ❌&lt;br&gt;
Offline mode ✅ Built-in ✅ ✅ ✅&lt;br&gt;
Hot reload ❌ ✅ ✅ ❌&lt;br&gt;
JS Bridge nativo Con Nexus ✅ ✅ Manual&lt;br&gt;
Sin permisos ✅ ✅ ✅ ✅&lt;br&gt;
Curva aprendizaje 5 minutos 2 horas 3 horas Variable&lt;br&gt;
Desacoplamiento ✅ Total ❌ ❌ N/A&lt;/p&gt;




&lt;p&gt;🚀 Producción probada&lt;/p&gt;

&lt;p&gt;No es teoría. WebVirt (versión lite) + Nexus ya funcionan en producción en una app financiera real:&lt;/p&gt;

&lt;p&gt;· ⚛️ React + TypeScript + TailwindCSS&lt;br&gt;
· 📦 5MB de assets servidos sin conexión&lt;br&gt;
· 📤 Export JSON nativo&lt;br&gt;
· 📥 Import JSON con FilePicker (sin permisos)&lt;br&gt;
· 📄 Export PDF nativo&lt;br&gt;
· 🔒 CSP restrictivo&lt;br&gt;
· ⚡ Carga instantánea&lt;/p&gt;




&lt;p&gt;📦 Próximamente&lt;/p&gt;

&lt;p&gt;WebVirt Engine v3.1.1&lt;/p&gt;

&lt;p&gt;· Repositorio: github.com/fouzstack/webvirt-engine&lt;br&gt;
· JitPack: implementation 'com.github.fouzstack:webvirt-engine:3.1.1'&lt;br&gt;
· Documentación completa&lt;br&gt;
· Módulo Kotlin -ktx&lt;/p&gt;

&lt;p&gt;Nexus v2.0.0&lt;/p&gt;

&lt;p&gt;· Repositorio: github.com/fouzstack/nexus&lt;br&gt;
· JitPack: implementation 'com.github.fouzstack:nexus:2.0.0'&lt;br&gt;
· Documentación completa&lt;br&gt;
· Templates de proyecto&lt;/p&gt;




&lt;p&gt;🎯 ¿Quién debería usar esto?&lt;/p&gt;

&lt;p&gt;✅ Desarrolladores React/Vue/Svelte que necesitan Android sin Capacitor&lt;/p&gt;

&lt;p&gt;✅ Equipos pequeños que quieren control total sin dependencias pesadas&lt;/p&gt;

&lt;p&gt;✅ Apps offline-first que necesitan rendimiento máximo&lt;/p&gt;

&lt;p&gt;✅ Cualquiera que valore arquitecturas limpias y desacoplamiento real&lt;/p&gt;

&lt;p&gt;❌ Si necesitas hot reload en desarrollo (por ahora)&lt;/p&gt;

&lt;p&gt;❌ Si tu empresa ya está comprometida con Capacitor/Cordova&lt;/p&gt;




&lt;p&gt;💬 Lo que dicen los patrones de diseño&lt;/p&gt;

&lt;p&gt;"Programa contra interfaces, no implementaciones" — GoF&lt;/p&gt;

&lt;p&gt;WebVirt Engine y Nexus no se conocen. Se respetan. Colaboran sin acoplarse.&lt;/p&gt;

&lt;p&gt;"Separa lo que cambia de lo que permanece" — Clean Architecture&lt;/p&gt;

&lt;p&gt;WebVirt cambia (assets, CSP, rutas). Nexus cambia (handlers, timeouts).&lt;br&gt;
Ninguno rompe al otro.&lt;/p&gt;

&lt;p&gt;"Una clase debería tener solo una razón para cambiar" — SRP&lt;/p&gt;

&lt;p&gt;WebVirt: servir assets. Nexus: bridge nativo. Una responsabilidad cada uno.&lt;/p&gt;




&lt;p&gt;🔗 Enlaces&lt;/p&gt;

&lt;p&gt;· WebVirt (lite): github.com/fouzstack/fouzstack-webvirt&lt;br&gt;
· WebVirt Engine: Próximamente en github.com/fouzstack/webvirt-engine&lt;br&gt;
· Nexus: Próximamente en github.com/fouzstack/nexus&lt;/p&gt;




&lt;p&gt;🙏 Agradecimientos&lt;/p&gt;

&lt;p&gt;A la comunidad de Android que sigue buscando formas más limpias de integrar WebViews.&lt;br&gt;
A los patrones de diseño del GoF que 30 años después siguen vigentes.&lt;br&gt;
Y a ti, por leer hasta aquí.&lt;/p&gt;




&lt;p&gt;¿Preguntas? ¿Ideas? ¿Quieres contribuir? Los repositorios estarán abiertos para issues y PRs.&lt;/p&gt;

&lt;p&gt;Comenta abajo: ¿Qué handler nativo te gustaría ver implementado con Nexus?&lt;/p&gt;

</description>
      <category>android</category>
      <category>javascript</category>
      <category>mobile</category>
      <category>spanish</category>
    </item>
    <item>
      <title>WebVirt : Load your SPA in Android WebView with 3 lines of code</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:02:38 +0000</pubDate>
      <link>https://forem.com/gfouz/webvirt-load-your-spa-in-android-webview-with-3-lines-of-code-2d9l</link>
      <guid>https://forem.com/gfouz/webvirt-load-your-spa-in-android-webview-with-3-lines-of-code-2d9l</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# 🚀 WebVirt: Load your SPA in Android WebView with just 3 lines of code&lt;/span&gt;

Tired of writing 100+ lines of boilerplate just to display a SPA in a WebView?

&lt;span class="gs"&gt;**WebVirt reduces it to 3 lines.**&lt;/span&gt;
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gu"&gt;## 😩 The problem (we've all been there)&lt;/span&gt;

Packaging a SPA (React, Vue, Angular, Svelte, SolidJs...) into an Android WebView should be trivial… but it's not:
&lt;span class="p"&gt;
-&lt;/span&gt; Manually configuring &lt;span class="sb"&gt;`WebViewClient`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Intercepting requests (&lt;span class="sb"&gt;`shouldInterceptRequest`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Resolving SPA routes (&lt;span class="sb"&gt;`/products/42`&lt;/span&gt; → &lt;span class="sb"&gt;`index.html`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Handling MIME types correctly
&lt;span class="p"&gt;-&lt;/span&gt; Implementing asset caching
&lt;span class="p"&gt;-&lt;/span&gt; Preventing directory traversal attacks
&lt;span class="p"&gt;-&lt;/span&gt; Controlling external traffic

All that… before you even start building your app.
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gu"&gt;## ✨ The solution: WebVirt&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
WebVirt.with(this)&lt;br&gt;
    .host("myapp.local")&lt;br&gt;
    .bind(webView);&lt;/p&gt;

&lt;p&gt;webView.loadUrl("&lt;a href="https://myapp.local/%22" rel="noopener noreferrer"&gt;https://myapp.local/"&lt;/a&gt;);&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
That's it.

WebVirt turns your APK into an internal virtual web server.

Your SPA lives in /assets and is served exactly like it would be in production.

---

🔒 Offline-first security by default

WebVirt follows a simple rule:

If you don't explicitly allow it, it's blocked.

· 🚫 External traffic blocked by default
· ✅ Whitelist with allowExternalDomains()
· 🔌 Offline mode with offlineOnly(true)
· 🛡 Directory traversal protection

📦 Automatic HTTP headers

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
WebVirt.with(this)&lt;br&gt;
    .host("myapp.local")&lt;br&gt;
    .allowExternalDomains("api.myapp.com", "cdn.myapp.com")&lt;br&gt;
    .bind(webView);&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

🧠 Automatic SPA routing

Using React Router, Vue Router, or Angular?

WebVirt detects extensionless routes and serves index.html automatically:

· /about → index.html ✅
· /products/42 → index.html ✅
· /app.js → app.js ✅
· /style.css → style.css ✅

No configuration. No hacks. No 404s.

---

⚡ Smart in-memory caching

Optimized for real performance:

· 🧠 index.html → memory-cached (never stale)
· 📦 Assets (JS, CSS, JSON, WASM, fonts) → immutable cache (max-age=31536000)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
webVirt.clearCache(); // Ideal in onDestroy()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

📦 Installation

settings.gradle

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
gradle&lt;br&gt;
dependencyResolutionManagement {&lt;br&gt;
    repositories {&lt;br&gt;
        maven { url '&lt;a href="https://jitpack.io" rel="noopener noreferrer"&gt;https://jitpack.io&lt;/a&gt;' }&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
build.gradle

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
gradle&lt;br&gt;
dependencies {&lt;br&gt;
    implementation 'com.github.fouzstack:fouzstack-webvirt:v1.0.0'&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

🎨 Advanced configuration (optional)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
WebVirt.with(this)&lt;br&gt;
    .host("myapp.local")&lt;br&gt;
    .subfolder("dist")&lt;br&gt;
    .allowExternalDomains("api.example.com")&lt;br&gt;
    .config(cfg -&amp;gt; {&lt;br&gt;
        cfg.setCacheEnabled(true);&lt;br&gt;
        cfg.setJavaScriptEnabled(true);&lt;br&gt;
        cfg.setDomStorageEnabled(true);&lt;br&gt;
    })&lt;br&gt;
    .bind(webView);&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Full access to WebViewSettings without breaking the fluent API.

---

📊 Before vs After

Without WebVirt With WebVirt
~100 lines of code 3 lines
Manual WebViewClient Automatic
Broken SPA routing Works out-of-the-box
Manual caching Included
MIME type hell Included
Manual security Offline-first

---

🌐 Repository

👉 github.com/fouzstack/fouzstack-webvirt

---

🤔 Why "WebVirt"?

Because your APK becomes a:

Virtual web server without a real server

· No ports
· No network
· No real localhost

Just your SPA running like in production.

---

🧪 Requirements

· Android 4.4+ (API 19)
· AndroidX
· Compile SDK 34+

---

📄 License

MIT — use it without fear.

---

💭 Final thought

The best tools aren't the biggest.

They're the ones you set up in seconds… and then forget.

WebVirt doesn't want the spotlight.
It wants you to focus on your product.

---

⭐ Did it help you?

· Star it on GitHub
· Open an issue if you find something
· PRs are welcome

Happy coding 🚀

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>java</category>
      <category>mobile</category>
      <category>showdev</category>
    </item>
    <item>
      <title>🚀 WebVirt: Carga tu SPA en Android WebView con solo 3 líneas de código</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Fri, 24 Apr 2026 05:16:55 +0000</pubDate>
      <link>https://forem.com/gfouz/webvirt-carga-tu-spa-en-android-webview-con-solo-3-lineas-de-codigo-5j7</link>
      <guid>https://forem.com/gfouz/webvirt-carga-tu-spa-en-android-webview-con-solo-3-lineas-de-codigo-5j7</guid>
      <description>&lt;p&gt;La primera vez que intenté cargar una SPA de React en un WebView pensé que sería trivial. No lo fue. Terminé escribiendo más de 150 líneas solo para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;servir archivos desde &lt;code&gt;assets&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;interceptar requests manualmente&lt;/li&gt;
&lt;li&gt;arreglar rutas de React Router&lt;/li&gt;
&lt;li&gt;y evitar errores 404 en producción&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Después de repetir ese proceso varias veces… decidí simplificarlo. Así nació &lt;strong&gt;WebVirt&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 El problema
&lt;/h2&gt;

&lt;p&gt;Cargar una SPA (React, Vue, Angular, Svelte) dentro de un &lt;code&gt;WebView&lt;/code&gt; en Android implica:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WebViewAssetLoader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shouldInterceptRequest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;manejo manual de rutas&lt;/li&gt;
&lt;li&gt;configuración de headers y MIME types&lt;/li&gt;
&lt;li&gt;cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No es difícil… pero sí repetitivo, verboso y propenso a errores.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ La solución
&lt;/h2&gt;

&lt;p&gt;Con WebVirt, todo se reduce a esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"miapp.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo.&lt;/p&gt;

&lt;p&gt;Tu WebView ya puede cargar una SPA completa desde assets sin configuración adicional.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ ¿Qué hace WebVirt?
&lt;/h3&gt;

&lt;p&gt;WebVirt actúa como un servidor web virtual dentro de tu APK.&lt;/p&gt;

&lt;p&gt;Intercepta las peticiones del WebView y sirve los archivos directamente desde assets, resolviendo automáticamente los problemas más comunes.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔁 SPA routing automático
&lt;/h3&gt;

&lt;p&gt;Si usas React Router (o similar), no necesitas configuración extra.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/dashboard&lt;/code&gt; → WebVirt sirve &lt;code&gt;index.html&lt;/code&gt; → tu router toma el control&lt;/p&gt;

&lt;h3&gt;
  
  
  💾 Caché inteligente
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;HTML → no cacheado (para reflejar cambios)&lt;/li&gt;
&lt;li&gt;JS/CSS → cacheado agresivamente (tipo CDN)&lt;/li&gt;
&lt;li&gt;Otros archivos → streaming directo&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔒 Seguridad básica integrada
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prevención de directory traversal (&lt;code&gt;../&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Validación de rutas&lt;/li&gt;
&lt;li&gt;Headers seguros por defecto&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📁 MIME types listos
&lt;/h3&gt;

&lt;p&gt;Soporte para HTML, JS, CSS, JSON, imágenes, fuentes, WASM y más.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Ejemplo real
&lt;/h3&gt;

&lt;p&gt;Supongamos que tienes un build de React (Vite, CRA, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/src/main/assets/
├── index.html
└── assets/
    ├── index.js
    └── index.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solo haces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://miapp.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¡Y listo!&lt;/p&gt;

&lt;p&gt;✅ React Router funciona&lt;br&gt;
✅ No hay 404 en rutas internas&lt;br&gt;
✅ No necesitas lógica extra&lt;/p&gt;
&lt;h3&gt;
  
  
  🧠 ¿Cómo funciona?
&lt;/h3&gt;

&lt;p&gt;A alto nivel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebView → intercept request → WebVirt → assets → response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Detecta si la URL pertenece a tu host virtual.&lt;/li&gt;
&lt;li&gt;Resuelve si es archivo o ruta SPA.&lt;/li&gt;
&lt;li&gt;Sirve desde memoria o desde assets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sin magia, solo encapsulación de lo que normalmente harías a mano.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 ¿Por qué usarlo?
&lt;/h3&gt;

&lt;p&gt;Porque evita repetir lo mismo en cada proyecto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;menos código boilerplate&lt;/li&gt;
&lt;li&gt;menos errores en routing&lt;/li&gt;
&lt;li&gt;mejor experiencia de desarrollo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No reemplaza las herramientas existentes,&lt;br&gt;
simplemente las abstrae en una API más simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Uso rápido en tu MainActivity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebVirt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"miapp.local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://miapp.local/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔮 Próximamente
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Publicación en Maven Central / JitPack&lt;/li&gt;
&lt;li&gt;Kotlin DSL&lt;/li&gt;
&lt;li&gt;LRU cache configurable&lt;/li&gt;
&lt;li&gt;Hooks para personalización&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📦 Código y documentación
&lt;/h3&gt;

&lt;p&gt;Estoy preparando el repo público con README completo y ejemplos.&lt;/p&gt;

&lt;p&gt;Si te interesa el proyecto o quieres probarlo antes:&lt;/p&gt;

&lt;p&gt;👉 Déjame un comentario&lt;br&gt;
👉 o sígueme para actualizaciones&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Conclusión
&lt;/h3&gt;

&lt;p&gt;Cargar una SPA en un WebView no debería tomar 150 líneas.&lt;/p&gt;

&lt;p&gt;Con WebVirt:&lt;/p&gt;

&lt;p&gt;✅ es simple&lt;br&gt;
✅ es rápido&lt;br&gt;
✅ funciona&lt;/p&gt;

&lt;p&gt;¿Te ha pasado lo mismo con WebView?&lt;br&gt;
Me interesa saber cómo lo estás resolviendo tú 👇&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>showdev</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Tu WebView no necesita un servidor HTTP. Necesita un Virtual Host.</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sun, 19 Apr 2026 09:01:38 +0000</pubDate>
      <link>https://forem.com/gfouz/tu-webview-no-necesita-un-servidor-http-necesita-un-virtual-host-3jca</link>
      <guid>https://forem.com/gfouz/tu-webview-no-necesita-un-servidor-http-necesita-un-virtual-host-3jca</guid>
      <description>&lt;h3&gt;
  
  
  Cómo un patrón de los años 90 resolvió uno de los problemas más frustrantes del desarrollo móvil moderno
&lt;/h3&gt;




&lt;p&gt;Hay una idea en ingeniería de software que reaparece una y otra vez, con décadas de distancia y en plataformas completamente distintas. Una idea tan elegante que la industria la redescubre cada cierto tiempo, la viste con nueva ropa, y la presenta como si fuera nueva.&lt;/p&gt;

&lt;p&gt;Esta es la historia de esa idea. Y de cómo terminó viviendo dentro de una app Android.&lt;/p&gt;




&lt;h2&gt;
  
  
  El problema que nadie quiere admitir
&lt;/h2&gt;

&lt;p&gt;Construir una app móvil con React, Vue o cualquier framework web moderno es tentador. Tienes un equipo que ya sabe web, una base de código compartida, y la promesa de "escribe una vez, corre en todas partes."&lt;/p&gt;

&lt;p&gt;Hasta que llega el momento de cargar tu app en Android sin conexión a internet.&lt;/p&gt;

&lt;p&gt;Android no tiene un servidor local. WebView no sabe hablar con tu carpeta &lt;code&gt;assets/&lt;/code&gt; como si fuera un dominio real. Y React Router — el corazón de la navegación en casi toda SPA moderna — necesita que las rutas como &lt;code&gt;/dashboard&lt;/code&gt; o &lt;code&gt;/perfil/123&lt;/code&gt; devuelvan siempre el mismo &lt;code&gt;index.html&lt;/code&gt;, sin importar qué tan profunda sea la URL.&lt;/p&gt;

&lt;p&gt;Sin eso, tu app se rompe. El usuario navega, presiona atrás, recarga — y ve una pantalla en blanco o un error 404 que no debería existir.&lt;/p&gt;

&lt;p&gt;La solución obvia sería levantar un servidor HTTP local. Pero eso consume batería, requiere permisos, complica el ciclo de vida de la app, y abre vectores de seguridad innecesarios. No es la respuesta correcta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La respuesta correcta tiene 30 años de historia detrás.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1991: Cuando los servidores aprendieron a mentir (bien)
&lt;/h2&gt;

&lt;p&gt;En los primeros días de la web, cada URL correspondía a un archivo físico en el disco del servidor. &lt;code&gt;/pagina.html&lt;/code&gt; era literalmente un archivo llamado &lt;code&gt;pagina.html&lt;/code&gt;. Simple, predecible, rígido.&lt;/p&gt;

&lt;p&gt;El problema llegó cuando los sitios crecieron. Un servidor físico, múltiples dominios. ¿Cómo diferenciaba el servidor a cuál de ellos le hablaba el visitante?&lt;/p&gt;

&lt;p&gt;La respuesta fue el &lt;strong&gt;Virtual Hosting&lt;/strong&gt;: la capacidad de un solo servidor de hacerse pasar por muchos, respondiendo diferente según el dominio que el cliente pedía. Apache HTTP Server lo popularizó a mediados de los 90. Un servidor físico podía alojar &lt;code&gt;empresa-a.com&lt;/code&gt;, &lt;code&gt;empresa-b.com&lt;/code&gt; y &lt;code&gt;empresa-c.com&lt;/code&gt; simultáneamente, cada uno con su propio espacio de archivos, sus propias reglas, su propia identidad.&lt;/p&gt;

&lt;p&gt;Era, en esencia, enseñarle a un servidor a interceptar una petición y decidir: &lt;em&gt;¿quién eres tú y qué te mereces recibir?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nginx lo refinó. Los CDNs lo escalaron a millones de dominios. Y el patrón quedó grabado en el ADN de la infraestructura web moderna.&lt;/p&gt;




&lt;h2&gt;
  
  
  2010: Las SPAs rompen todo (de nuevo)
&lt;/h2&gt;

&lt;p&gt;Cuando llegaron las Single Page Applications, el Virtual Hosting tuvo que evolucionar.&lt;/p&gt;

&lt;p&gt;Una SPA tiene una sola entrada: &lt;code&gt;index.html&lt;/code&gt;. Todo lo demás — rutas, vistas, estados — es JavaScript puro que corre en el navegador. El servidor no sabe nada de &lt;code&gt;/dashboard&lt;/code&gt; o &lt;code&gt;/usuario/perfil&lt;/code&gt;. Cuando alguien recarga esa URL, el servidor busca un archivo llamado &lt;code&gt;dashboard&lt;/code&gt; en el disco. No existe. 404.&lt;/p&gt;

&lt;p&gt;La solución fue tan simple como poderosa: &lt;strong&gt;el fallback a index.html&lt;/strong&gt;. Si el servidor no encuentra el archivo estático solicitado, en lugar de responder con un error, devuelve siempre &lt;code&gt;index.html&lt;/code&gt;. React Router toma el control desde ahí y reconstruye la vista correcta.&lt;/p&gt;

&lt;p&gt;En Nginx, son dos líneas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;En ASP.NET Core, Microsoft lo formalizó como middleware oficial con MapFallbackToFile("index.html"). El patrón tenía nombre, documentación, y respaldo institucional.&lt;/p&gt;

&lt;p&gt;Pero todo eso asume que tienes un servidor. ¿Qué pasa cuando no lo tienes?&lt;/p&gt;



&lt;p&gt;2024: El mismo patrón, sin servidor&lt;/p&gt;

&lt;p&gt;Android tiene un mecanismo poco conocido pero extraordinariamente poderoso: shouldInterceptRequest. Cada vez que el WebView está a punto de hacer una petición de red, se detiene y pregunta: ¿alguien quiere manejar esto antes que yo?&lt;/p&gt;

&lt;p&gt;La mayoría de los desarrolladores lo ignora. Algunos lo usan para bloquear anuncios o inyectar headers. Pero hay una tercera posibilidad: usarlo para construir un servidor virtual completo que nunca hace una petición de red real.&lt;/p&gt;

&lt;p&gt;Eso es exactamente lo que hace VirtualHostManager.&lt;/p&gt;

&lt;p&gt;Cuando el WebView intenta cargar &lt;a href="https://app.gfouz.com/assets/main.js" rel="noopener noreferrer"&gt;https://app.gfouz.com/assets/main.js&lt;/a&gt;, la clase intercepta esa petición antes de que salga al mundo. Busca el archivo en la carpeta assets/ del APK. Lo sirve directamente, con el MIME type correcto, con los headers de caché apropiados, sin tocar internet.&lt;/p&gt;

&lt;p&gt;Y cuando el WebView pide &lt;a href="https://app.gfouz.com/dashboard" rel="noopener noreferrer"&gt;https://app.gfouz.com/dashboard&lt;/a&gt; — una ruta de React Router que no existe como archivo — la clase reconoce que no tiene extensión, asume que es una ruta SPA, y devuelve index.html. React Router hace el resto.&lt;/p&gt;

&lt;p&gt;El servidor está dentro de la app. El dominio es ficticio. Las peticiones nunca salen del dispositivo. Y React Router nunca se entera de la diferencia.&lt;/p&gt;



&lt;p&gt;Por qué esto importa más de lo que parece&lt;/p&gt;

&lt;p&gt;La mayoría de soluciones para este problema siguen uno de dos caminos: usar file:// (que rompe CORS y tiene restricciones de seguridad severas) o levantar un servidor HTTP local con librerías como NanoHTTPD (que consume recursos y complica el ciclo de vida de la app).&lt;/p&gt;

&lt;p&gt;VirtualHostManager toma un tercer camino: habitar la infraestructura existente del WebView en lugar de luchar contra ella o rodearla.&lt;/p&gt;

&lt;p&gt;El resultado es una clase que:&lt;/p&gt;

&lt;p&gt;· No abre puertos de red.&lt;br&gt;
· No requiere permisos adicionales.&lt;br&gt;
· Cachea solo lo que vale cachear (index.html, que se pide en cada navegación).&lt;br&gt;
· Transmite archivos grandes como streams, sin cargarlos completos en RAM.&lt;br&gt;
· Valida rutas para prevenir path traversal attacks.&lt;br&gt;
· Soporta 25 tipos de archivo con sus MIME types correctos.&lt;br&gt;
· Funciona en Android 4.1 en adelante.&lt;/p&gt;

&lt;p&gt;Y hace todo eso en menos de 200 líneas de Java.&lt;/p&gt;



&lt;p&gt;El patrón que no envejece&lt;/p&gt;

&lt;p&gt;Lo fascinante de esta historia no es la clase en sí. Es que el problema — ¿cómo sirvo contenido estático con inteligencia? — ha tenido exactamente la misma solución en cada era de la computación:&lt;/p&gt;

&lt;p&gt;1995 → Apache Virtual Hosting&lt;br&gt;
2005 → Nginx try_files&lt;br&gt;
2016 → ASP.NET Core MapFallbackToFile&lt;br&gt;
2024 → Android shouldInterceptRequest + SPA fallback&lt;/p&gt;

&lt;p&gt;El contexto cambia. El hardware cambia. Los frameworks cambian. El patrón permanece.&lt;/p&gt;

&lt;p&gt;Eso, en ingeniería de software, es la señal más confiable de que algo es fundamentalmente correcto.&lt;/p&gt;

&lt;p&gt;VirtualHostManager es parte de FouzStack — una colección de soluciones de ingeniería para desarrollo móvil con tecnologías web.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/gfouz" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F82347049%3Fv%3D4%3Fs%3D400" height="300" class="m-0" width="300"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/gfouz" rel="noopener noreferrer" class="c-link"&gt;
            gfouz (Giovani Fouz) · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Welcome,  I'm excited to share my journey,
skills, and projects with you. Dive in, explore
my projects, and get to know the developer behind
the screen. - gfouz
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>android</category>
      <category>architecture</category>
      <category>mobile</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Android Virtual Host and Fake Server</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Tue, 14 Apr 2026 20:29:30 +0000</pubDate>
      <link>https://forem.com/gfouz/android-virtual-host-and-fake-server-3i9f</link>
      <guid>https://forem.com/gfouz/android-virtual-host-and-fake-server-3i9f</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🚀 Virtual Host para Android: Mi réplica nativa del SetVirtualHostNameToFolderMapping de Microsoft WebView2... ¡pero para WebView de Android!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Durante algún tiempo observé una característica elegante de &lt;strong&gt;WebView2 en .NET&lt;/strong&gt;: &lt;code&gt;SetVirtualHostNameToFolderMapping&lt;/code&gt;. Te permite mapear un hostname virtual (como &lt;code&gt;https://miapp.local/&lt;/code&gt;) directamente a una carpeta local, sirviendo archivos estáticos de forma limpia, segura y con soporte perfecto para &lt;strong&gt;Single Page Applications (SPA)&lt;/strong&gt; como React, Svelte, Vue o Angular.&lt;/p&gt;

&lt;p&gt;En Android, la historia siempre ha sido más complicada. La mayoría de soluciones recurren a trucos, copiar assets a archivos internos, servidores locales embebidos (como &lt;code&gt;react-native-static-server&lt;/code&gt;) o frameworks completos que añaden capas de abstracción.&lt;/p&gt;

&lt;p&gt;Hasta ahora.&lt;/p&gt;

&lt;p&gt;Presento &lt;strong&gt;VirtualHostManager&lt;/strong&gt;: mi implementación propia, ligera y optimizada en Java/Kotlin para Android. Una clase con &lt;strong&gt;responsabilidad única&lt;/strong&gt; que transforma tu WebView en un mini-servidor virtual inteligente, inspirada directamente en la solución de Microsoft pero adaptada al ecosistema Android.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hace exactamente?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sirve todos tus assets estáticos desde la carpeta &lt;code&gt;assets/&lt;/code&gt; (o un subdirectorio).&lt;/li&gt;
&lt;li&gt;Maneja automáticamente el &lt;strong&gt;fallback a index.html&lt;/strong&gt; para rutas de React Router, Vue Router, etc. (cualquier ruta sin extensión se trata como SPA).&lt;/li&gt;
&lt;li&gt;Soporta dominio virtual seguro (&lt;code&gt;https://tudominio.local/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Optimizada para bajo consumo de RAM: solo cachea &lt;code&gt;index.html&lt;/code&gt; (el archivo más solicitado), el resto se sirve con &lt;code&gt;InputStream&lt;/code&gt; directo sin cargar todo en memoria.&lt;/li&gt;
&lt;li&gt;Manejo seguro de paths (evita &lt;code&gt;../&lt;/code&gt; y accesos no deseados).&lt;/li&gt;
&lt;li&gt;Headers inteligentes de caché (immutable para JS/CSS/imágenes).&lt;/li&gt;
&lt;li&gt;Compatible con versiones antiguas de Android.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aquí el corazón de la clase (código completo en los comentarios o en el repo si lo publico):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ejemplo de uso súper simple&lt;/span&gt;
&lt;span class="nc"&gt;VirtualHostManager&lt;/span&gt; &lt;span class="n"&gt;vhm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VirtualHostManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"miapp.local"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dist/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWebViewClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebViewClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WebResourceResponse&lt;/span&gt; &lt;span class="nf"&gt;shouldInterceptRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebView&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebResourceRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vhm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shouldIntercept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUrl&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vhm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serveStaticAsset&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUrl&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shouldInterceptRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vhm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVirtualBaseUrl&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"index.html"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ¿Es esto una innovación a nivel de sistemas y arquitectura?
&lt;/h3&gt;

&lt;p&gt;No, no es una innovación&lt;br&gt;
disruptiva a nivel de sistemas o arquitectura, pero sí es una &lt;strong&gt;buena solución y mejora de calidad&lt;/strong&gt; sobre las soluciones típicas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Algunas ventajas:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Portabilidad de conceptos maduros de escritorio a móvil&lt;/strong&gt; — Llevamos una feature potente de WebView2 (.NET) de Microsoft al mundo Android de forma limpia y nativa, sin depender de bibliotecas pesadas ni servidores embebidos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Arquitectura minimalista y eficiente&lt;/strong&gt; — En lugar de lanzar un servidor HTTP completo (con overhead de hilos, sockets y consumo de batería), usamos el mecanismo nativo de &lt;code&gt;shouldInterceptRequest&lt;/code&gt; del WebView. Es más directo, ligero y respeta el ciclo de vida de la app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimización consciente de recursos móviles&lt;/strong&gt; — El diseño prioriza bajo uso de RAM y CPU, algo crítico en dispositivos Android de gama media/baja. El cacheo lazy y thread-safe de solo &lt;code&gt;index.html&lt;/code&gt; es un detalle pequeño pero poderoso para SPAs con navegación frecuente.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No es "reinventar la rueda". Es &lt;strong&gt;traer la rueda correcta al vehículo correcto&lt;/strong&gt;, con mejoras específicas para el entorno móvil.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ventajas frente a soluciones modernas existentes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vs. React Native + WebView&lt;/strong&gt; (o &lt;code&gt;react-native-webview&lt;/code&gt;):&lt;br&gt;&lt;br&gt;
React Native obliga a un puente JS → Native, añade complejidad de ecosistema y tamaño de APK. Con VirtualHostManager cargas tu SPA React/Vue directamente en un WebView nativo puro. Menos capas = mejor rendimiento, menor tamaño y mantenimiento más simple si ya tienes el frontend web.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vs. Capacitor / Ionic / Cordova&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Estos frameworks son geniales para acceso a APIs nativas, pero introducen su propio runtime, plugins y bridge. Si tu app es principalmente una SPA web bien hecha, VirtualHostManager te da control total sin el overhead. Quieres cámara o notificaciones? Añades solo los permisos y llamadas nativas que necesites, sin arrastrar todo el framework.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vs. WebViewAssetLoader (oficial de AndroidX)&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Es buena, pero más básica. No maneja automáticamente el fallback SPA de forma tan elegante, ni domina el concepto de "virtual host" con dominio personalizado. Mi implementación es más cercana a la experiencia de WebView2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vs. servidores locales embebidos (NanoHTTPD, etc.)&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Mucho más pesado en memoria y batería. Mi solución es interceptación directa: cero sockets, cero hilos extras.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: una app más ligera, más rápida al arrancar, con mejor ahorro de batería y que se siente como una experiencia web premium dentro de un contenedor nativo.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Para quién es ideal?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Desarrolladores que tienen una &lt;strong&gt;SPA moderna&lt;/strong&gt; (React, Vue, Svelte, etc.) y quieren empaquetarla como app Android con mínima fricción.&lt;/li&gt;
&lt;li&gt;Equipos que buscan &lt;strong&gt;máximo rendimiento&lt;/strong&gt; y control fino sin adoptar un framework completo.&lt;/li&gt;
&lt;li&gt;Quienes valoran la simplicidad y la inspiración cross-platform (de .NET a Android).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esta clase es open-source en espíritu: la comparto porque creo que la comunidad Android merece herramientas más elegantes para servir SPAs locales, teniendo muy en cuenta que las aplicaciones web son cada vez más sofisticadas y de alta tecnología. &lt;/p&gt;

&lt;p&gt;¿Qué opinas?&lt;br&gt;&lt;br&gt;
¿Te ha pasado que luchas con WebView + SPA y terminas añadiendo capas innecesarias?&lt;br&gt;&lt;br&gt;
¿Crees que vale la pena priorizar esta simplicidad sobre frameworks más "todo en uno"?&lt;/p&gt;

&lt;p&gt;Me encantaría leer tus experiencias en comentarios. Si te sirve, ¡úsala, mejórala y compártela!&lt;/p&gt;

&lt;h1&gt;
  
  
  Android #WebView #React #typescript  #tailwindcss  #SPA  #MobileDevelopment #Architecture #DotNet #Kotlin #Java
&lt;/h1&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Virtual Host for Android</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Tue, 14 Apr 2026 04:37:16 +0000</pubDate>
      <link>https://forem.com/gfouz/virtual-host-for-android-17na</link>
      <guid>https://forem.com/gfouz/virtual-host-for-android-17na</guid>
      <description>&lt;p&gt;Implementando el Patrón UseSpa de ASP.NET Core en Android WebView: Una Alternativa Ligera a Cordova para SPAs Offline&lt;/p&gt;

&lt;p&gt;Cómo construí un enrutador SPA eficiente para apps React que se ejecutan completamente desde los assets de Android, sin servidor HTTP y con consumo mínimo de batería y de RAM.&lt;/p&gt;




&lt;p&gt;El Problema: El Enrutamiento SPA se Rompe en Android WebView&lt;/p&gt;

&lt;p&gt;Si alguna vez has intentado empaquetar una app React o Vue dentro de un WebView de Android usando file:///android_asset/index.html, te has topado con este muro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net::ERR_FILE_NOT_FOUND
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La página de inicio carga perfectamente. Pero cuando el usuario navega a /inventario y refresca la página (o recibe un enlace profundo), el WebView intenta buscar el archivo físico /inventario dentro de la carpeta assets. No existe. La app se rompe.&lt;/p&gt;

&lt;p&gt;Las soluciones tradicionales son pesadas:&lt;/p&gt;

&lt;p&gt;Solución Problema&lt;br&gt;
Cordova / Capacitor Levanta un servidor HTTP real en localhost. Funciona, pero consume batería y RAM.&lt;br&gt;
PWA con Service Workers Requiere HTTPS e internet para la primera carga. No es verdaderamente offline-first.&lt;/p&gt;

&lt;p&gt;Pero espera—Microsoft ya resolvió esto elegantemente en ASP.NET Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSpa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spa&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;spa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este middleware sirve archivos estáticos normalmente, pero hace fallback a index.html para cualquier ruta que no tenga extensión de archivo. React Router toma el control a partir de ahí.&lt;/p&gt;

&lt;p&gt;Entonces me pregunté: "¿Por qué Android no tiene algo así?"&lt;/p&gt;




&lt;p&gt;La Solución: VirtualHostManager&lt;/p&gt;

&lt;p&gt;Desarrollé una clase Java ligera que replica el comportamiento de UseSpa de ASP.NET completamente dentro de la capa de interceptación de peticiones del WebView. Sin servidor HTTP local. Sin Cordova. Android SDK puro o Java sin dependencias ni librerías externas.&lt;/p&gt;

&lt;p&gt;El Corazón de la idea&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Determina si una ruta solicitada es una ruta de React Router.
 * Lógica: Si no tiene extensión de archivo, es una ruta SPA.
 */&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isReactRouterRoute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;assetPath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assetPath&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;assetPath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lastDot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assetPath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastIndexOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lastDot&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Sin extensión = ruta SPA&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta simple validación es lo que permite que /inventario, /reportes o /configuracion no devuelvan 404, sino que entreguen index.html para que React Router maneje la navegación.&lt;/p&gt;

&lt;p&gt;Configuración del WebView&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;VirtualHostManager&lt;/span&gt; &lt;span class="n"&gt;vhm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VirtualHostManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app.local"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"www"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWebViewClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebViewClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WebResourceResponse&lt;/span&gt; &lt;span class="nf"&gt;shouldInterceptRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebView&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebResourceRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUrl&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vhm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shouldIntercept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vhm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serveStaticAsset&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shouldInterceptRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vhm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVirtualBaseUrl&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// https://app.local/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;El Código Completo (Fragmentos Clave)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Interceptación y Fallback al Index
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WebResourceResponse&lt;/span&gt; &lt;span class="nf"&gt;serveStaticAsset&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlToAssetPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fullAssetPath&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 🧠 Aquí está la innovación: detección de rutas SPA&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isReactRouterRoute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fullAssetPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indexAssetPath&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Fallback a index.html&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fullAssetPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assetSubfolder&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Servir desde assets con MIME types y headers de caché&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create404Response&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Cacheo Thread-Safe de index.html
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;indexHtmlCache&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Double-checked locking para evitar bloqueos innecesarios en lecturas concurrentes&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fullAssetPath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexAssetPath&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexHtmlCache&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;synchronized&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexHtmlCache&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;indexHtmlCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadAssetBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fullAssetPath&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;dataStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ByteArrayInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexHtmlCache&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Validación de Seguridad contra Path Traversal
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;validatePath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SecurityException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Path inválido: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Limpieza de URLs con Fragmentos y Query Params
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;urlToAssetPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;virtualHost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;virtualHost&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"?"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\?"&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Optimizaciones que Marcan la Diferencia&lt;/p&gt;

&lt;p&gt;Característica Implementación Beneficio&lt;br&gt;
Cacheo de index.html volatile + double-checked locking Acceso concurrente sin bloqueos&lt;br&gt;
Cero sobrecarga de RAM para otros assets InputStream directo, sin byte[] Memoria mínima en dispositivos viejos&lt;br&gt;
Headers de caché inmutables max-age=31536000 para assets con hash El WebView no re-descarga archivos&lt;br&gt;
Protección contra Path Traversal Bloqueo de ../ y rutas absolutas Seguridad en apps offline&lt;br&gt;
Soporte para subcarpetas assetSubfolder configurable Compatible con npm run build sin cambios&lt;/p&gt;




&lt;p&gt;Comparativa: VirtualHostManager vs. Soluciones Existentes&lt;/p&gt;

&lt;p&gt;Métrica Cordova/Capacitor WebView + file://  VirtualHostManager&lt;br&gt;
Soporte SPA Routing ✅ Sí ❌ Roto ✅ Sí&lt;br&gt;
Requiere Servidor HTTP ✅ Sí (localhost) ❌ No ❌ No&lt;br&gt;
Consumo de Batería Alto Bajo Mínimo&lt;br&gt;
Consumo de RAM ~40-60 MB ~10 MB ~8 MB&lt;br&gt;
Deep Linking ✅ Sí ❌ No ✅ Sí&lt;br&gt;
Hot Updates ❌ No ❌ No ✅ Sí (reemplazar assets)&lt;br&gt;
Tiempo de inicio Lento (espera al servidor) Instantáneo Instantáneo&lt;/p&gt;




&lt;p&gt;Caso de Uso Real: App de Inventarios en Cuba&lt;/p&gt;

&lt;p&gt;Esta solución está actualmente en producción en una app de control de inventarios para pequeños negocios en Cuba, donde:&lt;/p&gt;

&lt;p&gt;· El internet es caro e inestable.&lt;br&gt;
· Los dispositivos tienen RAM y batería limitadas.&lt;br&gt;
· El trabajo por turnos requiere traspaso de datos offline vía exportación  de archivos.&lt;/p&gt;

&lt;p&gt;La app funciona completamente offline, sincroniza mediante archivos exportados, y genera reportes PDF—todo desde un único WebView con persistencia de datos utilizando a IndexDB. &lt;/p&gt;




&lt;p&gt;Código Fuente Completo&lt;br&gt;
En fin quiero compartir este pensamiento:&lt;/p&gt;

&lt;p&gt;No necesitas Cordova para tener una SPA funcional en Android. A veces, una clase Java de 150 líneas es todo lo que necesitas para que React Router funcione como si estuviera en un servidor real.&lt;/p&gt;

&lt;p&gt;Microsoft lo hizo bien con UseSpa. Ahora Android también lo tiene, (VirtualHostManager) una clase de 150 líneas en Java. &lt;/p&gt;




&lt;p&gt;¿Preguntas? ¿Ideas? &lt;/p&gt;

&lt;p&gt;Déjamelas en los comentarios. Si estás construyendo algo similar para entornos offline-first en Latinoamérica, me encantaría saber de tu proyecto.&lt;/p&gt;




&lt;p&gt;Giovani Fouz&lt;br&gt;
Desarrollador de Software. No deberíamos aferrarnos a ciertas tecnologías donde radica nuestra zona de comodidad  sino explorar nuevas alternativas, pero no termino sin mencionar: ¡ Wow vaya éxito el de React o Typescript o Tailwindcss !  ¡ Que grandiosas herramientas!&lt;br&gt;
Cuba, 2026&lt;/p&gt;




</description>
      <category>android</category>
      <category>mobile</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Comprendiendo el uso del Asterisco (`*`) y Doble Asterisco (`**`) en Python</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sat, 09 Nov 2024 11:15:03 +0000</pubDate>
      <link>https://forem.com/gfouz/comprendiendo-el-uso-del-asterisco-y-doble-asterisco-en-python-2i4e</link>
      <guid>https://forem.com/gfouz/comprendiendo-el-uso-del-asterisco-y-doble-asterisco-en-python-2i4e</guid>
      <description>&lt;p&gt;Cuando trabajamos en Python, a menudo nos encontramos con situaciones en las que necesitamos que nuestras funciones sean flexibles y capaces de manejar diferentes tipos y cantidades de argumentos. Para facilitar esto, Python ofrece dos operadores muy útiles: el asterisco simple (&lt;code&gt;*&lt;/code&gt;) y el doble asterisco (&lt;code&gt;**&lt;/code&gt;). En este post, exploraremos en detalle su funcionamiento, así como su aplicación en funciones, clases y estructuras de datos como listas y diccionarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué son las estructuras de datos en Python?
&lt;/h2&gt;

&lt;p&gt;Antes de entrar en el uso del asterisco, es importante entender qué son las estructuras de datos. En Python, las estructuras de datos son formas de organizar y almacenar datos en el programa. Las más comunes son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Listas&lt;/strong&gt;: Colecciones ordenadas que permiten elementos duplicados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuplas&lt;/strong&gt;: Similares a las listas, pero son inmutables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diccionarios&lt;/strong&gt;: Colecciones no ordenadas de pares clave-valor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Estas estructuras son fundamentales para manejar datos en Python y son el contexto donde los operadores &lt;code&gt;*&lt;/code&gt; y &lt;code&gt;**&lt;/code&gt; se vuelven particularmente interesantes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usos del Asterisco Simple (&lt;code&gt;*&lt;/code&gt;)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Funciones con Argumentos Variables
&lt;/h3&gt;

&lt;p&gt;El asterisco simple se utiliza principalmente en funciones para permitir que acepten un número variable de argumentos posicionales. Esto significa que puedes pasarle cualquier cantidad de argumentos y todos serán tratados como una tupla dentro de la función.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Esto imprime: 1 2 3 4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Desempaquetado de Listas o Tuplas
También puedes usar el asterisco simple para desempaquetar elementos de una lista o tupla al pasar argumentos a una función.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Esto imprime: 1 2 3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este uso del asterisco puede ser muy conveniente cuando tienes una lista de elementos y quieres pasarlos como argumentos a otra función.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usos del Doble Asterisco (&lt;code&gt;**&lt;/code&gt;)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Funciones con Argumentos de Palabra Clave
El doble asterisco se utiliza para permitir que una función acepte un número variable de argumentos de palabra clave (nombre-valor). Dentro de la función, estos argumentos se almacenan en un diccionario.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Salida:
# name: Alice
# age: 30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Desempaquetado de Diccionarios
Al igual que el asterisco para listas y tuplas, puedes usar el doble asterisco para desempaquetar pares clave-valor desde un diccionario al llamar a una función.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Osea es lo mismo que: func(name="Alice", age=30)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usos Avanzados de * y ** en Clases
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# (1, 2)
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# {'a': 3, 'b': 4}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En Herencia&lt;br&gt;
Puedes utilizar *args y **kwargs para pasar argumentos al constructor de la clase base, lo que permite que la clase derivada extienda el comportamiento del constructor de la clase base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Derived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Llama al constructor de Base
&lt;/span&gt;        &lt;span class="c1"&gt;# Puedes hacer algo adicional con args y kwargs aquí
&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Derived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combinaciones de simple y doble asterisco:&lt;br&gt;
A veces, puede ser útil combinar tanto los argumentos posicionales como los de palabra clave en tus funciones para permitir un manejo completo de los datos de entrada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mixed_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;mixed_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#Salida:
&lt;/span&gt;
&lt;span class="c1"&gt;# 1 2
# (3, 4)
# {'x': 5, 'y': 6}
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.python.org/3/tutorial/controlflow.html#defining-functions" rel="noopener noreferrer"&gt;Documentación Oficial de Python sobre Funciones&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.py4e.com" rel="noopener noreferrer"&gt;Python para Todos - Curso Gratuito&lt;/a&gt;&lt;br&gt;
&lt;a href="https://realpython.com/python-unpacking/" rel="noopener noreferrer"&gt;Real Python - Unpacking in Python&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.w3schools.com/python/python_functions.asp" rel="noopener noreferrer"&gt;W3Schools - Python Functions (Defining Functions)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resumen:
&lt;/h2&gt;

&lt;p&gt;args es útil para recibir un número variable de argumentos posicionales, mientras que kwargs es útil para recibir un número variable de argumentos de palabra clave.&lt;br&gt;
Puedes usar simple y doble asterisco para desempaquetar listas/tuplas y diccionarios, respectivamente, al llamar funciones.&lt;br&gt;
Ambos operadores pueden mejorar la flexibilidad y reutilización de tu código, permitiendo funciones que manejan diferentes tipos de entradas de manera eficiente.&lt;br&gt;
Recursos Útiles&lt;/p&gt;

</description>
    </item>
    <item>
      <title>React query, hook personalizado.</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sat, 09 Nov 2024 08:02:56 +0000</pubDate>
      <link>https://forem.com/gfouz/react-query-hook-personalizado-3a5m</link>
      <guid>https://forem.com/gfouz/react-query-hook-personalizado-3a5m</guid>
      <description>&lt;p&gt;En aplicaciones de React, es común necesitar acceder a datos de una API externa. Tradicionalmente, muchos desarrolladores utilizan métodos nativos de javascript como fetch dentro de los componentes, lo que puede llevar a problemas de administración del estado y a un manejo complicado de efectos secundarios (side effects). Aquí es donde React Query entra en juego.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué es React Query?
&lt;/h3&gt;

&lt;p&gt;React Query es una biblioteca destinada a simplificar las operaciones de obtención y manejo de datos en React. Proporciona potentes funciones para:&lt;/p&gt;

&lt;p&gt;Efectuar solicitudes de datos de una API.&lt;br&gt;
Almacenar y sincronizar el estado de la UI con los datos.&lt;br&gt;
Realizar refetching automáticamente y gestionar cachés.&lt;br&gt;
Nuestro Hook Personalizado&lt;/p&gt;

&lt;p&gt;A continuación, veamos nuestro hook personalizado, useCustomQuery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GenericObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;QueryFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GenericObject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useCustomQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;queryFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QueryFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;GenericObject&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;GenericObject&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;queryFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Desglose del Hook
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GenericObject y QueryFunction:
&lt;/h3&gt;

&lt;p&gt;Definimos tipos genéricos que facilitan el trabajo con objetos y funciones que realizan fetching de datos. Esto asegura que nuestro código pueda adaptarse a diferentes estructuras de datos que puedan regresar las APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  useQuery:
&lt;/h3&gt;

&lt;p&gt;Usamos el hook useQuery de React Query, que maneja automáticamente el estado de nuestras solicitudes. Proporciona propiedades útiles para manejar la carga de datos, errores y refetching.        &lt;/p&gt;

&lt;p&gt;Esto mejora la calidad del código y reduce bugs.&lt;br&gt;
Interfaces Genéricas: Utilizando interfaces genéricas, podemos definir estructuras de datos que se adaptan a diferentes situaciones. Esto hace que el código sea más reutilizable y fácil de mantener.&lt;br&gt;
Mejor Documentación: El uso de TypeScript como una herramienta de documentación proporcionada por los tipos mejora la comprensión de cómo se deben usar las funciones y qué tipo de datos se esperan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enlaces Útiles
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;React Query&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentación oficial: &lt;a href="https://react-query.tanstack.com/" rel="noopener noreferrer"&gt;TanStack Query Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Guía de inicio rápido: &lt;a href="https://react-query.tanstack.com/overview" rel="noopener noreferrer"&gt;Getting Started with React Query&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentación oficial: &lt;a href="https://www.typescriptlang.org/docs/" rel="noopener noreferrer"&gt;TypeScript Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Guía de TypeScript para React: &lt;a href="https://react-typescript-cheatsheet.netlify.app/" rel="noopener noreferrer"&gt;Using TypeScript with React&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitHub de React Query&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repositorio oficial: &lt;a href="https://github.com/TanStack/query" rel="noopener noreferrer"&gt;TanStack Query (React Query) GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ejemplos y Tutoriales&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tutorial de RapidAPI sobre React Query: &lt;a href="https://rapidapi.com/blog/react-query/" rel="noopener noreferrer"&gt;Getting Started with React Query&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Artículo sobre los beneficios de utilizar React Query: &lt;a href="https://blog.logrocket.com/why-you-should-use-react-query-in-your-next-react-app/" rel="noopener noreferrer"&gt;Benefits of Using React Query&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusión
&lt;/h3&gt;

&lt;p&gt;Integrar un hook personalizado junto con React Query y TypeScript en tus aplicaciones React transforma la forma en que gestionas las solicitudes de datos. No sólo mejora la experiencia de desarrollo al reducir la carga de lógica de manejo de estado, sino que también enriquece la robustez y facilidad de mantenimiento de tu código.&lt;br&gt;
Espero que encuentren útil este enfoque y les animo a probarlo en sus propios proyectos. ¡Feliz codificación!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Errores Comunes de Pylint. Guía Práctica</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Mon, 27 May 2024 09:10:20 +0000</pubDate>
      <link>https://forem.com/gfouz/errores-comunes-de-pylint-guia-practica-2ko</link>
      <guid>https://forem.com/gfouz/errores-comunes-de-pylint-guia-practica-2ko</guid>
      <description>&lt;p&gt;Pylint es una herramienta de análisis estático de código que se utiliza para encontrar errores y mejorar la calidad del código Python. Sin embargo, enfrentarse a los errores que reporta puede ser desalentador, especialmente para los desarrolladores novatos. Aquí exploramos algunos de los errores más comunes que lanza Pylint, cómo resolverlos y cómo podemos beneficiarnos de estas correcciones para escribir un código más limpio y eficiente.&lt;/p&gt;

&lt;p&gt;1- C0103: Variable name "x" doesn't conform to snake_case naming style&lt;br&gt;
Error:&lt;br&gt;
Este error se produce cuando el nombre de una variable no sigue la convención de nombres en Python, que es usar snake_case.&lt;/p&gt;

&lt;p&gt;Ejemplo de código que lanza el error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John Doe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solución:&lt;br&gt;
Renombrar la variable para que siga la convención snake_case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;user_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John Doe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2- E1101: Instance of 'class' has no 'member' member&lt;br&gt;
Error:&lt;br&gt;
Pylint no puede encontrar el atributo especificado en la instancia de la clase. Esto suele ocurrir cuando se intenta acceder a un atributo que no existe.&lt;/p&gt;

&lt;p&gt;Ejemplo de código que lanza el error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solución:&lt;br&gt;
Asegurarse de que el atributo exista en la clase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;

&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3- R0913: Too many arguments (6/5)&lt;br&gt;
Error:&lt;br&gt;
Esta advertencia surge cuando una función o método tiene demasiados parámetros, lo cual puede hacer que el código sea difícil de mantener y leer.&lt;/p&gt;

&lt;p&gt;Ejemplo de código que lanza el error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solución:&lt;br&gt;
Reducir el número de parámetros, por ejemplo, utilizando un diccionario o una clase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;user_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;johndoe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12345&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;john@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Doe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4- W0611: Unused 'import'&lt;br&gt;
Error:&lt;br&gt;
Este error aparece cuando hay una importación en el código que no se utiliza, lo que puede añadir desorden innecesario.&lt;/p&gt;

&lt;p&gt;Ejemplo de código que lanza el error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solución:&lt;br&gt;
Eliminar las importaciones no utilizadas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5- C0301: Line too long (82/80)&lt;br&gt;
Error:&lt;br&gt;
Este error se produce cuando una línea de código supera la longitud máxima recomendada de 80 caracteres.&lt;/p&gt;

&lt;p&gt;Ejemplo de código que lanza el error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Welcome to the platform. We hope you have a great experience here!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solución:&lt;br&gt;
Dividir la línea larga en varias líneas más cortas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Welcome to the platform. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;We hope you have a great experience here!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conclusión:&lt;/p&gt;

&lt;p&gt;Abordar y resolver los errores que Pylint destaca puede parecer un desafío, pero es un paso crucial hacia la mejora continua y la profesionalización de nuestro código. Cada corrección que hacemos nos acerca a un código más limpio, más eficiente y más fácil de mantener. Acepta los mensajes de Pylint como oportunidades de aprendizaje y crecimiento profesional.&lt;/p&gt;

&lt;p&gt;Para más detalles, puedes consultar la documentación oficial de Pylint que proporciona una guía exhaustiva sobre cómo interpretar y resolver los mensajes de error.&lt;/p&gt;

&lt;p&gt;Mantén la motivación y recuerda: escribir un buen código no es solo una habilidad técnica, sino también un arte que se perfecciona con práctica y perseverancia. ¡Sigue adelante y convierte cada error en un aprendizaje!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>¿Morirá React como jQuery?</title>
      <dc:creator>Giovani Fouz</dc:creator>
      <pubDate>Sun, 26 May 2024 21:35:15 +0000</pubDate>
      <link>https://forem.com/gfouz/morira-react-como-jquery-13bl</link>
      <guid>https://forem.com/gfouz/morira-react-como-jquery-13bl</guid>
      <description>&lt;p&gt;En el dinámico mundo del desarrollo web, las tecnologías van y vienen. jQuery, una vez la herramienta preferida para la manipulación del DOM y la gestión de eventos, ha sido en gran medida eclipsada por la evolución del lenguaje JavaScript y la aparición de frameworks más modernos. Esto plantea una cuestión pertinente: ¿podría React, la popular biblioteca de JavaScript para construir interfaces de usuario, eventualmente sufrir el mismo destino? Analicemos esta pregunta en detalle.&lt;/p&gt;

&lt;h2&gt;
  
  
  La Historia de jQuery
&lt;/h2&gt;

&lt;p&gt;Para comprender si React podría seguir el mismo camino que jQuery, es esencial entender primero cómo jQuery alcanzó su apogeo y posterior declive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ascenso de jQuery:
&lt;/h2&gt;

&lt;p&gt;Simplificación del DOM: En sus inicios, la manipulación del DOM y la gestión de eventos eran tareas tediosas y llenas de inconsistencias entre navegadores. jQuery ofreció una API sencilla y uniforme que facilitó estas tareas.&lt;br&gt;
Compatibilidad Cross-Browser: jQuery resolvía muchos problemas de compatibilidad entre navegadores, una de las mayores dificultades para los desarrolladores web de la época.&lt;br&gt;
Extensibilidad y Plugins: jQuery ofrecía un sistema de plugins robusto, permitiendo a los desarrolladores añadir funcionalidades adicionales con facilidad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declive de jQuery:
&lt;/h2&gt;

&lt;p&gt;Evolución del JavaScript Nativo: Con la llegada de ECMAScript 5 y, posteriormente, ECMAScript 6 (ES6), muchas de las funcionalidades que jQuery proporcionaba se integraron directamente en el lenguaje.&lt;br&gt;
Nuevos Frameworks y Bibliotecas: Frameworks como Angular, React y Vue ofrecieron soluciones más completas y modernas para la creación de aplicaciones web, reduciendo la necesidad de jQuery.&lt;br&gt;
Mejora de los Navegadores: Los navegadores modernizaron sus API, reduciendo las inconsistencias y, por ende, la necesidad de una biblioteca como jQuery para gestionar estas diferencias.&lt;br&gt;
La Situación Actual de React&lt;br&gt;
React, lanzado por Facebook en 2013, se ha convertido en una de las bibliotecas más populares para la construcción de interfaces de usuario, gracias a varias características clave:&lt;/p&gt;

&lt;p&gt;Componentización: React introdujo el concepto de componentes reutilizables, facilitando la gestión de interfaces complejas.&lt;br&gt;
Virtual DOM: React utiliza un DOM virtual para minimizar las operaciones costosas en el DOM real, mejorando el rendimiento.&lt;/p&gt;

&lt;p&gt;Ecosistema y Herramientas: React cuenta con un ecosistema robusto, incluyendo herramientas como Redux para la gestión del estado, y Next.js para el renderizado del lado del servidor.&lt;br&gt;
Adopción y Soporte Empresarial: Grandes empresas han adoptado React, lo que garantiza su soporte continuo y evolución.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Podría React Volverse Obsoleto?
&lt;/h2&gt;

&lt;p&gt;Aunque React goza de una popularidad y uso extensivo en la actualidad, no es invulnerable a los cambios y avances tecnológicos. Aquí algunos factores que podrían influir en su futuro:&lt;/p&gt;

&lt;h2&gt;
  
  
  Emergencia de Nuevas Tecnologías:
&lt;/h2&gt;

&lt;p&gt;Tecnologías más recientes podrían surgir con paradigmas de desarrollo más eficientes. Por ejemplo, Svelte es un framework que compila el código a JavaScript puro, eliminando la necesidad de un virtual DOM.&lt;br&gt;
Evolución del Lenguaje y Estándares Web:&lt;/p&gt;

&lt;p&gt;Si el lenguaje JavaScript y los estándares web continúan evolucionando, podrían integrar de forma nativa muchas de las funcionalidades que actualmente proporcionan bibliotecas como React. Esto podría hacer que los desarrolladores prefieran soluciones nativas más ligeras.&lt;br&gt;
Competencia de Otros Frameworks:&lt;/p&gt;

&lt;p&gt;Frameworks competidores como Vue.js y Angular siguen evolucionando y ofreciendo características únicas que podrían atraer a los desarrolladores. También, frameworks como Solid.js y Qwik, que promueven un rendimiento y eficiencia superiores, podrían ganar terreno.&lt;br&gt;
Saturación del Ecosistema:&lt;/p&gt;

&lt;p&gt;La complejidad creciente del ecosistema de React, con la proliferación de librerías y herramientas adicionales, podría desanimar a los nuevos desarrolladores y conducirlos a buscar alternativas más simples.&lt;/p&gt;

&lt;p&gt;Conclusión&lt;/p&gt;

&lt;p&gt;En resumen, mientras que es difícil predecir con certeza el futuro de cualquier tecnología, la trayectoria de jQuery ofrece lecciones valiosas. React, con su fuerte adopción y apoyo comunitario, está bien posicionado para permanecer relevante en el futuro cercano. Sin embargo, la continua evolución del desarrollo web significa que siempre habrá nuevas tecnologías y paradigmas que podrían desplazar a las herramientas actuales. La clave para la longevidad de React residirá en su capacidad para adaptarse e innovar frente a estos cambios constantes.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
