Skip to content

SDK 4.x DemoApp - #194

Open
kupriyanovNik wants to merge 11 commits into
devfrom
feat/sdk-4.x-demo-app
Open

SDK 4.x DemoApp#194
kupriyanovNik wants to merge 11 commits into
devfrom
feat/sdk-4.x-demo-app

Conversation

@kupriyanovNik

Copy link
Copy Markdown

No description provided.

@kupriyanovNik
kupriyanovNik changed the base branch from master to dev July 9, 2026 04:55
@kupriyanovNik

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c89190b7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// checking. Stopgap until flutter/flutter#181033 lands; the engine always calls
// didInitializeImplicitFlutterEngine on the main thread.
@objc class AppDelegate: FlutterAppDelegate, @preconcurrency FlutterImplicitEngineDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore the preconcurrency protocol conformance

The old example kept @preconcurrency on FlutterImplicitEngineDelegate because current Flutter engines are not actor-annotated there; dropping it reintroduces the Swift 6/Xcode 26 isolation failure before the iOS example can build. In the Xcode 26 environment declared for this repo, keep the protocol conformance as @preconcurrency FlutterImplicitEngineDelegate so didInitializeImplicitFlutterEngine can satisfy the requirement.

Useful? React with 👍 / 👎.

userId = trimmed;
notifyListeners();

await reloadProfile();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh the flow after changing users

When a user logs in, the SDK identity changes but the controller keeps using the flow fetched during initialization for the previous anonymous/user profile. For placements whose audience or Flow differs by customer user id, the premium recipes will keep presenting the stale Flow until the app restarts; reload or invalidate flow after identify (and similarly after logout) before presenting paywalls.

Useful? React with 👍 / 👎.

@kupriyanovNik

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c288acc52f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +132 to +134
await _adapty.identify(trimmed);
await _userManager.login(trimmed);
userId = trimmed;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear identity state before switching users

When this identity switch succeeds but either subsequent refresh fails, for example offline or a server error from getProfile/getFlow, profile and flow still contain the previous identity's objects because the refresh helpers only overwrite on success. That leaves isPremiumUser and premium paywall presentation using the old user's state after login, and the same pattern exists in logout; clear those identity-scoped fields before switching or before refreshing. The fresh evidence is that the reload calls are present now, but their failure path never invalidates the old values.

Useful? React with 👍 / 👎.

@x401om

x401om commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The new Recipes app is a clear improvement over the old example — the structure is much easier to follow. A few things to resolve before it goes in.

Blocking

1. Identity-scoped state survives a failed refresh

lib/app/app_controller.dart:122 (login) and :145 (logout) switch identity and then call reloadProfile() / loadFlow(). Both helpers assign only on success — _applyProfile(await _adapty.getProfile(), notify: false) and flow = await _adapty.getFlow(...) are inside try, and catch only sets errorMessage.

So when identify succeeds but the refresh fails (offline, backend error), userId is already the new user while profile and flow still belong to the previous one. isPremiumUser then reads the previous user's access level and premium recipes stay unlocked after login; logout has the same hole. Clearing profile and flow before switching identity closes both paths.

Should fix

2. The example silently left Swift 6 language mode

The project the PR deletes sets SWIFT_VERSION = 6.0; the new example/AdaptyRecipes-Flutter/ios/Runner.xcodeproj sets SWIFT_VERSION = 5.0 in all six configurations. That is also why the missing @preconcurrency on FlutterImplicitEngineDelegate (ios/Runner/AppDelegate.swift:5) does not surface: under Swift 5 the conformance isolation problem is not an error.

I checked both halves of this on Xcode 26.5 / Flutter 3.41.8. The branch builds as-is:

$ flutter build ios --simulator --debug
✓ Built build/ios/iphonesimulator/Runner.app

Flipping only SWIFT_VERSION to 6.0, with no other change, reproduces exactly the failure that the dropped comment in dev describes:

