Skip to content

Feat/migrate new dart client#245

Open
deaflynx wants to merge 4 commits into
thingsboard:develop/1.9.0from
deaflynx:feat/migrate-new-dart-client
Open

Feat/migrate new dart client#245
deaflynx wants to merge 4 commits into
thingsboard:develop/1.9.0from
deaflynx:feat/migrate-new-dart-client

Conversation

@deaflynx

@deaflynx deaflynx commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates the app from the hand-written thingsboard_client 4.2.1 to the autogenerated thingsboard_ce_client 4.4.0. The data-access layer is rebuilt on the generated client while the auth/session/storage/websocket runtime is preserved.

Key changes

  • Service → controller APIs: semantic services (getAlarmService(), getDeviceService(), …) replaced by generated per-controller APIs (getAlarmControllerApi(), getDeviceControllerApi(), …); responses are now Dio Response<T> and are unwrapped via .data.
  • Immutable models: generated built_value models with all-nullable fields replace the old mutable models; call sites updated for the Builder pattern, defensive null handling, and enum unknownDefaultOpenApi fallbacks.
  • Type/shape renames and query-object → flat-parameter changes across alarms, entities, auth/2FA, routes and themes.
  • Vendored client-side helpers (e.g. AlarmCommentJsonNode access, platform-version helpers) restored on the app side via extensions.

Review fixes (commit a5397f1)

Addresses the code-review findings on this PR plus a few related regressions found alongside:

  • Device claiming reports success only on a non-failure claim response (was unconditional).
  • commentNode made nullable to avoid an NPE on system-generated alarm activity.
  • Account deletion now bridges the built_value Authority to the hand-written enum (the comparisons were previously always false → dead code).
  • 2FA: no duplicate global error overlay on 429, and a null JWT token is surfaced rather than masked.
  • noauth QR login: timeouts, null-safe parsing, clear error on a missing token.
  • Asset details navigation fixed to match the /asset/:id route.

Testing

Manual verification of the migrated flows is in progress — priority on device claiming (response contract), 2FA 429 handling, and noauth QR edge cases.

deaflynx added 3 commits June 10, 2026 11:20
Replace the hand-written thingsboard_client (4.2.1, pub.dev) with the
autogenerated thingsboard_ce_client 4.4.0 referenced via local path.

- Route all API access through per-controller accessors that return
  Response<T>, unwrapped via .data.
- Construct/mutate models with the built_value builder and rebuild APIs.
- Move mobile-app info to getMobileAppControllerApi (pkgName/platform).
- Add a barrel (thingsboard_client.dart) that hides generated types
  shadowing hand-written/Flutter counterparts and re-exports the
  hand-written auth models, keeping the import surface stable.
- Add support helpers: toPageData adapter, alarm query keys, mobile
  basic info, platform version, pages layout, version info, and an
  AlarmCommentInfo extension.
- Adapt to the community client surface: UserEmailInfo in place of
  UserInfo, AssetInfo listing endpoints, immutable additionalInfo via a
  session-local dashboard-access flag, EntityType.oAUTH2CLIENT, and an
  authorityFromString bridge between the generated and hand-written
  Authority types.
…tion

- Re-apply the rotated JWT pair returned by changePassword so the stored
  refresh token stays valid and the session is not dropped.
- Fix alarm assignee lookup, comment editing, and sort ordering that
  regressed against the new client's data shapes.
getAuthorityName switched over the generated built_value Authority using
hand-written enum constants, so no case ever matched and the role label
rendered blank. Bridge the value by name before matching.
@deaflynx deaflynx added this to the 1.9.0 milestone Jun 10, 2026
@deaflynx

Copy link
Copy Markdown
Contributor Author

Code review

Found 3 issues:

  1. Device claim now reports success even when the server rejects the claim. The migration dropped RequestConfig(ignoreErrors: true) and the if (response.response == ClaimResponse.CLAIMED || SUCCESS) {...} else { emit error } check. The new code fires DeviceProvisioningStatus.done unconditionally whenever the call doesn't throw, so an HTTP 200 with a failure body (e.g. FAILURE, CLAIMED_BY_ANOTHER_USER) is silently treated as success. This regresses the error path maintained in 95621cb.

.getDeviceControllerApi()
.claimDevice(
deviceName: deviceName,
claimRequest: ClaimRequest((b) => b..secretKey = deviceSecretKey),
)
.timeout(
const Duration(seconds: 20),
onTimeout:
() => throw Exception('Device claiming timeout reached'),
);
communicationService.fire(
const DeviceProvisioningStatusChangedEvent(
DeviceProvisioningStatus.done,
),

  1. commentNode force-unwraps a nullable comment, crashing on system alarm activities. comment is JsonObject? and system-generated activity entries (acks, clears, status changes) have comment == null. SystemActivityWidget calls .commentNode on exactly those entries, so comment! throws "Null check operator used on a null value". This is the same crash class fixed and re-applied in a639cab / 245c362; the guard is now gone.

extension AlarmCommentInfoExt on AlarmCommentInfo {
AlarmCommentJsonNode get commentNode =>
AlarmCommentJsonNode.fromJson(comment!.asMap.cast<String, dynamic>());
}

  1. Asset details navigation is broken by a route path mismatch. The asset details route was moved from a nested child of /assets (full path /assets/asset/:id) to a top-level route with path /asset/:id, but the call site still navigates to /assets/asset/..., which no longer matches — the details page will not open. (The move also drops parentNavigatorKey: globalNavigatorKey, unlike every other details route, so the bottom nav bar would overlay the page.)

GoRoute(
path: '${AssetRoutes.asset}/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return AssetDetailsPage(id);
},

if (asset.id?.id != null) {
getIt<ThingsboardAppRouter>().navigateTo('/assets/asset/${asset.id!.id}');
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- account deletion: bridge built_value Authority via authorityFromString so
  the tenant/customer delete branches actually match and run
- alarm activity: guard nullable AlarmCommentInfo.comment (commentNode now
  nullable) to avoid NPE on system-generated entries
- device claiming: report success only when the claim response is not an
  explicit failure, instead of firing done unconditionally
- 2FA: suppress the duplicate global error overlay on 429 via extra
  ignoreErrors, and guard a null JWT token instead of masking it with ''
- noauth QR login: add connect/receive timeouts, null-safe response parsing
  and a clear error when the target host returns no token
- asset details: navigate to '/asset/:id' to match the route (was the stale
  '/assets/asset/:id', so the details page never opened)
@deaflynx

Copy link
Copy Markdown
Contributor Author

Addressed in a5397f1.

1. Device claim false success — no longer fires done unconditionally. _isClaimFailure inspects the claim response and reports success only when the body isn't an explicit non-success result (empty / CLAIMED / SUCCESS count as success).

// body (e.g. the CLAIMED case) is still considered success.
if (_isClaimFailure(response.data)) {
emit(const DeviceProvisioningClaimingErrorState(null));
return;
}
communicationService.fire(
const DeviceProvisioningStatusChangedEvent(
DeviceProvisioningStatus.done,
),
);

2. commentNode NPE on system activitycommentNode now returns AlarmCommentJsonNode?; all three call sites use ?., so system-generated entries with a null comment no longer crash.

extension AlarmCommentInfoExt on AlarmCommentInfo {
/// `comment` is nullable on the new built_value model, and system-generated
/// activity entries may not carry a payload, so callers must handle null.
AlarmCommentJsonNode? get commentNode {
final raw = comment?.asMap;
if (raw == null) return null;
return AlarmCommentJsonNode.fromJson(raw.cast<String, dynamic>());
}
}

3. Asset details route mismatch — call site now navigates to /asset/:id to match the route.

@override
void onEntityTap(AssetInfo asset, WidgetRef ref) {
if (asset.id?.id != null) {
getIt<ThingsboardAppRouter>().navigateTo('/asset/${asset.id!.id}');
}

(Left parentNavigatorKey as-is — the device detail route and PE both omit it; will revisit if the bottom nav overlays the page during testing.)

Also fixed while in these files:

  • Account deletion was dead codeuser.authority (built_value enum) was compared against the hand-written Authority enum the barrel re-exports, so both delete branches were unreachable. Bridged via authorityFromString(user.authority.name).
  • 2FA — suppressed the duplicate global error overlay on 429 (extra: {'ignoreErrors': true}) and guard a null JWT token instead of masking it with ?? ''.
  • noauth QR login — added connect/receive timeouts, null-safe response parsing, and a clear error when the target host returns no token.

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