Skip to content

fix: SoftAP/BLE device provisioning navigation & retry fixes (PROD-5897/5940/6041/6042) - #2

Open
deaflynx wants to merge 8 commits into
feat/migrate-new-dart-clientfrom
fix/provisioning
Open

fix: SoftAP/BLE device provisioning navigation & retry fixes (PROD-5897/5940/6041/6042)#2
deaflynx wants to merge 8 commits into
feat/migrate-new-dart-clientfrom
fix/provisioning

Conversation

@deaflynx

Copy link
Copy Markdown
Owner

Summary

SoftAP/BLE device-provisioning navigation, retry, and error-handling fixes, validated end-to-end on a real ESP32 (SoftAP) device. PE counterpart of deaflynx/flutter_thingsboard_app#2.

8 focused commits covering 5 reported issues:

Commit Fix Issue
re-enter flow on "Try again" when no Wi-Fi found retry returns to the Wi-Fi list instead of dead-ending PROD-6041
read provisioning authority from auth user correct role gate (support)
release Wi-Fi process binding after claiming re-provisioning works after a failed claim PROD-5897
supersede in-flight SoftAP connection attempts retry screen updates immediately PROD-5940
show connection progress, fail fast on Android, actionable error screen no long hang; clear error UI PROD-6042
guard SoftAP bloc dispose when provisioning never initialized avoids dispose crash
prevent navigator over-pop when exiting provisioning flow exit dialog no longer empties the navigator stack
stop QR scanner double-pop tearing down the provisioning page one-shot scan guard; fixes re-scan crash PROD-5897

Testing

Verified on physical ESP32 (SoftAP) on Android (Pixel 6, Android 16):

  • PROD-6042 — "Ready" without joining device Wi-Fi → ~10s fail-fast → actionable "Unable connect to device" screen ✅
  • PROD-5940 — repeated "Try again" → screen updates immediately every time ✅
  • PROD-6041 — wrong Wi-Fi password → "Try again" → returns to Wi-Fi list ✅
  • PROD-5897 — provision → failed claim → re-scan QR → page stays up, no crash ✅
  • Full happy path also confirmed (device joined Wi-Fi, got IP, SuccessfullyProvisionedEvent).

flutter analyze on the touched code: no issues.

iOS pass still pending (requires macOS build); fixes are platform-shared Dart but the iOS flow differs (auto-join pop-up) and warrants a separate re-test.

Notes

🤖 Generated with Claude Code

deaflynx added 8 commits June 18, 2026 14:40
…D-6041)

The "Device not able to find Wi-Fi nearby" screen passed an empty
onTryAgain callback, so its Try again button did nothing. Wire it to
EspSoftApConnectToDeviceEvent so it reconnects and re-scans for networks,
matching the "Unable connect to device" error screen.
…text

TbContext is no longer registered in GetIt (commented out in locator),
so getIt<TbContext>() in the device provisioning action threw and broke
the entire provisioning flow before it started. Read the current user's
authority via ITbClientService().client.getAuthUser() to gate the
customer-role check, matching how the rest of the app reads the user.
…works (PROD-5897)

On Android, connectToSecureNetwork binds the whole process to the home
Wi-Fi network (bindProcessToNetwork) so the device can be claimed over
the internet. That binding was never released, so a later provisioning
attempt could no longer reach the device SoftAP at 192.168.4.1 ("Machine
is not on the network", errno 64) and showed "Unable connect to device"
on re-entry after a failed claim.

Release the binding via PluginWifiConnect.disconnect() once claiming
resolves (success or failure) in DeviceProvisioningBloc, and as a safety
net in EspSoftApView.dispose() for early exits.
…pdates immediately (PROD-5940)

Tapping Ready / Try again while a previous connection-retry chain was still cycling spawned a second concurrent chain that shared the connectionRetries counter and kept emitting states, so a stale chain could repaint the "Unable connect to device" screen right after the user's new action.

Tag each connection attempt with a monotonic generation: a user-initiated tap bumps it and resets the retry budget, while internal retries carry their own generation and bail at every suspension point once superseded. The latest user action now always wins and the loading state shows immediately.
… screen (PROD-6042)

Tapping Ready without joining the device's Wi-Fi previously sat on a bare spinner for ~90s (6 x 15s retries) and then showed a generic error.

- Loading screen now shows the in-progress attempt number instead of a silent spinner.
- Retries are tuned per platform: iOS keeps a short exponential backoff (1/2/4/8s) to ride out the one-time Local Network permission prompt; Android, which has no such prompt, retries once and fails fast.
- The connection-error screen now explains the likely cause (phone not on the device's Wi-Fi) and offers an Open Wi-Fi settings button; the Wi-Fi-not-found copy is clearer too.
EspSoftApBloc.close() unconditionally called provisioning.dispose(), but provisioning is a late field only assigned after a successful startProvisioning. Exiting the flow after a failed connection (e.g. the device's Wi-Fi was never joined) therefore threw a LateInitializationError. Track initialization with a flag and only dispose when set.

Found while testing PROD-6042.
The exit confirmation dialog's 'Yes' action popped the navigator twice
unconditionally: once to close the dialog and once to leave the
provisioning page. When the provisioning page was the only remaining
route, the second pop emptied the stack and crashed GoRouter
('popped the last page off of the stack'), leaving a dark screen.

The dialog now returns a bool result; each caller (SoftAP and BLE
views) awaits it and pops the page once via maybePop(), guarded by
context.mounted.
MobileScanner.onDetect fires per camera frame, so the QR route was
popped multiple times: the first pop returned the Barcode correctly,
but a lingering second pop popped the next route (the ESP provisioning
page) with a stray Barcode result. On re-scan after a failed claim this
flashed the SoftAP page (onCreate->onClose) and, via navigateTo's blind
'result as T?', crashed with a TypeError.

- qr_code_scanner: one-shot guard so onDetect pops exactly once
- router: navigateTo returns null on an unexpected result type instead
  of throwing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant