Skip to content

feat: ENG-1123: setup() + command queue foundation#2

Merged
itsjavi merged 7 commits into
mainfrom
feat/sdk-setup
Jun 4, 2026
Merged

feat: ENG-1123: setup() + command queue foundation#2
itsjavi merged 7 commits into
mainfrom
feat/sdk-setup

Conversation

@pandeymangg
Copy link
Copy Markdown
Contributor

@pandeymangg pandeymangg commented Jun 2, 2026

What

Lands the four foundations the rest of the Flutter SDK builds on (ENG-1123):

  1. FormbricksConfigSharedPreferences-backed singleton (key formbricks-flutter); init-once, synchronous get(), awaited update()/reset().
  2. ApiClient — thin package:http wrapper for the two client endpoints, returning typed Result<T, ApiErrorResponse>.
  3. Formbricks.setup({appUrl, workspaceId}) — validates input, hydrates cached config, syncs workspace (+ user when one is cached and expired), installs a lifecycle-aware expiry ticker, marks setup complete.
  4. CommandQueue — FIFO single-in-flight worker so the public API can't race; each add returns its own Future.

Plus the supporting Result<T,E> sealed type, error hierarchy, config models, and a Logger. The playground demo now calls Formbricks.setup on launch (credentials via --dart-define / a local .env) and shows the result.

welcome() is removed — the real API replaces it.

Deliberate divergences from the RN SDK

  • No environmentId alias / legacy migration — workspaceId only.
  • Fixed the error-cooldown bug. RN skipped setup when the cooldown had expired (backwards). Here: cooldown still active → short-circuit; elapsed → clear + proceed.
  • Init config once, synchronous get() after (RN re-read storage on every access).
  • update() awaits the disk write before completing (RN fire-and-forget could lose state on crash).
  • One unified, lifecycle-aware expiry ticker (RN had two always-on 60s tickers); pauses on paused/inactive, immediate check on resumed.
  • clock.now() everywhere (never DateTime.now()) so expiry logic is deterministic in tests.
  • Named _kErrorCooldown constant instead of an inline 10 * 60000.
  • Logger uses dart:developer log(), not print (avoid_print is on).

Note: the getWorkspaceState environment endpoint targets /api/v2/client/{workspaceId}/environment (backend moved to v2); the user endpoint stays /api/v2/.../user.

Source → test mapping

Source Test
lib/src/common/result.dart test/common/result_test.dart
lib/src/common/command_queue.dart test/common/command_queue_test.dart
lib/src/common/config.dart test/common/config_test.dart
lib/src/common/api_client.dart test/common/api_client_test.dart
lib/src/common/setup.dart test/common/setup_test.dart
lib/src/common/expiry_ticker.dart test/common/expiry_ticker_test.dart
lib/src/types/config.dart test/types/config_test.dart
lib/src/types/errors.dart test/types/errors_test.dart
lib/formbricks_flutter.dart (facade) test/formbricks_flutter_test.dart
lib/src/common/logger.dart exercised by config_test / setup_test (no dedicated file)
lib/src/common/time.dart exercised by config_test / setup_test / expiry_ticker_test

Verification

  • dart analyze --fatal-infos . → clean.
  • melos run format-check → clean.
  • flutter test → all green (SDK + playground).
  • Coverage ≥ 80 % on every touched file (~95 % overall), incl. branch coverage on the setup error-cooldown.
  • Manual: ran the playground on an iOS sim — with a bad URL it surfaces FormbricksError(network_error), proving the full path (queue → ApiClient → http → error-state persisted). With real staging creds it shows "setup complete ✓".

Out of scope (follow-ups)

filterSurveys / displayPercentage (the filteredSurveys field stays []), track / identify / UpdateQueue, the survey WebView + SurveyStore (ENG-1127), GitHub Actions CI (ENG-1125), SonarQube (ENG-1124), encrypted-storage opt-in.

🤖 Generated with Claude Code

@pandeymangg pandeymangg changed the title feat: sdk setup ENG-1123: setup() + command queue foundation Jun 2, 2026
@pandeymangg pandeymangg changed the title ENG-1123: setup() + command queue foundation feat: ENG-1123: setup() + command queue foundation Jun 2, 2026
@pandeymangg pandeymangg requested a review from itsjavi June 2, 2026 09:21
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Walkthrough

This pull request implements the complete Formbricks Flutter SDK initialization infrastructure. It introduces a serialized command queue for operation ordering, persistent configuration management backed by SharedPreferences, HTTP API communication with error normalization, and lifecycle-aware expiry checking. The setup() function orchestrates workspace validation, caches config with error-cooldown semantics, and optionally starts an expiry ticker. A public Formbricks facade exposes setup() and debugStoredConfig() via the command queue. The playground app now reads environment variables and calls Formbricks.setup on launch, displaying status. Dependencies (http, shared_preferences, clock, mocktail, fake_async) are added, and the entire implementation is covered with 40+ tests verifying type safety, persistence, error handling, and lifecycle behavior.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a description explaining the setup and command queue implementation, its purpose, and how it enables subsequent SDK features.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately describes the main feature introduced: implementing the SDK setup orchestration and command queue foundation for the Flutter SDK.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

