HomeDocumentationiOS Under The Surface
iOS Under The Surface
13

Networking: From Dart to the Wire

iOS Networking Stack for Flutter Developers

March 30, 2026

When your Flutter app calls http.get('https://api.example.com/data'), the request passes through more layers on iOS than most developers expect. Dart's HTTP client, the Dart VM's socket implementation, Apple's networking frameworks, the kernel's TCP/IP stack, the Wi-Fi or cellular driver — each layer adds behaviour, restrictions, and potential failure modes.

On Android, the networking path is more direct: Dart → dart:io → POSIX sockets → Linux kernel TCP/IP stack → hardware. iOS's path has additional system-level intermediaries that enforce policies, optimise connections, and manage resources in ways your code doesn't control and might not expect.

The request path

Let's trace a concrete HTTPS request from Dart through every layer:

javascript
Dart: http.get('https://api.example.com/data')
  │
  ▼
dart:io HttpClient
  │ Creates a socket connection
  ▼
Dart VM native socket implementation (C++)
  │ Uses POSIX socket API
  ▼
BSD socket layer (XNU kernel)
  │ socket(), connect(), write(), read()
  ▼
XNU TCP/IP stack
  │ TCP three-way handshake
  │ IP routing
  ▼
Network interface driver
  │ Wi-Fi (en0) or Cellular (pdp_ip0)
  ▼
Hardware radio → network → server

For HTTPS, the TLS handshake happens in user space (BoringSSL, compiled into the Dart VM), using syscalls to read/write the underlying TCP socket. The kernel handles TCP but doesn't participate in TLS — the encryption/decryption happens in your process.

This is the path for Dart's built-in HTTP client. Some Flutter plugins use iOS's URLSession instead, which takes a different path.

URLSession: the system networking stack

When a Flutter plugin uses iOS's native networking (through NSURLSession), the path changes:

javascript
Plugin Swift code: URLSession.shared.dataTask(with: url)
  │
  ▼
URLSession (Foundation framework, in-process)
  │ Manages connection pooling, caching, cookies
  ▼
CFNetwork (Core Foundation networking)
  │ HTTP/2 multiplexing, connection coalescing
  │ TLS via Apple's SecureTransport / Network.framework
  ▼
Network.framework (user-space networking stack)
  │ TCP, UDP, QUIC, TLS
  │ Interfaces with kernel via nw_* APIs
  ▼
XNU kernel TCP/IP stack
  │
  ▼
Hardware

URLSession provides features that Dart's HTTP client doesn't:

HTTP/2 and HTTP/3 by default. URLSession automatically negotiates HTTP/2 with servers that support it, multiplexing multiple requests over a single TCP connection. On iOS 15+, it supports HTTP/3 (QUIC) for reduced connection latency. Dart's http package uses HTTP/1.1 by default.

Connection coalescing. If your app makes requests to api.example.com and images.example.com, and both resolve to the same IP address with a valid wildcard certificate, URLSession reuses the same TCP connection for both. Dart's client opens separate connections.

Background transfers. URLSession supports background download/upload sessions that continue even when your app is suspended (Post 3). The system daemon nsurlsessiond manages the transfer. When the transfer completes, your app is briefly woken to process the result.

System-level proxy and VPN integration. URLSession respects the device's proxy configuration and VPN routing automatically. Dart's socket-level networking also goes through the VPN (because VPN is implemented at the network interface level), but it doesn't respect HTTP-level proxy settings by default.

Some Flutter HTTP packages (dio with native adapters, cronet_http, or the cupertino_http package) use URLSession under the surface, gaining these benefits. The default http package uses Dart's socket implementation.

App Transport Security (ATS)

ATS is iOS's policy for network connections. It enforces HTTPS by default and requires modern TLS configuration:

  • TLS 1.2 or later (TLS 1.0 and 1.1 are blocked)
  • Forward secrecy (ECDHE key exchange)
  • Strong certificates (SHA-256 or better, RSA 2048-bit or better)
  • No plaintext HTTP (unless explicitly excepted)

ATS is enforced at the system level for connections made through URLSession and the related Apple networking APIs. For Dart's socket-level connections (which bypass URLSession), ATS enforcement depends on the iOS version and the specific API path — but Apple's direction is clear: all networking should meet ATS standards.

If your app needs to connect to a server that doesn't meet ATS requirements (an internal API using self-signed certificates, a legacy server on TLS 1.0), you add exceptions in Info.plist:

xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>legacy-api.internal.company.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
        </dict>
    </dict>
</dict>

Apple reviews ATS exceptions during App Store review. Blanket exceptions (NSAllowsArbitraryLoads = true) require justification. Apple has been progressively tightening this — the direction is toward mandatory HTTPS with no exceptions.

For Flutter developers, ATS matters when:

  • You're connecting to a development server over HTTP (common during development — use NSAllowsLocalNetworking for local connections)
  • A plugin's native networking code hits ATS restrictions that Dart's socket-level code didn't
  • App Store review rejects your app for ATS exceptions you forgot to justify

Wi-Fi Assist and network transitions

iOS has Wi-Fi Assist — a feature that automatically switches from a weak Wi-Fi connection to cellular data when Wi-Fi quality degrades. This switch can happen mid-request.

For URLSession-based connections, the system handles this transparently — the connection migrates to the new interface. For Dart's socket-level connections, a network interface change means the TCP connection's source IP address changes. The existing TCP connection dies (the server sees a connection reset), and the app must reconnect.

