Zum Inhalt springen
flutter 2026-02-18

WebView in App einbetten -- Warum es komplizierter ist, als du denkst

WebViews wirken einfach -- Website einbetten, fertig. In der Praxis stecken hinter einer Zeile Code 1.290+ Zeilen Infrastruktur. Ein echter Projektbericht.


WebView in App einbetten -- Warum es komplizierter ist, als du denkst

WebView in App einbetten — Warum es komplizierter ist, als du denkst

TL;DR: WebViews in Apps einzubetten wirkt einfach — bestehende Website nehmen und einbetten. In der Praxis steckt dahinter versteckte Komplexität: Authentifizierung synchronisieren, Navigation zwischen Web und Native managen, JavaScript-Bridges bauen. Anhand eines echten E-Commerce-Projekts mit Shopify-Infrastruktur zeigt dieser Artikel, wo die Probleme liegen und wann WebViews die richtige Wahl sind.

Die Ausgangsfrage

“Können wir nicht einfach unsere Website in die App einbetten?”

Das hört jeder Mobile-Entwickler irgendwann. Es klingt logisch: Ein funktionierender Online-Shop existiert, mit kuratierten Produktseiten, funktionierendem Checkout und integrierter Suche. Warum alles nativ nachbauen, wenn Einbetten einfacher wäre?

Ein WebView ist eine Browserkomponente innerhalb einer nativen App — im Grunde ein eingebetteter Browser ohne Adressleiste. Laut AppBrain (2025) nutzen über 60% der Android-Anwendungen irgendeine Form von WebView-Technologie.

Die Antwort: Technisch möglich, aber “einfach” ist es nicht.

Dieser Artikel zeigt an einem echten Projekt — eine Schmuck-E-Commerce-Marke mit Shopify-Backend — was hinter dieser einen Zeile Code steckt. Keine Theorie, sondern echte Code-Beispiele, echte Bugs und echte Workarounds.

Die App ist eine Flutter-Implementierung mit Hybrid-Strategie: Manche Screens sind nativ gebaut, andere nutzen WebViews. Die Probleme entstehen genau an den Grenzen zwischen diesen zwei Welten.

Das ist kein Argument gegen WebViews. Es ist ein Argument für informierte Planung.

Hybrid-Architektur: Wo nativ, wo WebView?

Nativ gebaut:

  • Authentifizierung (Login, Registrierung)
  • Onboarding
  • Startseite mit Produkt-Highlights
  • Kontoverwaltung
  • Einstellungen & Präferenzen

WebView:

  • Shop-Browsing (Kollektionen, Kategorien)
  • Produktdetails
  • Warenkorb
  • Checkout
  • Suche
  • Bestellhistorie

Drei Gründe für diesen Split:

  1. CMS-Flexibilität: Produktseiten und Kollektionen ändern sich ständig. Das Marketing-Team verwaltet sie über Shopify — nativer Nachbau würde bedeuten, jede CMS-Änderung manuell in der App nachzuziehen.

  2. PCI Compliance: Checkout nativ bauen erfordert eigene PCI-Compliance-Zertifizierung. Hosted Solutions wie Shopify Checkout übernehmen das.

  3. Kosten-Nutzen: Nicht jeder Screen rechtfertigt native Implementierung.

Die Shop-Seite sieht im Code täuschend einfach aus:

WebviewPageWidget(initialUrl: shopConfig.baseUrl);

Eine Zeile. Hinter dieser einen Zeile: ein 476-Zeilen-Container, fünf Plugins mit über 800 Zeilen Code, eine JavaScript-Bridge, Cookie-Management, plattformspezifische Navigations-Workarounds und eine State Machine für den initialen Seitenaufruf.

Komplexität #1: Auth-Synchronisation

Die erste große Herausforderung in Hybrid-Apps: Credentials leben in zwei separaten Welten.

Login passiert nativ. Die App kommuniziert direkt mit der Shopify Storefront API, speichert Tokens sicher über flutter_secure_storage, verwaltet den Auth-State über einen zentralen Controller.

