Release 4.x.x - #32
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
@titerman @eandreeva-twr please create a PR targeting release/4.0.0 that updates the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc1d136be5
ℹ️ 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".
@titerman @eandreeva-twr if you need you can check migration guide file at https://github.com/adaptyteam/AdaptySDK-KMP/blob/a0344f9ba2c683ae5886860a119e4877b2429a8c/MIGRATION_GUIDE_4.0.0.md |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0344f9ba2
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 209ceaacd0
ℹ️ 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".
Each embedded view now gets its own id. The id was derived from the model (AdaptyFlow.instanceIdentity / AdaptyOnboarding.id), so embedding the same flow twice produced one id for both views. The id keys the per-view observer map and the native view manager, so the second view evicted the first one's observer, events reached the wrong instance, and disposing either unregistered the other. idForNativePlatformView becomes createNativePlatformViewId(), minting a fresh id per view, and that id is threaded through the platform views as an explicit param. Mirrors React Native, which builds its view id from flow.id + useId(). Key the register/dispose effects on that id and read the callbacks through rememberUpdatedState. Both effects were keyed on Unit, so swapping the flow at the same composition position left the observer registered under the old id with the first composition's lambdas — callbacks silently stopped firing.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9ac6f520a
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e1140f8248
ℹ️ 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".
The native side defaults enable_safe_area_paddings to true, so an embedded flow under an inset-consuming parent stayed blank: the insets guard never settled. androidEnableSafeArea (Android-only) exposes the contract param. The full-screen present path keeps the default true, while the embedded views (AdaptyUIFlowPlatformView and AdaptyUI.createNativeFlowView) default to false, since the host layout is expected to handle safe area and native paddings would otherwise double up.
* Replace deprecated `capitalize()` calls with `replaceFirstChar`. * Transition from `tasks.create` to `tasks.register` for lazy task configuration. * Add `stripDebugInfo` task to remove debug symbols from the Swift bridge static library using `strip -S`. * Improve task descriptions and refine the execution order to build, dedup, and then strip debug info.
…ll request #38 from adaptyteam/bugfix/open-issues Fix log level handling and strip debug info from iOS bridge
Breaking: - `Adapty.updateAttribution(attribution, source)` is now `updateExternalAttribution(attribution, provider)`, sending `update_external_attribution_data`. - `AdaptyPaywallProductSubscription` is now `AdaptyProductSubscription`, following the `AdaptyProduct.Subscription` schema rename now that promoted products share the type. - Paywall product requests send the offer identifier nested under `subscription.offer.offer_identifier` instead of `subscription_offer_identifier`. - `createFlowView`, `createNativeFlowView` and `AdaptyUIFlowPlatformView` take a new optional `customLayoutId`. Added: - `Adapty.makePromotedPurchase` plus `AdaptyPromotedProduct` and `OnPromotedPurchaseListener`, fed by the `did_receive_promoted_purchase` event. Without a listener the native SDK completes the purchase itself. - `AdaptyConfig.Builder.withAdaptyAttributionEnabled`. Adapty Attribution is now opt-in: installation details are not collected, and `OnInstallationDetailsListener` does not fire, until it is enabled. - `AdaptyFlow.hasViewConfiguration`, mirroring the iOS property. - `custom_layout_id` on `adapty_ui_create_flow_view`. Versions: cross_platform 4.0.2 -> 4.1.2, adapty-bom 4.1.0, crossplatform 4.1.2, iOS SPM 4.1.1.
…ndling - Add `AdaptyExternalAttributionProvider`, an open value type carrying the raw provider id with the six known providers as constants, so ids added by the backend after this release round-trip without an SDK update. Mirrors the iOS entity of the same name. - Rename `AdaptyProfile.appliedAttributionSources` to `appliedExternalAttributionProviders` and type it with the new entity, and take the entity instead of a `String` in `updateExternalAttribution`, matching iOS. The wire field stays `applied_attribution_sources`; the 4.1.2 schema does not rename it. - Guard `makePromotedPurchase` with `isAndroidPlatform`, returning `DEVELOPER_ERROR` like the other iOS-only methods. - Log instead of silently dropping a promoted purchase that arrives with no `OnPromotedPurchaseListener` registered. The native SDK completes the purchase itself only when no delegate is set, and the plugin layer always installs one (`EventHandler.register`), so that fallback never applies here and the KDoc claiming otherwise was wrong. Without a listener the user tapped Buy on the App Store page and nothing happened. - Bump the iOS SPM pin to 4.1.2,
- Hold a promoted purchase that arrives before setOnPromotedPurchaseListener and replay it on registration, instead of dropping it. The collector starts in AdaptyImpl.init, and a promoted purchase cold-launches the app, so the intent normally lands between activate and registration — the drop was on the common path, not an edge case. Only the latest is kept, and it is delivered exactly once, on the main scope like a live one. - Log the held purchase through ConsoleLogger rather than `logger`. applyLogLevel is a whitelist, not a minimum level: VERBOSE and DEBUG get ConsoleLogger and everything else gets EmptyLogger, and AdaptyConfig defaults to INFO, so the warning was invisible on a default setup. AdaptyLogger has no severity to raise. - Trim the raw value in AdaptyExternalAttributionProvider, matching iOS, Flutter and Unity, so an id with stray whitespace reaches the backend the same way from every platform. This means it can no longer be a data class, since a data class cannot normalize a constructor val; equals, hashCode and toString are explicit again, as on iOS. - Annotate AdaptyFlow with @ConsistentCopyVisibility so its generated copy matches its internal constructor. copy carried AdaptyFlowLayoutsConfigurationRequestResponse in its signature, putting an internal wire model's name in the BCV dumps, where renaming it on a future cross_platform bump would read as a public API change. No consumer could reach it — Kotlin callers cannot name an internal type. The replay is covered by a regression test that fails without the retain.
…42) Custom image/video assets passed via Res.getUri(...) arrive as file:// URLs, which native could not load: - iOS builds URL(filePath:) from the string, so a file:// prefix breaks it. - Android's transformFileLocation forced every path through FileLocation.fromAsset(...), so real on-disk files were looked up as APK assets and failed. Strip the file:// scheme to a plain path before sending to native, while preserving file:///android_asset/... so Android can still map it to an APK asset. Route Android's transformFileLocation by form: android_asset -> fromAsset, file://|/ -> fromFileUri, else -> fromAsset. Example app: pass locale into createFlowView for the custom paywall present, and supply local video custom assets on the presented and native-view paths.
…42) (#43) Custom image/video assets passed via Res.getUri(...) arrive as file:// URLs, which native could not load: - iOS builds URL(filePath:) from the string, so a file:// prefix breaks it. - Android's transformFileLocation forced every path through FileLocation.fromAsset(...), so real on-disk files were looked up as APK assets and failed. Strip the file:// scheme to a plain path before sending to native, while preserving file:///android_asset/... so Android can still map it to an APK asset. Route Android's transformFileLocation by form: android_asset -> fromAsset, file://|/ -> fromFileUri, else -> fromAsset. Example app: pass locale into createFlowView for the custom paywall present, and supply local video custom assets on the presented and native-view paths.
KMPAdapty 4.0.0 (Paywall → Flow)
4.0.0 reorganizes the SDK around flows instead of paywalls.
The paywall API is deleted, not deprecated — your code won't compile until you port it. The substitutions are mechanical, and this guide lists them all.
TL;DR
getPaywall→getFlow,AdaptyPaywall→AdaptyFlow,AdaptyUIPaywallView→AdaptyUIFlowView,paywallView*callbacks →flowView*.@Deprecated.What is a flow?
A flow groups one or more paywall variations with flow-level metadata and per-language remote configs. Where
getPaywall()returned one paywall,getFlow()returns anAdaptyFlowwhosepaywallsareAdaptyFlowPaywalls.Installation
4.0.0 is a pre-release, so pin the exact version — Gradle does not resolve pre-releases through dynamic ranges (
+,latest.release):Core API (
Adapty)getPaywall(placementId, locale, fetchPolicy, loadTimeout)getFlow(placementId, fetchPolicy, loadTimeout)getPaywallForDefaultAudience(placementId, locale, fetchPolicy)getFlowForDefaultAudience(placementId, fetchPolicy)getPaywallProducts(paywall)getPaywallProducts(flow)logShowPaywall(paywall)logShowFlow(flow)createWebPaywallUrl(paywall)createWebPaywallUrl(flowPaywall)orcreateWebPaywallUrl(product)openWebPaywall(paywall, openIn)openWebPaywall(flowPaywall, openIn)oropenWebPaywall(product, openIn)localeis gone from flow fetching — it's resolved at render time. Delete the argument.getPaywallProducts,createWebPaywallUrlandopenWebPaywallkeep their paywall names: those are the v4 wire names.Purchases, profile, and fallbacks (
makePurchase,restorePurchases,getProfile,identify,updateProfile,setFallback) are unchanged.UI API (
AdaptyUI)createPaywallView(paywall, …)→AdaptyUIPaywallViewcreateFlowView(flow, …)→AdaptyUIFlowViewpresentPaywallView/dismissPaywallViewpresentFlowView/dismissFlowViewsetPaywallsEventsObserversetFlowsEventsObserverregister/unregisterPaywallEventsListenerregister/unregisterFlowEventsListenerAdaptyUI.createNativePaywallView(…)→AdaptyNativePaywallViewAdaptyUI.createNativeFlowView(…)→AdaptyNativeFlowViewAdaptyUIPaywallPlatformView(paywall, …)AdaptyUIFlowPlatformView(flow, …)AdaptyUI.requestAppReview(),AdaptyUI.openWebUrl(url, openIn)(new)Porting your observer
AdaptyUIPaywallsEventsObserveris deleted; implementAdaptyUIFlowsEventsObserverinstead. Each callback has a direct equivalent:paywallView*becomesflowView*and takesAdaptyUIFlowView. One is named differently:paywallViewDidFailRendering→flowViewDidReceiveError(widened to any flow error, not just rendering)One event is new:
flowViewDidReceiveAnalyticEvent(view, name, paramsJsonString). It defaults to a no-op.Same for
AdaptyUIFlowPlatformView: youronDid…params carry over,onDidFailRenderingbecomesonDidReceiveError, andonDidReceiveAnalyticEventis added.Default behavior changes
These don't cause compile errors, so check them at runtime:
flowViewDidPerformActionto restoreTo keep the old close-on-purchase behavior:
Per-view observers (from
registerFlowEventsListenerorAdaptyUIFlowPlatformView) run in addition to the global observer, not instead of it — your callback observes an event, it doesn't replace the default.Optional: system requests and observer mode
New in 4.0.0, with no v3 equivalent — skip unless you need them. Flows can ask your app for an OS permission or an in-app review, and observer mode can hand purchases to your app from inside a flow view. Both are registered globally, because each is a request the flow waits on an answer for.
If you register a handler you must answer:
handlePermissionand both resolver methods are abstract. With no handler registered, a permission request resolves as denied when the flow tears down, and observer mode does nothing when the user taps buy.onStartPurchase/onFinishPurchaseonly drive the loading indicator — report the transaction to Adapty yourself.Models
AdaptyFlow.paywalls— the flow'sAdaptyFlowPaywallvariations.AdaptyUIPermissionwraps the raw permission id, with the known ids as constants (AdaptyUIPermission.PUSH). Unknown, platform-specific (Androidphone/sms) and future ids arrive verbatim viapermission.value, so match with anelsebranch.AdaptyUIPermissionResult.granted(detail)/.denied(detail)— whathandlePermissionreturns.AdaptyConfig.ServerCluster.CN— new, alongsideDEFAULTandEU.Native embedded views
Deprecated onboarding
The whole onboarding surface is now
@Deprecated— methods (getOnboarding,getOnboardingForDefaultAudience,createOnboardingView,presentOnboardingView,dismissOnboardingView,createNativeOnboardingView,setOnboardingsEventsObserver,register/unregisterOnboardingEventsListener), the observer (AdaptyUIOnboardingsEventsObserver), the composable (AdaptyUIOnboardingPlatformView), and the models (AdaptyOnboarding,AdaptyUIOnboardingView,AdaptyUIOnboardingMeta,AdaptyOnboardingsAnalyticsEvent,AdaptyOnboardingsInput,AdaptyOnboardingsStateUpdatedParams,AdaptyNativeOnboardingView).Everything still works unchanged this release; a future major migrates onboardings into the Flow Builder. Suppress the warnings for now — no code change needed.