Listeners / Delegates for UI / SDK events and Custom User Attributes
This page describes how to implement listeners / delegates for UI / SDK events and Custom User Attributes
To enhance your integration and gain deeper insights into user interactions, implement the following event listeners.
While these steps are not mandatory strictly speaking to make the Purchasely SDK work, we encourage you to implement them as it only takes a few minutes of work.
UI / SDK Events Listener
When users interact with Purchasely Screens, the Purchasely SDK triggers UI / SDK events.
These events are triggered internally inside the application and sent to the Purchasely Platform, to compute all the KPIs related to conversion. However, contrary to Server events, these UI / SDK events cannot be forwarded to a 3rd party integration using a server-to-server integration, directly from the Console.
If you want to leverage these analytics and gain real-time insights on how users interact with the Screens managed by the Purchasely SDK, you need to implement an event delegate / listener, to fetch the events in the app and process them (i.e.: forward them to any analytics or engagement platform)
Implementation
UI / SDK Events are gathered by the Purchasely Platform but they cannot be routed from the Purchasely Console to the webhook or to 3rd party integrations.
If you wish to track these events in your own Analytics or in a 3rd party analytics tool, you need to set yourself an event delegate / event listener inside the app.
Listening to events
Purchasely.setEventDelegate(self)Purchasely.eventListener = eventListener// Nothing special to setup, just go to "Receiving events" below// Nothing special to setup, just go to "Receiving events" below// Nothing special to setup, just go to "Receiving events" belowThis code must be inserted after starting the SDK.
Receiving events
You will receive the events like this :
func eventTriggered(_ event: PLYEvent, properties: [String : Any]?) {
switch event {
case .linkOpened:
print("Link opened")
default:
print("Ignored")
}
}private val eventListener = object : io.purchasely.ext.EventListener {
override fun onEvent(event: PLYEvent) {
when (event) {
PLYEvent.LoginTapped -> Log.d("Purchasely", "Login tapped, we should open login page")
}
}
}Purchasely.addEventListener((event) => {
console.log('Event Name ' + event.name);
console.log(event.properties);
console.log(event);
});
//When you do not want to listen to events anymore
Purchasely.removeEventListener();Purchasely.listenToEvents((event) =>
print(event.name)
);Purchasely.addEventsListener((event) => {
console.log("Event Name " + event.name);
console.log(event.properties);
console.log(event);
});Once received, these events can be directly forwarded to your internal Analytics or 3rd-party Analytics SDK inside your app.
Web2App Redemption Delegate / Listener
When a user opens a ply/redeem/TOKEN deeplink, the Purchasely SDK redeems the web subscription. The SDK reports the outcome to your app. Your app can also draw the result screen itself, instead of the built-in alert.
This feature is available from v6.1.0 on iOS, Android, React Native, Flutter and Cordova.
Read Web-to-app funnels (redemption) for the whole funnel: the shape of the link, the delivery contract, the events and the security rules.
Functionning
The SDK calls your delegate or your listener on the main thread. It calls it exactly once for each settled redemption.
Keep appHandlesRedemptionAlert at false, the default value, to let the SDK present its own success or failure alert. The SDK then calls your app once the user acknowledges that alert. On Android the listener fires when the user dismisses the outcome alert, unless the app owns the alert. Set appHandlesRedemptionAlert to true to suppress the built-in alert. The SDK then calls your app as soon as the redemption settles, and your app must show its own result screen.
Implementation
import Purchasely
// In your AppDelegate
Purchasely
.apiKey("X-API-KEY")
.webRedemptionDelegate(self, appHandlesRedemptionAlert: false)
.start()
extension AppDelegate: PLYWebRedemptionDelegate {
func webRedemptionCompleted(result: PLYWebRedemptionResult) {
if result.isSuccess {
// Unlock the content and draw your result screen
} else {
print("Redemption failed: \(result.errorCode) \(result.errorMessage)")
}
}
}Purchasely.Builder(applicationContext)
.apiKey("X-API-KEY")
.webRedemptionListener { result ->
when (result) {
is PLYWebRedemptionResult.Success -> {
val subscription = result.context?.subscription
// Unlock the content and draw your result screen
}
is PLYWebRedemptionResult.Failure -> {
println("Redemption failed: ${result.errorCode} ${result.errorMessage}")
}
}
}
.build()
.start { error -> }await Purchasely.builder('X-API-KEY')
.webRedemptionListener((result) => {
if (result.isSuccess) {
// Unlock the content and draw your result screen
} else {
console.log(`Redemption failed: ${result.errorCode} ${result.errorMessage}`);
}
})
.stores(['google'])
.start();await Purchasely.apiKey('X-API-KEY')
.webRedemptionListener((result) {
if (result.isSuccess) {
// Unlock the content and draw your result screen
} else {
debugPrint('Redemption failed: ${result.errorCode} ${result.errorMessage}');
}
})
.stores([PLYStore.google])
.start();await Purchasely.builder('X-API-KEY')
.webRedemptionListener(function (result) {
if (result.isSuccess) {
// Unlock the content and draw your result screen
} else {
console.log('Redemption failed: ' + result.errorCode + ' ' + result.errorMessage);
}
})
.stores([Purchasely.Store.google])
.start();On iOS the protocol is PLYWebRedemptionDelegate, and it requires the single method webRedemptionCompleted(result:). The PLYWebRedemptionResult object exposes isSuccess, errorCode, errorMessage, replay and context. The context is a PLYWebRedemptionContext, and its subscription property is an optional PLYSubscription.
On Android the callback method is PLYWebRedemptionListener.onRedemptionCompleted. The PLYWebRedemptionResult class is a sealed class of Success(context, replay) and Failure(errorCode, errorMessage). The subscription property of the context is a PLYSubscriptionData, the type that Purchasely.userSubscriptions() returns.
Check both levels for null on Android. The context of a Success is null when the response of the server carries nothing to describe. The subscription of a present context is separately null when the redemption unlocks no subscription. The SDK today always builds a context on a success, but the field stays nullable for a future response shape. The errorCode and the errorMessage of a Failure are also nullable.
The replay value is true when the server reports that the token was already redeemed. It is false for a fresh redemption. The flag is a verdict of the server about the token, not an observation of the behaviour of the user. The SDK keeps no cache of the outcome, and it calls the server on every attempt.
Android also provides a two-argument form of the builder method and of the DSL method: webRedemptionListener(appHandlesRedemptionAlert = true) { result -> }. Both forms take appHandlesRedemptionAlert first, then the listener.
React Native, Flutter and Cordova give one flat result object, with the same five properties as iOS. Each bridge takes the callback first and appHandlesRedemptionAlert as an optional second argument. Register the callback in the chain, and not with the runtime function that each bridge also provides: a redemption can settle during start(), when the link is what launches the app.
Custom User Attributes Listener
The Purchasely SDK allows you to publish Surveys, gain insights on users and leverage them to personalize the journey.
When a user validates an answer to a survey, a Custom User Attribute can be set by the SDK and fetch by the app thanks to a listener / delegate.
By implementing Custom User Attribute listener, you will be able to:
- publish surveys in no-code using the Screen Composer
- automatically fetch the user data
{attribute, type, value(s)}in the app - and process it to send it to your backend or any 3 party integration
... without needing to update your app every time you publish a new survey.
This feature is available starting from the following versions:
- iOS: v5.0.2+
- Android: v5.0.4+
- React Native: v5.0.4+
Functionning
When the user submit their answer(s) to the Survey, if a user attribute has been associated to the survey, this listener / delegate will automatically be called by the SDK to handover to the app.
After the app has been notified, it can:
- fetch the data
{attribute ID, type, value(s)} - and process it
Implementation
class UserAttributeHandler: PLYUserAttributeDelegate {
func onUserAttributeSet(key: String, type: PLYUserAttributeType, value: Any?, source: PLYUserAttributeSource) {
print("onUserAttributeSet: \(key) \(type) \(String(describing: value)) \(source)")
}
func onUserAttributeRemoved(key: String, source: PLYUserAttributeSource) {
print("onUserAttributeRemoved: \(key) \(source)")
}
}
Purchasely.setUserAttributeDelegate(UserAttributeHandler())
Purchasely.userAttributeListener = object : UserAttributeListener {
override fun onUserAttributeSet(key: String, type: PLYUserAttributeType, value: Any, source: PLYUserAttributeSource) {
Log.d(TAG, "User attribute added: $key, $type, $value from $source")
}
override fun onUserAttributeRemoved(key: String, source: PLYUserAttributeSource) {
Log.d(TAG, "User attribute removed: $key")
}
}Purchasely.addUserAttributeSetListener((attribute: PurchaselyUserAttribute) => {
console.log('Attribute set:', attribute);
});
Purchasely.addUserAttributeRemovedListener(attribute => {
console.log('Attribute removed:', attribute);
});
// -- Definition of a Purchasely User Attribute -- //
export type PurchaselyUserAttribute = {
key: string;
value?: any | null;
type?: PLYUserAttributeType | null;
source?: PLYUserAttributeSource | null;
};class MyUserAttributeListener implements UserAttributeListener {
@override
void onUserAttributeSet(String key, PLYUserAttributeType type, dynamic value, PLYUserAttributeSource source){
print("Attribute set: $key, Type: $type, Value: $value, Source: $source");
}
@override
void onUserAttributeRemoved(String key, PLYUserAttributeSource source) {
print("Attribute removed: $key, Source: $source");
}
}
Purchasely.setUserAttributeListener(MyUserAttributeListener());The listener should be implemented for the following types of Custom User Attributes:
StringIntFloatBoolDateArray of Strings
Surveys allowing multiple choice answers are associated with the type Array of StringsWhen configuring your survey, you can define whether multiple answers are allowed or not.
- If the Survey is configured to allow ONE single answer, the attribute returned will be a String
- If the Survey is configured to allow MULTIPLE answers, the attribute returned will be an Array of Strings.
Once fetched, the data can be processed, which consist in sending it to your backend or any 3rd party integration directly from the app.
Understanding the PLYUserAttributeSource
PLYUserAttributeSourceThe source parameter of the listener/delegate methods indicates where the user attribute update originated. It can have two possible values:
- PURCHASELY: The change was initiated internally by the Purchasely’s SDK.
- CLIENT: The change was triggered directly by your app.
@objc public enum PLYUserAttributeSource: Int {
case purchasely, client
}enum class PLYUserAttributeSource {
PURCHASELY,
CLIENT
}export enum PLYUserAttributeSource {
PURCHASELY,
CLIENT
}enum PLYUserAttributeSource {
purchasely,
client,
}This distinction helps you understand whether the attribute change was driven automatically by Purchasely's SDK or explicitly by your app's logic.
Ignore the delegate when the parametersourceis set toclientWhen your app sets a Custom User Attribute, the listener / delegate will be called back by the SDK with the parameter
sourceset toclient.To avoid processing a data that you already have - because the app has set it in the first place - in most cases, you can ignore the event triggered when the parameter
sourceis set toclient.
Updated 8 days ago

