Skip to content

Enable self-service profile updates in Flutter SDKs - #34

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
janithjay:users-me-profile
Sep 3, 2026
Merged

Enable self-service profile updates in Flutter SDKs#34
brionmario merged 1 commit into
thunder-id:mainfrom
janithjay:users-me-profile

Conversation

@janithjay

Copy link
Copy Markdown
Contributor

Purpose

Currently, the Flutter SDK's user profile view decodes the sign-in token in Dart and renders its claims read-only, and its save button is a no-op that only fires the onSaved callback. The getUserProfile/updateUserProfile channel methods it should have used returned a hardcoded empty claim map from both native handlers.

This PR makes the profile view fetch from GET /users/me by default, matching the JavaScript, iOS, and Android SDKs, and adds,

  • Self-service editing per attribute, validated and saved against the schema from GET /users/me/meta (PUT /users/me).
  • Avatar picture and display-name sync from the real profile, kept in sync with ThunderIDProvider's user across sign-in/refresh.
  • An opt-out config flag, ThunderIDConfig.fetchUserProfile (default true), for apps that want the previous token-claims-only, read-only behavior instead.

Approach

This package performs no protocol work of its own, so the feature is bridged rather than reimplemented. This PR exposes it over the method channel and builds the Dart UI on top.

  • Platform channel: added getUserSchema and setCachedUser, and repointed getUserProfile/updateUserProfile at the real native methods, which previously returned a hardcoded empty claim map. The channel surface is now identical across the Kotlin handler, the Swift handler, and the Dart client at 21 methods.
  • Removed the Dart-side JWT decode: BaseUserProfile base64-decoded the access token to build its claim list, the only protocol work in lib/ outside the channel, while the already-bridged decodeJwtToken sat unused.
  • setCachedUser: both native clients short-circuit getUser() on their cached user, so a /users/me merge must be written back through the channel or the next refresh() resurrects the pre-merge claims.
  • user_profile.dart: schema-driven field building, validation (required + regex, invalid regex ignored rather than blocking save), and a merge-before-save step since the backend rejects a save missing any required attribute, even for a single-field edit.
  • Token-only mode (fetchUserProfile = false) mirrors the Quickstart's pre-existing claim-formatting verbatim, hoisted from the sample into the reusable SDK component.
  • ThunderIDProvider: the sync is fire-and-forget with failures swallowed, matching the JavaScript provider. updateShouldNotify now compares user by identity, since a merge swaps in a new User while isSignedIn stays true.

Data-contract pieces match the JavaScript SDK exactly: readonlyFields, the defaultAttributeMappings fallback order, the readonly derivation, the required-then-regex validation order, and the {attributes: ...} PUT payload.

Pre-existing defects this depended on

Three unrelated bugs blocked Android:

  • allowInsecureConnections was never bridged - the Android SDK exposes it and its own Quickstart sets it from BuildConfig.DEBUG, but it was absent from ThunderIDConfig, so a Flutter app could not reach a locally-served instance. Now bridged, defaulting to false, sample-gated on kDebugMode. No effect on iOS.

Related Issues

Related PRs

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Copilot AI lite review requested due to automatic review settings September 3, 2026 06:32
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: a077bdf7-c94d-4c7a-9754-b7843959c1c0


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are correctness and accessibility issues in the new profile editing flow (read-only enforcement, boolean payload typing, missing semantics) that should be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the Flutter ThunderID SDK to support self-service user profile editing by fetching the real profile (GET /users/me) and schema (GET /users/me/meta) via the platform channel, then validating and saving edits (PUT /users/me). It also keeps the provider’s cached user in sync with /users/me so UI consuming ThunderIDProvider.user updates immediately after saves/refresh.

Changes:

  • Added schema-driven profile field rendering + per-field edit/save/cancel behavior, with validation and merge-before-save payload building.
  • Extended the method-channel surface to support real profile/schema operations and native cached-user write-through (setCachedUser).
  • Added config flags for profile-fetch behavior and Android debug-only insecure TLS for local development, plus unit tests covering the new behavior.
File summaries
File Description
test/user_profile_fields_test.dart Adds unit tests for schema field building, validation, claim formatting, payload building, deep merge, and envelope decoding.
test/thunderid_client_test.dart Extends client mock + tests for getUserProfile/getUserSchema/updateUserProfile/setCachedUser and config defaults.
samples/quickstart/lib/screens/home_screen.dart Updates the sample profile screen to use BaseUserProfile and render inline editable fields.
samples/quickstart/lib/main.dart Enables debug-only allowInsecureConnections for local Android instances with self-signed certs.
lib/src/widgets/user_profile.dart Replaces token-decoding profile UI with schema-driven /users/me profile view + field edit/save flow and token-only fallback mode.
lib/src/widgets/thunderid_provider.dart Adds background /users/me sync and merges saved profile attributes into user, plus identity-based notify behavior.
lib/src/thunderid_client.dart Adds setCachedUser, getUserSchema, and updates profile APIs to return UserProfile envelopes.
lib/src/models/user_profile.dart Introduces /users/me UserProfile model + AttributeSchema model and deep normalization of nested channel maps.
lib/src/models/thunderid_config.dart Adds fetchUserProfile and allowInsecureConnections config flags and forwards them through toMap().
lib/src/i18n/default_strings.dart Adds default strings for edit/cancel and validation errors.
ios/thunderid_flutter/Package.swift Bumps iOS SDK dependency to 1.1.0.
ios/thunderid_flutter.podspec Bumps CocoaPods dependency to ThunderID >= 1.1.0.
ios/Classes/ThunderIDMethodHandler.swift Implements real getUserProfile, getUserSchema, updateUserProfile, and setCachedUser channel handlers + encoders.
android/src/main/kotlin/dev/thunderid/flutter/ThunderIDMethodHandler.kt Implements real profile/schema/update/cache methods; unwraps JSON containers for codec compatibility; bridges allowInsecureConnections.
android/build.gradle Bumps Android SDK dependency to v1.1.0.
Review details

Suppressed comments (1)

android/src/main/kotlin/dev/thunderid/flutter/ThunderIDMethodHandler.kt:137

  • updateUserProfile casts payload to Map<String, Any>, but a profile update payload can legitimately include nulls (and nested values decoded as platform collections). Using Any? prevents accidental type assumptions in this boundary layer.
                "updateUserProfile" -> {
                    @Suppress("UNCHECKED_CAST")
                    val payload = args["payload"] as? Map<String, Any> ?: emptyMap()
                    result.success(encodeUserProfile(client.updateUserProfile(payload)))
  • Files reviewed: 15/15 changed files
  • Comments generated: 7
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/src/widgets/user_profile.dart
Comment thread lib/src/widgets/user_profile.dart
Comment thread lib/src/widgets/user_profile.dart
Comment thread samples/quickstart/lib/screens/home_screen.dart
Comment thread lib/src/widgets/user_profile.dart
Comment thread samples/quickstart/lib/screens/home_screen.dart
Signed-off-by: janithjay <janithjayashan018@gmail.com>
@brionmario
brionmario merged commit 8600381 into thunder-id:main Sep 3, 2026
8 checks passed
@janithjay
janithjay deleted the users-me-profile branch September 3, 2026 08:51
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.

3 participants