Dann will der User seinen Warenkorb sehen. Der Warenkorb ist eine WebView. Die WebView weiß initial nichts vom Login-Status.

Tokens zu Cookies

Lösung: Ein TokenBag-Modell konvertiert native Tokens in WebView-kompatible Cookies.

class TokenBag {
  final String? cartId;
  final String? shopifyCustomerAccessToken;

  String? get cartToken => cartId?.split('/').last;

  Map<String, String> get cookies {
    return {
      if (cartToken != null) 'cart': cartToken!,
      if (shopifyCustomerAccessToken != null)
        'shopifyCustomerAccessToken': shopifyCustomerAccessToken!,
    };
  }
}

Klingt handhabbar. Aber das Setzen dieser Cookies ist komplex. Jede Auth-State-Änderung erfordert: bestehende Session-Cookies entfernen, neue Cookies setzen, zusätzliche App-spezifische Cookies setzen.

Die Nuklear-Option: WebView komplett zerstören

Manchmal reicht Cookie-Änderung nicht. Die gesamte WebView muss zerstört und neu aufgebaut werden:

final rebuildKey = ValueKey(
  (tokenBag.shopifyCustomerAccessToken ?? 'guest') +
      rebuildIndex.value.toString(),
);

InAppWebView(
  key: rebuildKey,
  // ...
);

Flutter erkennt den neuen Key, verwirft die alte WebView komplett und erstellt eine neue. Inklusive neuem Seitenaufruf, neuem JavaScript-Kontext, neuen Cookie-Sessions. Kein sanfter Übergang — ein harter Reset.

Web-initiierter nativer Login

Es gibt noch die andere Richtung: WebView kann nativen Login auslösen. Wenn ein User im Web-Checkout auf “Anmelden” klickt, öffnet eine JavaScript-Bridge-Funktion ein natives Login-Bottom-Sheet. Nach nativem Login aktualisiert die App den Auth-State, refresht Cookies, und die WebView sollte das widerspiegeln.

Ein Git-Branch heißt buchstäblich fix/SHOP-74-login-in-cart. Nicht Login allgemein — nur Login aus dem Warenkorb heraus. So spezifisch sind die Edge Cases.

Komplexität #2: Navigation

Die shouldOverrideUrlLoading-Methode im WebView-Container umfasst 100 Zeilen plattformspezifische Logik. Jede Zeile existiert aus einem bestimmten Grund.

State Machine für initiale Requests

enum InitialRequestState {
  none,
  loading,
  loaded,
  cancelled,
}

Warum? Weil shouldOverrideUrlLoading jeden URL-Request abfängt — auch den initialen, der die Startseite lädt. Ohne diese State Machine würde die App ihren eigenen initialen Seitenaufruf blockieren.

iOS vs. Android: Fundamental unterschiedliche Event-Modelle

iOS sendet NavigationType.BACK_FORWARD für Zurück-Navigation. Android? Sendet kein shouldOverrideUrlLoading während goBack(). Android markiert Redirects explizit über ein isRedirect-Flag. iOS hat dieses Flag nicht.

Jede Bedingung im Code existiert, weil ein bestimmter Bug auf einer bestimmten Plattform aufgetaucht ist. Das ist kein Over-Engineering — das ist Schadensbegrenzung.

Der restartWebView()-Workaround

// TODO(khalit): this feels like a hack. calling restartWebView is needed
// because it prevents shouldOverrideUrl from being called, which would
// cause unexpected behavior.
restartWebView();
await controller.loadUrl(
  urlRequest: URLRequest(url: WebUri(fullUrl)),
);

Und Zurück-Navigation mit künstlichem Delay:

Future.delayed(
  Durations.extralong1, // 700ms
  () async {
    await controller.reload();
  },
);

700 Millisekunden warten, weil sofortiger WebView-Reload die vorherige statt die neue Seite laden würde. Die Art Fix, die in Tutorials nie vorkommt.