8-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale status note — welcome() no longer exists.

This PR replaces the placeholder welcome() with the Formbricks.setup facade, so the "currently exposes only a placeholder welcome()" status is now inaccurate. Update this note (and consider marking the setup + command queue roadmap row done at Line 183) to reflect the new public surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 8 - 10, Update the README status note to remove the
obsolete reference to the placeholder welcome() and instead mention the new
public facade Formbricks.setup as the current exposed API; also update the
Roadmap row about "setup + command queue" to marked done/completed to reflect
that Formbricks.setup and the command queue implementation exist now. Ensure the
phrasing replaces "welcome()" with "Formbricks.setup" and adjusts any
tense/visibility language so the public surface description is accurate.
tool/run.sh (1)

70-71: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix Android adb shell boot-wait portability by removing Bash [[ ... ]].

tool/run.sh still contains while [[ -z $(getprop sys.boot_completed) ]]; ... inside the remote adb shell command (lines 70-71). Android’s default /system/bin/sh won’t parse [[ ... ]], and the syntax failure is masked by || true.

Suggested fix
-    "$adb" -s "$(booted_serial)" wait-for-device shell \
-      'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' 2>/dev/null || true
+    "$adb" -s "$(booted_serial)" wait-for-device shell \
+      'while [ "$(getprop sys.boot_completed)" != "1" ]; do sleep 1; done' 2>/dev/null || true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/run.sh` around lines 70 - 71, The remote adb shell loop uses
Bash-specific [[ ... ]] which /system/bin/sh doesn't support; update the command
invoked in tool/run.sh (the "$adb" -s "$(booted_serial)" wait-for-device shell
... line) to use POSIX test syntax (for example: while [ -z "$(getprop
sys.boot_completed)" ]; do sleep 1; done) so the loop runs on Android's default
sh; ensure quoting around $(getprop ...) is present and remove or keep the
trailing || true as appropriate for error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/FLUTTER_SDK_PLAN.md`:
- Line 310: The fenced code block showing the project tree under
"formbricks_flutter/" lacks a language identifier which triggers markdownlint
MD040; update the opening fence from ``` to ```text (i.e., add the "text"
language identifier) for that code block so the block becomes ```text ... ```
while keeping the existing closing backticks unchanged.

In `@packages/formbricks_flutter/lib/src/common/api_client.dart`:
- Line 114: Replace the non-testable DateTime.now() used in the cache-busting
query param of the environment endpoint string
('/api/v2/client/$workspaceId/environment?rand=${DateTime.now().millisecondsSinceEpoch}')
with clock.now().millisecondsSinceEpoch and add the package:clock import (import
'package:clock/clock.dart') at the top of the file so the call is mockable in
tests; update the exact string expression in api_client.dart where that URL is
built.

In `@packages/formbricks_flutter/lib/src/common/logger.dart`:
- Around line 29-31: The configure method currently no-ops when level is null,
leaving a previously set verbosity sticky; update the static
configure({LogLevel? level}) in logger.dart so that when level is null it resets
_instance._level to the library’s default log level (e.g. LogLevel.info) instead
of doing nothing — change the branch that checks level to assign either the
provided level or the default to _instance._level (referencing configure,
_instance._level, and LogLevel).

In `@packages/formbricks_flutter/lib/src/common/setup.dart`:
- Around line 98-143: The ApiClient created as api must be closed on all
error/early-return paths to avoid leaking the underlying http.Client; modify the
setup flow so that ApiClient.close() is called if _syncExistingConfig(config,
api, existing) returns an Err or if api.getWorkspaceState() returns an Err
(before returning or throwing in _handleErrorOnFirstSetup), and ensure it's also
closed in the fresh-setup success/error branch and before returning when
startTicker is false—use a try/finally around the code that uses api (including
calls to _syncExistingConfig and getWorkspaceState) or explicitly call
api.close() in the Err branches; reference ApiClient, _syncExistingConfig,
getWorkspaceState, and _handleErrorOnFirstSetup to locate the relevant spots.

In `@packages/formbricks_flutter/lib/src/types/config.dart`:
- Around line 133-137: The copyWith implementations (e.g. TUserState.copyWith)
use "param ?? this.field" so callers cannot clear fields by passing null; change
these to use a sentinel default value (e.g. a private const _unset = Object())
and make parameters default to that sentinel (e.g. Object? expiresAt = _unset,
Object? data = _unset), then inside copyWith check "expiresAt == _unset ?
this.expiresAt : expiresAt as DateTime?" and similarly for data; apply the same
sentinel pattern to the other copyWith methods that touch workspace,
workspaceId, and appUrl so callers can pass null to explicitly clear those
nullable fields.

