# Network

> expo-network wrapped for every SymbioteNative adapter — live network state, IP address, and airplane-mode detection.

`@symbiote-native/network` wraps [`expo-network`](https://docs.expo.dev/versions/latest/sdk/network/)
so every SymbioteNative adapter can read the device's network connection state. Like
[battery](/docs/packages/battery/), it's built on `expo-modules-core` and mixes stateless one-shot
calls with exactly one live subscription: `useNetworkState`/`NetworkStateService.connect()` seed from
a one-shot `getNetworkStateAsync()` call and then subscribe to `addNetworkStateListener` for live
updates, the same seed-then-subscribe shape as battery's own hooks — everything else
(`getIpAddressAsync`, `isAirplaneModeEnabledAsync`) is a plain stateless function, same as
[local-auth](/docs/packages/local-auth/).

| OS platform | Support |
| ----------- | ------- |
| iOS         | ✅ live |
| Android     | ✅ live |

| Framework adapter | Support |
| ----------------- | ------- |
| React             | ✅ live |
| Vue               | ✅ live |
| Angular           | ✅ live |
| Svelte            | ✅ live |
| Solid             | ✅ live |

<Aside type="note">
  `NetworkStateType.BLUETOOTH`, `WIMAX`, `VPN`, and `OTHER` are Android-only —
  iOS never reports them. `isInternetReachable` always matches `isConnected` on
  iOS; on Android it additionally checks internet capability, confirmed internet
  access, and (for VPN connections) non-zero downstream bandwidth, so it can be
  `false` while `isConnected` is `true`.
</Aside>

## Installation

```sh
npm install @symbiote-native/network
```

Scaffolding or extending a SymbioteNative app? `npx @symbiote-native/cli new --network` (or
`add --network` in an existing app) installs and wires this for you — see
[`@symbiote-native/cli`](https://github.com/OneEyed1366/symbiote-native/tree/master/packages/cli).

`expo-network` and `expo-modules-core` come along as regular dependencies, pinned to exact
versions — never install either yourself, and never add the `expo` meta-package to your project
(it bundles its own Metro/Babel pipeline, which conflicts with this project's own).

<Aside type="danger" title="Native setup is required before first use">
  `expo-network`'s native code is discovered by `expo-modules-autolinking`, a
  different mechanism from the `react-native.config.cjs`/podspec autolinking
  every other SymbioteNative wrapper uses — and it isn't the standard Expo setup
  flow either, since this project never installs the `expo` meta-package. Follow
  [How to: wire up an Expo native
  module](/docs/howtos/expo-native-module-setup/) once per app first; it covers
  this package and every other `expo-modules-core` package with zero further
  native changes.
</Aside>

## Usage

### Live network state

<Tabs syncKey="framework">
  <TabItem label="React">
    ```tsx
    import { useNetworkState } from '@symbiote-native/network/react';

    export default function ConnectionStatus() {
      const networkState = useNetworkState(); // NetworkState, {} until the first reading arrives

      return <text>{networkState.isConnected ? `Connected via ${networkState.type}` : 'Offline'}</text>;
    }
    ```

  </TabItem>
  <TabItem label="Vue">
    ```vue
    <script setup lang="ts">
    import { useNetworkState } from '@symbiote-native/network/vue';

    const networkState = useNetworkState(); // Ref<NetworkState>, {} until the first reading arrives
    </script>

    <template>
      <text>{{ networkState.isConnected ? `Connected via ${networkState.type}` : 'Offline' }}</text>
    </template>
    ```

  </TabItem>
  <TabItem label="Angular">
    ```ts
    import { Component, inject } from '@angular/core';
    import { SYMBIOTE_ELEMENTS } from '@symbiote-native/angular';
    import { NetworkStateService } from '@symbiote-native/network/angular';

    @Component({
      standalone: true,
      imports: [SYMBIOTE_ELEMENTS],
      template: `<text>{{ networkState().isConnected ? 'Connected via ' + networkState().type : 'Offline' }}</text>`,
    })
    export class ConnectionStatus {
      readonly networkState = inject(NetworkStateService).connect();
    }
    ```

  </TabItem>
  <TabItem label="Svelte">
    ```svelte
    <script lang="ts">
      import { useNetworkState } from '@symbiote-native/network/svelte';

      const networkState = useNetworkState(); // { readonly current: NetworkState }, {} until the first reading arrives
    </script>

    <text>{networkState.current.isConnected ? `Connected via ${networkState.current.type}` : 'Offline'}</text>
    ```

  </TabItem>
  <TabItem label="Solid">
    ```tsx
    import { createNetworkState } from '@symbiote-native/network/solid';

    export default function ConnectionStatus() {
      const networkState = createNetworkState(); // Accessor<NetworkState>, {} until the first reading arrives

      return <text>{networkState().isConnected ? `Connected via ${networkState().type}` : 'Offline'}</text>;
    }
    ```

  </TabItem>
</Tabs>

### One-shot functions

The stateless functions are already framework-agnostic — import them straight from the package
root, on any adapter:

```ts
import {
  getIpAddressAsync,
  isAirplaneModeEnabledAsync,
} from '@symbiote-native/network';

const ipAddress = await getIpAddressAsync(); // "0.0.0.0" if it could not be retrieved
const airplaneMode = await isAirplaneModeEnabledAsync();
```

## API

### Functions

| Signature                                              | Description                                                                                                                                                                                                                    |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `getNetworkStateAsync(): Promise<NetworkState>`        | Gets the device's current network connection state — `{ type, isConnected, isInternetReachable }`                                                                                                                              |
| `getIpAddressAsync(): Promise<string>`                 | Gets the device's current IPv4 address. Resolves `"0.0.0.0"` if it could not be retrieved                                                                                                                                      |
| `isAirplaneModeEnabledAsync(): Promise<boolean>`       | Tells if the device is in airplane mode                                                                                                                                                                                        |
| `addNetworkStateListener(listener): EventSubscription` | Subscribes to network-state-change events (connection type, connected, internet reachable); call `.remove()` on the returned subscription to unsubscribe. The primitive `useNetworkState`/`NetworkStateService.connect()` wrap |

### `useNetworkState()` / `NetworkStateService.connect()`

| React (`/react`)  | Vue (`/vue`)      | Angular (`/angular`)            | Svelte (`/svelte`) | Solid (`/solid`)     | Signature | Returns                                                                                                                                                                                                                  |
| ----------------- | ----------------- | ------------------------------- | ------------------ | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `useNetworkState` | `useNetworkState` | `NetworkStateService.connect()` | `useNetworkState`  | `createNetworkState` | `()`      | Live `NetworkState` — plain value (React), `Ref<NetworkState>` (Vue), `Signal<NetworkState>` (Angular), `{ readonly current: NetworkState }` (Svelte, read as `.current`), `Accessor<NetworkState>` (Solid), seeded `{}` |

### `NetworkState` / `NetworkStateEvent`

| Field                 | Type                            | Description                                                                                                                                    |
| --------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                | `NetworkStateType \| undefined` | The current network connection type                                                                                                            |
| `isConnected`         | `boolean \| undefined`          | Whether there is an active network connection — does not mean internet is reachable. `false` when `type` is `NONE`/`UNKNOWN`, `true` otherwise |
| `isInternetReachable` | `boolean \| undefined`          | Whether the internet is reachable over the current connection — see the caveat above for the Android-vs-iOS check difference                   |

`NetworkStateEvent` is a plain alias of `NetworkState`, passed as the argument to
`addNetworkStateListener`'s listener.

### `NetworkStateType`

| Member      | Value         | Description                                                |
| ----------- | ------------- | ---------------------------------------------------------- |
| `NONE`      | `'NONE'`      | No active network connection detected                      |
| `UNKNOWN`   | `'UNKNOWN'`   | The connection type could not be determined                |
| `CELLULAR`  | `'CELLULAR'`  | Active connection over mobile data                         |
| `WIFI`      | `'WIFI'`      | Active connection over Wi-Fi                               |
| `BLUETOOTH` | `'BLUETOOTH'` | Active connection over Bluetooth. `@platform android`      |
| `ETHERNET`  | `'ETHERNET'`  | Active connection over Ethernet                            |
| `WIMAX`     | `'WIMAX'`     | Active connection over WiMAX. `@platform android`          |
| `VPN`       | `'VPN'`       | Active connection over VPN. `@platform android`            |
| `OTHER`     | `'OTHER'`     | Active connection over any other type. `@platform android` |

## Notes

- **`isAirplaneModeEnabledAsync` throws on iOS rather than resolving `false`.** iOS's native
  module defines only `getIpAddressAsync` and `getNetworkStateAsync`, so the presence check in the
  core raises an `UnavailabilityError` before anything is called. Branch on
  `Platform.OS === 'android'` at a cross-platform call site.
- **`getIpAddressAsync` returns a Wi-Fi address on both platforms.** iOS walks the interface list
  and keeps only interfaces named `en*`; Android reads `WifiManager`'s connection info. On a
  cellular-only connection both fall through to `"0.0.0.0"` — that value means "no Wi-Fi address",
  not "the call failed".
- **A one-shot `getNetworkStateAsync()` on iOS can block for up to five seconds and then look
  offline.** The native module starts a throwaway `NWPathMonitor` for the call and waits on a
  semaphore; if no path update arrives in time it returns the same
  `{ type: 'NONE', isConnected: false, isInternetReachable: false }` a genuinely disconnected
  device returns. The event path (`addNetworkStateListener`, and so `useNetworkState`) does not pay
  this cost — it reads the module's own long-lived monitor.
- **Android state events are debounced by 250 ms.** `ConnectivityManager` callbacks collapse onto
  a single delayed emission, so a burst of transitions arrives as one event and the settled state
  lands a quarter-second late. Disconnects are the exception — they are emitted immediately, to
  avoid re-reading a just-lost network as still connected.

## Common questions

- **Connected to Wi-Fi but the internet is down (hotel login page).** `isConnected` only says there
  is a network. Use `isInternetReachable` to confirm real internet access.
- **`ERR_NETWORK_NO_ACCESS_NETWORKINFO` on Android.** Reported when the network info cannot be read;
  catch the rejection and treat it as offline.
- **The hook reports offline forever after the app was frozen in the background (Android).** The
  event stream can arrive out of order. Call `getNetworkStateAsync` on foreground to correct it.
- **Always `{ isConnected: false, type: NONE }` after an SDK update.** Reported upstream; check
  the Android network-state permission is present in the final manifest.

Sources: [expo/expo#14527](https://github.com/expo/expo/issues/14527),
[expo/expo#47846](https://github.com/expo/expo/issues/47846),
[expo/expo#33070](https://github.com/expo/expo/issues/33070),
[Expo Network guide](https://www.codingeasypeasy.com/blog/expo-network-mastering-network-connectivity-and-reachability-in-react-native).

## How the wrapper works

`@symbiote-native/network` ships **zero React/Vue/Angular/Svelte/Solid logic in `expo-network` itself** — that
package's own JS is hand-ported, verbatim, into this package's own `core/` (its types file has no
`expo` meta-package import to swap out in the first place, unlike local-auth's or brightness's):

```
packages/network/src/
├── core/                        getNetworkStateAsync/getIpAddressAsync/isAirplaneModeEnabledAsync +
│                                  addNetworkStateListener; native-module.ts resolves the single
│                                  `ExpoNetwork` native module via expo-modules-core's
│                                  requireNativeModule
├── react/hooks/use-network-state   @symbiote-native/network/react
├── vue/composables/use-network-state @symbiote-native/network/vue
├── svelte/runes/use-network-state  @symbiote-native/network/svelte
└── angular/services/network-state.service @symbiote-native/network/angular
```

Each adapter's `useNetworkState`/`NetworkStateService` is a thin lifecycle wrapper — seed from the
one-shot call, subscribe on mount, unsubscribe on unmount — over the same `core` functions, the
same one-listener seed-then-subscribe shape as
[battery](/docs/packages/battery/#how-the-wrapper-works)'s three hooks. The native code itself is
never vendored or copied — `expo-modules-autolinking` resolves it straight out of `node_modules`
(see [the native setup guide](/docs/howtos/expo-native-module-setup/)).