Komplexität #3: Fünf Plugins, zwei Welten, ein window.mobileApp

Die eleganteste — und gleichzeitig anspruchsvollste — Lösung im Projekt ist die JavaScript-Bridge. Sie verbindet die native App mit der Website über ein Plugin-System.

Die fünf Plugins

PluginZeilenAufgabe
ConsentPlugin298Consent-Daten an WebView übertragen
TrackingPlugin268Web-Analytics zu Firebase Analytics übersetzen
NavigatePlugin127Webseitige Navigation handhaben
LoginPlugin68Web löst nativen Login-Flow aus
UpdateCartPlugin55Web informiert App über Warenkorb-Änderungen
Gesamt~816JavaScript-Bridge-Infrastruktur

816 Zeilen Plugin-Code. Plus der 476-Zeilen-Container. Für “einfach einbetten.”

Die Consent-Komplexität: Drei Ansätze gleichzeitig

Das ConsentPlugin nutzt drei gleichzeitige Ansätze, um sicherzustellen, dass die Website den Consent-State erkennt:

Ansatz 1: URL-Parameter — Consent-Daten direkt in die URL injizieren.

Ansatz 2: JavaScript-Injection bei DOCUMENT_START — URL-Parameter während des Seitenladens parsen und global setzen.

Ansatz 3: evaluateJavascript nach dem Laden — Consent-Daten nochmal per JS-Ausführung senden.

Drei Ansätze für eine Information. Warum? Weil keiner allein zuverlässig genug ist:

  • URL-Parameter können bei Client-Side-Redirects verschwinden
  • JavaScript-Injection kommt für bestimmte Shopify-Scripts zu früh
  • evaluateJavascript kommt für initiales Rendering zu spät

Also alle drei zusammen. Vollständige Redundanz.

Komplexität #4: Details, die den Schlaf rauben

1. User-Agent-Spoofing

Shopify gibt 403-Fehler zurück, wenn Standard-WebView-User-Agents erkannt werden. Die Lösung: Hartcodierte Browser-User-Agents pro Plattform.

userAgent: Platform.isIOS
    ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) '
      'AppleWebKit/605.1.15 ...'
    : 'Mozilla/5.0 (Linux; Android 13; Pixel 7) '
      'AppleWebKit/537.36 ...',

Ja, hartcodierte Strings. Ja, sie können veralten. Ja, manuelle Updates sind nötig.

2. Custom WebView SDK Fork

Die App nutzt einen Custom Fork von flutter_inappwebview. Warum? Die Standardversion hat keinen Android Payment Request Support — nötig für Google Pay im Checkout.

Das bedeutet: Eigenen Fork pflegen, Upstream-Änderungen beobachten, Kompatibilität bei jedem Flutter-Update prüfen. Für ein einzelnes fehlendes Feature.

3. Loading State: Unsichtbar rendern, dann anzeigen

// Schritt 1: Unsichtbar rendern
Opacity(opacity: 0, child: webView),

// Schritt 2: Höhe messen (nach dem Laden)
final height = await controller.evaluateJavascript(
  source: 'Math.max(document.body.scrollHeight, '
          'document.documentElement.scrollHeight)',
);
contentHeight.value = height.toDouble();

// Schritt 3: Mit gemessener Höhe anzeigen
SizedBox(height: contentHeight.value, child: webView);

4. Der verlassene Native-Ansatz

Tief in der Codebasis liegt auskommentierter Code:

//   ref
//       .read(shopifyCartControllerProvider.notifier)
//       .addProductToCart(
//         merchandiseId: selectedVariant.id,
//         product: product,
//         variant: selectedVariant.title,
//       );

Ein Versuch, Warenkorb-Funktionalität nativ zu implementieren. Versucht und aufgegeben — weil Synchronisation zwischen nativem Cart-State und Web-Cart-State zu fragil war.