This is a common source of intermittent network errors in Flutter apps on iOS: the user walks from a room with good Wi-Fi to a room with weak Wi-Fi. Wi-Fi Assist switches to cellular. Dart's HTTP client's in-flight request fails with a socket error. The retry succeeds on cellular.

The mitigation: robust retry logic in your HTTP client. dio's interceptor pattern makes this clean:

dart
dio.interceptors.add(RetryInterceptor(
  dio: dio,
  retries: 3,
  retryDelays: [
    Duration(seconds: 1),
    Duration(seconds: 2),
    Duration(seconds: 4),
  ],
));

For long-lived connections (WebSockets, server-sent events), you need reconnection logic that detects the connection drop and re-establishes it. The connectivity_plus plugin can notify you of network interface changes, giving you a signal to proactively reconnect.

Cellular data restrictions

iOS allows users to disable cellular data on a per-app basis (Settings → Cellular → scroll to your app). If the user has disabled cellular data for your app and Wi-Fi is unavailable, all network requests fail.

The error isn't a clear "cellular data disabled" — it's a generic connection failure. Your app can't distinguish between "no network available" and "network available but your app isn't allowed to use it." This is a deliberate privacy decision by Apple — the system doesn't tell apps why they can't connect, only that they can't.

For Flutter apps, this manifests as network errors that the user swears shouldn't happen ("I have full signal!"). The fix is on the user's side (re-enable cellular data for your app), but your app should handle the failure gracefully — show a "No connection" state with a retry button, not a crash.

VPN and network extensions

When a VPN is active, all network traffic (including Dart's socket-level connections) is routed through the VPN tunnel. The VPN is implemented as a network interface at the OS level — your app's connections go through the kernel's routing table, which directs them to the VPN interface.

This is transparent to your Flutter code — http.get() works the same whether or not a VPN is active. But the VPN adds latency (each packet makes an extra hop through the VPN tunnel and encryption) and can introduce connectivity issues (VPN server overloaded, split tunnelling misconfiguration, DNS resolution through the VPN).

iOS also supports content filter network extensions — apps that inspect and filter network traffic. Enterprise MDM (Mobile Device Management) deployments often install content filters that can block specific domains or protocols. Your app's network requests might fail because an MDM content filter is blocking them, with no indication to your app of why.

DNS resolution

DNS on iOS goes through the system resolver, which supports:

  • DNS over HTTPS (DoH) and DNS over TLS (DoT) — encrypted DNS queries, configurable by the user or MDM profile
  • Multicast DNS (mDNS/Bonjour) — for local network service discovery
  • DNS caching — the system maintains a DNS cache that's shared across all apps

For Dart's socket-level networking, DNS resolution uses the getaddrinfo() POSIX function, which goes through the system resolver. This means your Dart HTTP requests benefit from the system's DNS cache and encrypted DNS configuration automatically.

One iOS-specific behaviour: when the device is on a captive Wi-Fi network (hotel, airport), iOS detects the captive portal and presents a login sheet. Until the user completes the captive portal login, DNS resolution and HTTP requests may fail or be redirected to the portal page. Your app receives unexpected responses (HTML login pages instead of JSON API responses) until the portal is cleared.

Network diagnostics

Charles Proxy / Proxyman. These HTTP debugging proxies can intercept your Flutter app's HTTPS traffic. On iOS, you install a proxy's CA certificate on the device and configure the proxy in Wi-Fi settings. Dart's socket-level connections respect the system proxy when using HttpClient's findProxy configuration. Some Flutter HTTP packages handle this automatically; others need explicit proxy configuration.

Instruments — Network. The Network instrument shows all network connections, their timing, data transfer, and protocol negotiation. This shows connections from both Dart's socket layer and native URLSession-based plugins.

`nettop` and `networksetup`. Command-line tools for monitoring network activity (available on macOS; limited equivalents on iOS via Instruments).

Console.app. System logs from cfnetwork, nsurlsessiond, and the network stack show connection failures, ATS violations, and DNS resolution issues. Filter by your app's process name.

The network stack and Flutter

For most Flutter apps, the networking details are handled by the Dart HTTP client and are invisible. But understanding the iOS network stack matters for:

Debugging intermittent failures. Wi-Fi Assist transitions, captive portals, cellular data restrictions, VPN issues — these are iOS-specific failure modes that don't exist in the same form on Android.

Performance. URLSession's HTTP/2 multiplexing and connection coalescing can reduce latency compared to Dart's HTTP/1.1 client. For apps making many concurrent API calls, using a URLSession-backed HTTP package can measurably improve performance.

App Store compliance. ATS requirements, privacy manifests (declaring which domains your app connects to), and Apple's guidelines about background networking all affect whether your app passes review.

Enterprise environments. MDM profiles, corporate VPNs, content filters, and proxy servers add layers between your app and the network that can cause failures invisible in development but common in production.

The final post in this series covers the build pipeline — from flutter build ipa on your machine to an app on the App Store.

This is Post 9 of the iOS Under the Surface series. Previous: The Sandbox, Code Signing, and the Secure Enclave. Next: The Build: From Source to the App Store.

Related Topics

ios networking flutterURLSession flutterapp transport securityios network stackflutter http iosios vpn flutterios wifi assist flutter

Ready to build your app?

Flutter apps built on Clean Architecture — documented, tested, and yours to own. See which plan fits your project.