ConformanceIsolation (Xcode): Conformance of 'AppDelegate' to protocol
'FlutterImplicitEngineDelegate' crosses into main actor-isolated code and can cause data races
ios/Runner/AppDelegate.swift:4:45

So the Codex P1 from Jul 9 is not accurate as written — nothing is broken for the example as configured — but the regression underneath it is real and one level up: the example used to compile under Swift 6, deliberately (the @preconcurrency workaround carried a comment pointing at flutter/flutter#181033), and now it doesn't. Restoring SWIFT_VERSION = 6.0 in the Runner target together with the annotation and its comment keeps the example honest about strict concurrency. Deciding to keep the example on Swift 5 is defensible too, but it should be a deliberate call rather than a side effect of flutter create, and then that Codex thread can be closed as not applicable.

3. errorMessage is cleared by whichever helper runs second

reloadProfile() (:90) and loadFlow() (:109) both start with errorMessage = null, and they are always invoked as a pair (:72-73, :137-138, :159-160). If the profile reload fails and the flow load then succeeds, the error is silently dropped and the user sees nothing. Setting the message at the call site rather than resetting it inside each helper would avoid this.

4. Drop the extra directory level under example/

Everything moved into example/AdaptyRecipes-Flutter/, so example/ now holds a single subdirectory and no README of its own (example/README.md was deleted). That costs us two things:

  • pub.dev resolves a package's example from fixed paths under example/ (example/lib/main.dart, example/pubspec.yaml, example/README.md). With everything one level deeper, none of them match and the Example tab on pub.dev goes empty.
  • cd example && flutter run no longer works — you have to know the subdirectory name.

Since we aren't planning a second example app, there's nothing for the extra level to organise: moving the app's contents up into example/ directly restores both, and the app's own README lands at example/README.md where pub.dev expects it. It also drops the -Flutter suffix, which is redundant in the Flutter SDK repo — the path and name: adapty_recipes_flutter currently repeat it.

CI

The new Resolve example package step works around the root flutter analyze walking into the example, which resolves its package: imports against the root package_config. Scoping the root analysis to the package instead — flutter analyze lib — removes the need for the extra pub get and keeps example code out of the root run. Applies either way, independently of the directory layout above. Not a blocker, just lighter.

Sync with dev

The branch is 34 commits behind dev and currently conflicting. Three of the four overlapping paths are files this PR deletes (example/lib/purchase_observer.dart, example/lib/screens/main_screen.dart, example/lib/screens/paywalls_list_screen.dart), so those are delete/modify conflicts; AGENTS.md needs a real merge. Worth confirming that example-facing fixes landed in dev since the fork point are reflected in the new app — in particular driving the flow locale from the view creation step, and the getFlow locale warning.

Nit

AppConstants.isConfiguredValue accepts any value that doesn't start with YOUR_, so a truncated or mistyped key still reaches activate() and surfaces as an SDK error instead of the configuration screen. Checking for the public_live_ prefix would catch that case.

@kupriyanovNik

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: e967c56f34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kupriyanovNik

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e967c56f34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return;
}

configurationInvalid = !AppConstants.debugAssertValidConfiguration();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check placeholders before invoking the debug assertion

When the repository's placeholder configuration is used with the README's default debug flutter run, debugAssertValidConfiguration() throws before configurationInvalid is assigned or this early-return can run. Because initState discards the initialize() future, startup reports an unhandled asynchronous Flutter error even though the app has a dedicated configuration fallback screen; check hasValidConfiguration without asserting on this expected state.

Useful? React with 👍 / 👎.

Comment on lines +88 to +89
} catch (error) {
_setErrorIfOwned(errorOperation, _messageFor(error));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Provide a retry path after initialization failures

When currentUserId, isActivated, or activate fails transiently, this catch leaves isInitialized false and only publishes an error. The recipes screen offers only banner dismissal, while profile actions are disabled through canUseSdk, and nothing calls initialize() again, so the demo remains unusable until the process is restarted; expose an initialization retry or retry automatically.

Useful? React with 👍 / 👎.

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