MAUI Networking and Offline Data
Use this skill when a MAUI feature calls backend APIs, works against local
services during development, persists local data, or must behave well offline.
Workflow
- Inspect
MauiProgram.cs, existing API clients, model serialization, and data
storage packages.
- Register API clients through DI. Prefer typed clients or named clients over
constructing
HttpClient in pages.
- Define request/response DTOs and serialization options once. Prefer
System.Text.Json source generation for trimmed or NativeAOT-sensitive apps.
- Decide the local development address by runtime platform and environment.
- Keep cleartext HTTP exceptions debug-only and platform-scoped.
- Define offline boundaries before schema/code: name the read-only cached
reference data, the entities users can edit while offline, and the fields
that remain server-authoritative.
- For SQLite/offline sync answers, show the storage initialization API before
the entity schema: explicitly name
SQLiteAsyncConnection plus
CreateTableAsync<T> for sqlite-net, or an EF Core DbContext setup. Do not
only show POCO models, sqlite-net attributes, or db.Table<T>() queries.
- In every offline-sync design, include a security line: if cached data is
sensitive, regulated, or contains customer/business PII, encrypt the local
SQLite store, for example with SQLCipher/provider encryption, and keep the
database key in
SecureStorage rather than source code or Preferences.
- In every offline-sync design, include a background performance line: drain
queued sync work in bounded batches/chunks off the UI thread, and marshal only
UI updates through
MainThread when needed.
- Store local data in SQLite or app data files behind a repository/service
abstraction.
- Add cancellation, timeout, retry, and user-facing error states.
HttpClient DI Pattern
builder.Services.AddHttpClient<IProductsApi, ProductsApi>(client =>
{
client.BaseAddress = new Uri("https://api.contoso.dev/");
});
Keep auth headers in a delegating handler or typed client boundary. Do not add
bearer tokens in every page or ViewModel.
Local Development Networking
| Runtime |
Local backend address |
| Android emulator |
Use 10.0.2.2 to reach the host machine. |
| iOS simulator |
localhost usually reaches the Mac host. |
| Windows/Mac Catalyst app |
localhost reaches the same machine. |
| Physical device |
Use a LAN-reachable hostname/IP, reverse tunnel, dev proxy, or deployed endpoint. |
For cleartext HTTP during development:
- Android needs a debug-only network security config such as
networkSecurityConfig or UsesCleartextTraffic. Gate it with #if DEBUG,
IsDevelopment, or an MSBuild Condition on the Debug configuration so it is
never present in Release builds.
- iOS/Mac Catalyst need an App Transport Security (
NSAppTransportSecurity)
exception for debug HTTP, also guarded by a Debug-only condition.
- Release builds should use HTTPS and remove broad cleartext exceptions.
Connectivity Check
Use MAUI connectivity APIs to decide when to show offline UI and when to drain
sync queues:
public sealed class SyncService(IConnectivity connectivity)
{
public bool IsOnline => connectivity.NetworkAccess == NetworkAccess.Internet;
}
Subscribe to ConnectivityChanged from a long-lived service when the app should
resume sync on reconnect, and unsubscribe when the owner is disposed. Only
NetworkAccess.Internet indicates a routable connection. In migrated apps,
replace Xamarin.Essentials.Connectivity with Microsoft.Maui.Networking
APIs or injected IConnectivity.
JSON Guidance
- Use one
JsonSerializerOptions instance for casing, enum conversion, and null
handling.
- Prefer source-generated
JsonSerializerContext when the app is trimmed,
NativeAOT-sensitive, or serializes many known DTOs.
- Separate DTOs from database entities when local schema and API shape evolve at
different speeds.
SQLite and Offline Sync
Use SQLite for structured local state, offline queues, and cached server data.
Start offline-sync guidance with an explicit boundary list or table so the
answer does not imply every local row is editable. For example:
| Boundary |
Examples |
Sync behavior |
| Read-only cached reference data |
product catalog, feature flags, lookup values |
pull/refresh from server; do not enqueue local edits |
| Offline-editable user data |
draft orders, notes, inspection forms |
local writes get dirty state and outbox entries |
| Server-authoritative data |
payment status, inventory counts, account roles |
display cached values but require online server confirmation before mutation |
For sqlite-net, this usually means a SQLiteAsyncConnection and
CreateTableAsync<T> setup before any db.Table<T>() queries. For EF Core, keep
the local schema behind a DbContext.
Always include a short initialization snippet in offline-sync guidance so the
storage layer is concrete, not just the row shape or sqlite-net attributes.
Immediately after the boundary table or storage initialization, include a
one-sentence security decision for sensitive offline data, naming encryption
such as SQLCipher and a SecureStorage-protected key when applicable.
Also include a one-sentence sync performance decision using the words
background and batch or chunk, for example: "Drain the outbox on a
background worker in small batches/chunks and use MainThread only to update
progress UI."
var db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<Order>();
await db.CreateTableAsync<SyncOperation>();
Common fields for syncable rows:
- Server ID and local ID.
UpdatedAt, ETag, row version, or another concurrency token.
- Dirty state such as
PendingCreate, PendingUpdate, or PendingDelete.
- Tombstone/deleted marker when deletes must sync.
Keep sync boundaries explicit:
- Cache read-only reference data separately from editable offline data and say
which local tables are never enqueued for upload.
- Retry idempotent operations automatically; ask the user before replaying
non-idempotent actions.
- Resolve conflicts with a documented policy: server wins, client wins, field
merge, or user decision.
- Persist queued operations before sending them so app termination does not lose
offline edits.
- Encrypt the local SQLite database when it stores sensitive business data.
Options include SQLCipher-based packages or provider-specific SQLite
encryption. Store the database key in
SecureStorage, not in source code or
Preferences.
- Page or chunk large sync operations and write in bounded batches so the app
stays responsive and avoids mobile memory spikes.
- Drain sync queues on background work, not the UI thread; marshal only progress
or completion updates back with
MainThread.BeginInvokeOnMainThread when UI
needs to change.
Retry and Error Handling
- Pass
CancellationToken from UI commands and lifecycle shutdown paths.
- Use short connection timeouts and clear user messages for no network,
unauthorized, validation, and server errors.
- Retry only transient failures such as timeouts and 5xx responses.
- Use exponential backoff with jitter for background sync.
- Do not silently swallow sync failures; surface pending state and retry status.
Validation Checklist
- API clients are registered in DI and are not created directly in UI code.
- Local dev base addresses are platform-aware.
- Cleartext HTTP exceptions are debug-only.
- SQLite state has an explicit sync/conflict boundary and names the concrete
storage API, such as
SQLiteAsyncConnection with CreateTableAsync<T> or an
EF Core DbContext.
- Sensitive local data has an encryption decision, such as SQLCipher plus a key
stored in
SecureStorage.
- Requests support cancellation and distinguish transient from permanent errors.
1---2name: maui-networking-offline-data3description: Build MAUI networking and offline data. USE FOR: typed `HttpClient`, JSON serialization, Android `10.0.2.2`, iOS simulator localhost, LAN/dev-tunnel fallback, debug cleartext, offline-first screens, SQLite/EF Core sync metadata, queues, encryption decisions, retries, cancellation. DO NOT USE FOR: auth redirects, Aspire service discovery, UI layout.4---56# MAUI Networking and Offline Data78Use this skill when a MAUI feature calls backend APIs, works against local9services during development, persists local data, or must behave well offline.1011## Workflow12131. Inspect `MauiProgram.cs`, existing API clients, model serialization, and data14 storage packages.152. Register API clients through DI. Prefer typed clients or named clients over16 constructing `HttpClient` in pages.173. Define request/response DTOs and serialization options once. Prefer18 `System.Text.Json` source generation for trimmed or NativeAOT-sensitive apps.194. Decide the local development address by runtime platform and environment.205. Keep cleartext HTTP exceptions debug-only and platform-scoped.216. Define offline boundaries before schema/code: name the read-only cached22 reference data, the entities users can edit while offline, and the fields23 that remain server-authoritative.247. For SQLite/offline sync answers, show the storage initialization API before25 the entity schema: explicitly name `SQLiteAsyncConnection` plus26 `CreateTableAsync<T>` for sqlite-net, or an EF Core `DbContext` setup. Do not27 only show POCO models, sqlite-net attributes, or `db.Table<T>()` queries.288. In every offline-sync design, include a security line: if cached data is29 sensitive, regulated, or contains customer/business PII, encrypt the local30 SQLite store, for example with SQLCipher/provider encryption, and keep the31 database key in `SecureStorage` rather than source code or `Preferences`.329. In every offline-sync design, include a background performance line: drain33 queued sync work in bounded batches/chunks off the UI thread, and marshal only34 UI updates through `MainThread` when needed.3510. Store local data in SQLite or app data files behind a repository/service36 abstraction.3711. Add cancellation, timeout, retry, and user-facing error states.3839## HttpClient DI Pattern4041```csharp42builder.Services.AddHttpClient<IProductsApi, ProductsApi>(client =>43{44 client.BaseAddress = new Uri("https://api.contoso.dev/");45});46```4748Keep auth headers in a delegating handler or typed client boundary. Do not add49bearer tokens in every page or ViewModel.5051## Local Development Networking5253| Runtime | Local backend address |54| --- | --- |55| Android emulator | Use `10.0.2.2` to reach the host machine. |56| iOS simulator | `localhost` usually reaches the Mac host. |57| Windows/Mac Catalyst app | `localhost` reaches the same machine. |58| Physical device | Use a LAN-reachable hostname/IP, reverse tunnel, dev proxy, or deployed endpoint. |5960For cleartext HTTP during development:6162- Android needs a debug-only network security config such as63 `networkSecurityConfig` or `UsesCleartextTraffic`. Gate it with `#if DEBUG`,64 `IsDevelopment`, or an MSBuild `Condition` on the Debug configuration so it is65 never present in Release builds.66- iOS/Mac Catalyst need an App Transport Security (`NSAppTransportSecurity`)67 exception for debug HTTP, also guarded by a Debug-only condition.68- Release builds should use HTTPS and remove broad cleartext exceptions.6970## Connectivity Check7172Use MAUI connectivity APIs to decide when to show offline UI and when to drain73sync queues:7475```csharp76public sealed class SyncService(IConnectivity connectivity)77{78 public bool IsOnline => connectivity.NetworkAccess == NetworkAccess.Internet;79}80```8182Subscribe to `ConnectivityChanged` from a long-lived service when the app should83resume sync on reconnect, and unsubscribe when the owner is disposed. Only84`NetworkAccess.Internet` indicates a routable connection. In migrated apps,85replace `Xamarin.Essentials.Connectivity` with `Microsoft.Maui.Networking`86APIs or injected `IConnectivity`.8788## JSON Guidance8990- Use one `JsonSerializerOptions` instance for casing, enum conversion, and null91 handling.92- Prefer source-generated `JsonSerializerContext` when the app is trimmed,93 NativeAOT-sensitive, or serializes many known DTOs.94- Separate DTOs from database entities when local schema and API shape evolve at95 different speeds.9697## SQLite and Offline Sync9899Use SQLite for structured local state, offline queues, and cached server data.100Start offline-sync guidance with an explicit boundary list or table so the101answer does not imply every local row is editable. For example:102103| Boundary | Examples | Sync behavior |104|----------|----------|---------------|105| Read-only cached reference data | product catalog, feature flags, lookup values | pull/refresh from server; do not enqueue local edits |106| Offline-editable user data | draft orders, notes, inspection forms | local writes get dirty state and outbox entries |107| Server-authoritative data | payment status, inventory counts, account roles | display cached values but require online server confirmation before mutation |108109For sqlite-net, this usually means a `SQLiteAsyncConnection` and110`CreateTableAsync<T>` setup before any `db.Table<T>()` queries. For EF Core, keep111the local schema behind a `DbContext`.112113Always include a short initialization snippet in offline-sync guidance so the114storage layer is concrete, not just the row shape or sqlite-net attributes.115Immediately after the boundary table or storage initialization, include a116one-sentence security decision for sensitive offline data, naming encryption117such as SQLCipher and a `SecureStorage`-protected key when applicable.118Also include a one-sentence sync performance decision using the words119`background` and `batch` or `chunk`, for example: "Drain the outbox on a120background worker in small batches/chunks and use `MainThread` only to update121progress UI."122123```csharp124var db = new SQLiteAsyncConnection(databasePath);125await db.CreateTableAsync<Order>();126await db.CreateTableAsync<SyncOperation>();127```128129Common fields for syncable rows:130131- Server ID and local ID.132- `UpdatedAt`, `ETag`, row version, or another concurrency token.133- Dirty state such as `PendingCreate`, `PendingUpdate`, or `PendingDelete`.134- Tombstone/deleted marker when deletes must sync.135136Keep sync boundaries explicit:137138- Cache read-only reference data separately from editable offline data and say139 which local tables are never enqueued for upload.140- Retry idempotent operations automatically; ask the user before replaying141 non-idempotent actions.142- Resolve conflicts with a documented policy: server wins, client wins, field143 merge, or user decision.144- Persist queued operations before sending them so app termination does not lose145 offline edits.146- Encrypt the local SQLite database when it stores sensitive business data.147 Options include SQLCipher-based packages or provider-specific SQLite148 encryption. Store the database key in `SecureStorage`, not in source code or149 `Preferences`.150- Page or chunk large sync operations and write in bounded batches so the app151 stays responsive and avoids mobile memory spikes.152- Drain sync queues on background work, not the UI thread; marshal only progress153 or completion updates back with `MainThread.BeginInvokeOnMainThread` when UI154 needs to change.155156## Retry and Error Handling157158- Pass `CancellationToken` from UI commands and lifecycle shutdown paths.159- Use short connection timeouts and clear user messages for no network,160 unauthorized, validation, and server errors.161- Retry only transient failures such as timeouts and 5xx responses.162- Use exponential backoff with jitter for background sync.163- Do not silently swallow sync failures; surface pending state and retry status.164165## Validation Checklist166167- API clients are registered in DI and are not created directly in UI code.168- Local dev base addresses are platform-aware.169- Cleartext HTTP exceptions are debug-only.170- SQLite state has an explicit sync/conflict boundary and names the concrete171 storage API, such as `SQLiteAsyncConnection` with `CreateTableAsync<T>` or an172 EF Core `DbContext`.173- Sensitive local data has an encryption decision, such as SQLCipher plus a key174 stored in `SecureStorage`.175- Requests support cancellation and distinguish transient from permanent errors.