Dragon Guard Handheld WMS is built around a strict MVVM pattern wired together by .NET MAUI’s built-in dependency injection. This page documents the file layout, how the app starts up, how services are registered, the two HTTP client pipelines, and the rules governing navigation and base URL resolution. Read this before touching app startup, DI wiring, or any HTTP handler.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/CodexaCP/DG_APK/llms.txt
Use this file to discover all available pages before exploring further.
Do not use Shell navigation.
AppShell.xaml is excluded from compilation in Handheld.csproj — it crashes on rugged Android firmware during fragment startup. Navigation is handled exclusively through NavigationService, which wraps a NavigationPage root set in App.xaml.cs.File layout
| Folder | Responsibility |
|---|---|
Constants/ | Compile-time API base URLs and other app-wide literals |
Components/ | Reusable XAML widgets shared across multiple pages |
Helpers/ | StatusPalette — maps status codes to short labels (Rel/Recv/Ship/Part/Done) and badge colors |
Models/ | Plain POCO DTOs; no business logic, JSON attributes only |
Services/ | All I/O: API calls, session state, navigation, device identity, scanning abstraction |
ViewModels/ | Page-level state and commands; load data on demand, keep only in-memory collections |
Views/ | ContentPage XAML + code-behind; minimal logic, delegates to VM |
Platforms/Android/ | MainActivity, rugged scanner SDK adapter, Android network security config |
Resources/ | Styles, colour palette, fonts, localisation resources |
Docs/ | Authoritative architecture and module decision records |
Startup flow
App.xaml.cs creates the NavigationPage root
App.CreateWindow() resolves SafeBootPage from DI and wraps it in a NavigationPage with Dragon Guard navy (#15325B) chrome. The window is returned immediately so Android draws the screen while startup continues asynchronously.App.xaml.cs
SafeBootPage displays the splash
SafeBootPage is a pure code-behind ContentPage (no XAML) that renders a full-screen blue gradient and an activity spinner with the text “Preparando entorno operativo”. It has no ViewModel and takes no user input — it exists solely to give the user visual feedback while the session initialises.InitializeStartupAsync() restores session state
Running on a background thread,
InitializeStartupAsync() calls SessionService.InitializeAsync(), which reads the persisted JWT token and company context from SecureStorage.App.xaml.cs
Route to MainPage or LoginPage
SessionService.HasValidOperationalContext returns true when a non-expired token and exactly one active company are present in storage. If it returns false — missing token, no valid company — the app navigates directly to LoginPage. Only an unhandled exception during initialisation triggers SignOutAsync() before redirecting to LoginPage. The NavigationPage root is replaced on the main thread via SetRootPage().Dependency injection registration
All services and pages are registered inMauiProgram.CreateMauiApp(). The separation between singletons and transients is deliberate:
- Singletons
- Transients
Singletons hold cross-cutting state that must survive for the lifetime of the app.
MauiProgram.cs
| Singleton | Responsibility |
|---|---|
SessionService | JWT token, user identity, active company, company access list |
ApiSettingsService | Current base URL; DEBUG override allowed |
AppExperienceService | Build-mode flags (AllowConnectionOverride, ShowConnectionSettings) |
NavigationService | Thread-safe push/pop; blocks protected navigation without a token |
DeviceIdentityService | WiFi MAC → ANDROID_ID → GUID fallback chain |
IScanCaptureService | Barcode/RFID abstraction; raises TagObserved for EPC reads |
Special case: RfidHandheldContextService
RfidHandheldContextService must be a singleton because it caches per-module RFID context across navigations. However, AddHttpClient<T> always registers T as a transient. To work around this, a named client is registered instead and the singleton is constructed manually using a factory:
MauiProgram.cs
Two HTTP client pipelines
MauiProgram.cs defines two private helpers that configure the handler chain for every typed service client:
| Pipeline | Handler chain | Services |
|---|---|---|
| Public | DynamicBaseUrlMessageHandler | AuthService |
| Protected | DynamicBaseUrlMessageHandler → AuthenticatedHttpMessageHandler | ItemService, ReceivingService, ShipmentService, ShipmentLineService, ReceivingLineService, InventoryMovementService, PickService, CompanyService, BinService, TransferService |
HttpClient starts with PlaceholderBaseUrl as its BaseAddress:
MauiProgram.cs
DynamicBaseUrlMessageHandler replaces this placeholder with ApiSettingsService.CurrentBaseUrl at request time, so the backend host can change (on DEBUG builds) without rebuilding the APK. On Android, SocketsHttpHandler replaces the default handler to avoid known rugged-firmware compatibility issues with HttpClientHandler.
AuthenticatedHttpMessageHandler
Every request routed through the protected pipeline is decorated with two headers byAuthenticatedHttpMessageHandler:
Authorization: Bearer <token>— the Dragon Guard JWT fromSessionServiceX-Company-Id: <guid>— the active company’s identifier fromSessionService
LoginPage rather than sending an unauthenticated request.
NavigationService
NavigationService is the only safe abstraction for pushing and popping pages. It uses a SemaphoreSlim(1, 1) to prevent concurrent navigation calls from multiple async paths:
Services/NavigationService.cs
PushAsync— protected; redirects toLoginPageifSessionService.IsAuthenticatedisfalse.PushUnprotectedAsync— skips the authentication check; used for pages reachable before login (e.g.WelcomeSetupPage).PopAsync— pops only if the navigation stack has more than one entry; no-ops silently otherwise.- Non-blocking semaphore (
WaitAsync(0)) — a concurrent push/pop is dropped rather than queued, preventing navigation races.
Base URL strategy
API base URLs are defined as compile-time constants:Constants/ApiConstants.cs
ApiSettingsService reads and exposes the current base URL at runtime. Its behaviour differs between build configurations:
Services/AppExperienceService.cs
| Build | AllowConnectionOverride | Behaviour |
|---|---|---|
DEBUG | true | Loads saved URL from Preferences; operator can change it in Settings |
Release | false | Deletes any saved URL key; always uses DefaultAndroidBaseUrl |