In `@packages/formbricks_flutter/README.md`:
- Around line 31-32: The README text is ambiguous about whether setup returns a
Result or throws an exception; update the wording around the setup contract to
consistently state the chosen error handling approach (either "setup returns
Result with Err(FormbricksSetupError)" or "setup throws FormbricksSetupError"),
and make related lines mention the same behavior (including the 10-minute
cooldown) while aligning examples (the earlier Err(...) example or try/catch) to
use the same contract; reference the setup function name and the
FormbricksSetupError type and update any sample code and sentence that mentions
Err(...) or throws to match the single chosen contract.

---

Outside diff comments:
In `@README.md`:
- Around line 8-10: Update the README status note to remove the obsolete
reference to the placeholder welcome() and instead mention the new public facade
Formbricks.setup as the current exposed API; also update the Roadmap row about
"setup + command queue" to marked done/completed to reflect that
Formbricks.setup and the command queue implementation exist now. Ensure the
phrasing replaces "welcome()" with "Formbricks.setup" and adjusts any
tense/visibility language so the public surface description is accurate.

In `@tool/run.sh`:
- Around line 70-71: The remote adb shell loop uses Bash-specific [[ ... ]]
which /system/bin/sh doesn't support; update the command invoked in tool/run.sh
(the "$adb" -s "$(booted_serial)" wait-for-device shell ... line) to use POSIX
test syntax (for example: while [ -z "$(getprop sys.boot_completed)" ]; do sleep
1; done) so the loop runs on Android's default sh; ensure quoting around
$(getprop ...) is present and remove or keep the trailing || true as appropriate
for error handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cd4cd1c-f508-4fbc-9fe8-13d96fce2d66

📥 Commits

Reviewing files that changed from the base of the PR and between 274c9b6 and 0a20572.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (30)
  • .gitignore
  • README.md
  • apps/playground/.env.example
  • apps/playground/README.md
  • apps/playground/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  • apps/playground/lib/main.dart
  • docs/FLUTTER_SDK_PLAN.md
  • packages/formbricks_flutter/README.md
  • packages/formbricks_flutter/lib/formbricks_flutter.dart
  • packages/formbricks_flutter/lib/src/common/api_client.dart
  • packages/formbricks_flutter/lib/src/common/command_queue.dart
  • packages/formbricks_flutter/lib/src/common/config.dart
  • packages/formbricks_flutter/lib/src/common/expiry_ticker.dart
  • packages/formbricks_flutter/lib/src/common/logger.dart
  • packages/formbricks_flutter/lib/src/common/result.dart
  • packages/formbricks_flutter/lib/src/common/setup.dart
  • packages/formbricks_flutter/lib/src/common/time.dart
  • packages/formbricks_flutter/lib/src/types/config.dart
  • packages/formbricks_flutter/lib/src/types/errors.dart
  • packages/formbricks_flutter/pubspec.yaml
  • packages/formbricks_flutter/test/common/api_client_test.dart
  • packages/formbricks_flutter/test/common/command_queue_test.dart
  • packages/formbricks_flutter/test/common/config_test.dart
  • packages/formbricks_flutter/test/common/expiry_ticker_test.dart
  • packages/formbricks_flutter/test/common/result_test.dart
  • packages/formbricks_flutter/test/common/setup_test.dart
  • packages/formbricks_flutter/test/formbricks_flutter_test.dart
  • packages/formbricks_flutter/test/types/config_test.dart
  • packages/formbricks_flutter/test/types/errors_test.dart
  • tool/run.sh

Comment thread docs/FLUTTER_SDK_PLAN.md
Comment thread packages/formbricks_flutter/lib/src/common/api_client.dart Outdated
Comment thread packages/formbricks_flutter/lib/src/common/logger.dart
Comment thread packages/formbricks_flutter/lib/src/common/setup.dart
Comment thread packages/formbricks_flutter/lib/src/types/config.dart Outdated
Comment thread packages/formbricks_flutter/README.md Outdated
Copy link
Copy Markdown
Member

@itsjavi itsjavi left a comment

Choose a reason for hiding this comment

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

thank you @pandeymangg tests and build passes locally,
I left a couple of remarks that would be good to consider.

Comment thread packages/formbricks_flutter/lib/src/common/api_client.dart Outdated
Comment thread packages/formbricks_flutter/lib/src/common/api_client.dart
Comment thread packages/formbricks_flutter/lib/src/common/setup.dart
@pandeymangg pandeymangg requested a review from itsjavi June 4, 2026 05:43
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 4, 2026

Copy link
Copy Markdown
Member

@itsjavi itsjavi left a comment

Choose a reason for hiding this comment

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

your last 3 commits make sense to me, and all works locally. approved

@itsjavi itsjavi merged commit 8b3ae53 into main Jun 4, 2026
10 checks passed
@itsjavi itsjavi deleted the feat/sdk-setup branch June 4, 2026 08:48
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.

2 participants