Wann WebViews die richtige Wahl sind

Nach all dieser Komplexität wäre es unfair, WebViews pauschal zu verdammen.

1. Produktseiten & Kollektionen

CMS-verwalteter Content, der sich wöchentlich ändert? Definitiv WebView. Das Marketing-Team pflegt über Shopify, und die App zeigt es sofort — ohne App-Updates.

2. Checkout

PCI Compliance, Payment-Provider-Integration, Shop Pay, Apple Pay, Google Pay, Klarna, PayPal — alles nativ zu implementieren ist ein eigenes Projekt. Der gehostete Shopify-Checkout erledigt alles.

3. Suche

Facettierte Filterung, Autocomplete, Suchvorschläge — alles Shopify-Stärken mit geringem Nutzen durch nativen Nachbau.

4. Legal Pages

AGB, Datenschutz, Impressum — im externen Browser öffnen. Kein WebView, kein Custom Chrome.

Praktische Entscheidungshilfe

KriteriumEher nativEher WebView
Auth/LoginJaNein
Payment/CheckoutNur wenn PCI-konformJa (Hosted Solution)
CMS-ContentBei seltenen UpdatesBei häufigen Updates
NavigationskritischJaVorsicht
PerformancekritischJaNein
Offline-FähigkeitJaNein

Fazit

“Können wir nicht einfach die Website einbetten?”

Ja, aber.

WebViews sind ein Werkzeug, kein Shortcut. Sie sind angebracht — für CMS-Content, gehostete Checkouts, überall, wo Web-Infrastruktur besser ist als das, was Native in verfügbarer Zeit liefern könnte.

Aber sie bringen eigene Komplexität mit:

  • Credentials müssen zwischen separaten Systemen synchronisiert werden
  • Navigation braucht plattformspezifische Anpassungen für iOS und Android
  • JavaScript-Bridges müssen gebaut, getestet und gepflegt werden
  • Edge Cases (User Agents, SSL, Loading States) fressen Zeit

In Zahlen: Dieses Projekt brauchte 476 Zeilen für den WebView-Container, 816 Zeilen für Plugin-Code und unzählige Bugfix-Commits — für etwas, das mit “einfach einbetten” anfing.

Die richtige Frage ist nicht “einbetten oder nativ bauen?” sondern: “Welche Screens nativ, welche WebView, und wie kommunizieren sie?”

Wer das früh im Projekt beantwortet — und die versteckte Komplexität kennt — trifft bessere Architektur-Entscheidungen und vermeidet Budget-Überraschungen.

Häufige Fragen

Was ist ein WebView in mobilen Apps?

Ein WebView ist eine eingebettete Browserkomponente in nativen Apps. Es rendert Web-Inhalte direkt — ohne separaten Browser. User sehen keine Adressleiste und merken idealerweise nicht, dass sie Websites anschauen.

Sind WebView-Apps günstiger als native Apps?

Anfangs oft ja — native Reimplementierung bestehender Websites spart Geld. Langfristig kann die versteckte Komplexität (Auth-Synchronisation, Navigations-Workarounds, JavaScript-Bridges) den Kostenvorteil auffressen. Dieses Projekt hat 1.290+ Zeilen nur für WebView-Infrastruktur.

Akzeptiert Apple WebView-Apps im App Store?

Apple lehnt ausschließlich WebView-Apps ab (Guideline 4.2 — Minimum Functionality). Hybrid-Apps mit nativen Features plus WebViews werden akzeptiert — sofern sie Vorteile gegenüber mobilen Websites bieten.

Wann sollte man nativ bauen statt WebView?

Faustregel: Nativ für (a) Auth, (b) Offline-Anforderungen, (c) performancekritische Funktionalität, (d) komplexe native Gesten und Animationen. WebView für CMS-verwalteten Content, gehostete Checkouts und häufig aktualisierte Screens ohne native Performance-Anforderungen.

KH
Khalit Hartmann Freelance Mobile & Full-Stack Developer