Skip to content

Commit

Permalink
InAppMessages load redesign
Browse files Browse the repository at this point in the history
  • Loading branch information
Ankmara authored Apr 4, 2024
1 parent 08e0efd commit 55d7962
Show file tree
Hide file tree
Showing 23 changed files with 629 additions and 427 deletions.
78 changes: 42 additions & 36 deletions Documentation/in-app-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,62 +175,68 @@ If your app is successfully requesting and receiving in-app messages but they ar

### Log Messages

> Note: All logs assigned to In-app handling process are prefixed with `[InApp]` shortcut to bring easier search-ability to you. Bear in mind that some supporting processes (such as Image caching) are logging without this prefix.
While troubleshooting in-app message issues, you can follow the process of requesting, receiving, preloading, and displaying in-app messages through the information logged by the SDK at verbose log level. Look for messages similar to the ones below:

1. ```
Attempting to show in-app message for event with types ["payment"].
Event {eventCategory}:{eventType} occurred, going to trigger In-app show process
```
In-app process has been triggered by SDK usage of identifyCustomer() or event tracking.
```
An event of type `payment` was tracked, the SDK is checking if there are any in-app messages to display on this event.
Register request for in-app message to be shown for $eventType (not for identifyCustomer). Identify customer event will always download in app messages from backend.
2. ```
Data not preloaded, saving message for later.
Picking in-app message for eventType {eventType}. {X} messages available: [{message1 name}, {message2 name}, ...].
```
In-app messages must be preloaded before they can be displayed. If the preload hasn't started or is still in progress, the SDK will wait until the preload is complete and only perform the logic to select an in-app message afterward.
```
3. ```
Created request:
[Request]
POST https://api.exponea.com/webxp/s/2c4f2d02-1dbe-11eb-844d-2a3b671acf42/inappmessages?v=1
This log contains `eventType` for which the messages going to be searched. Then count of `X` messages and the names of **all** messages received from the server is listed in
```
Message '{message name}' failed event filter. Message filter: {"event_type":"session_start","filter":[]} Event type: payment properties: {price=2011.1, product_title=Item #1} timestamp: 1.59921557821E9
```
The SDK requested in-app messages from the Engagement platform.
3. ```
Response received:
[Response]
HTTP 200 https://api.exponea.com/webxp/s/2c4f2d02-1dbe-11eb-844d-2a3b671acf42/inappmessages?v=1
We show reasons why some messages are not picked. In this example, message failed event filter - the type was set for `session_start`, but `payment` was tracked.
```
4. ```
Got {X} messages with highest priority for eventType {eventType}. [{message1 name}, {message2 name}, ...]
```
The SDK received in-app messages from the Engagement platform. You should see `Response Body:` followed by the in-app messages data in JSON format a few lines below the above message:
There may be a tie between a few messages with the same priority. All messages with same highest priority are listed.
```
Response Body:
{"success":true,"data":[{"id":"65baf899dd467ee54357e371","name":"Payment in-app message","date_filter":{"enabled":false},"frequency":"until_visitor_interacts","load_priority":null,"load_delay":null,"close_timeout":null,"trigger":{"event_type": "custom_event", "filter": [], "type": "event"},"has_tracking_consent":true,"message_type":"slide_in","variant_id":0,"variant_name":"Variant A","is_html":false,"payload":{"title":"Test In-App Message","body_text":"This is an example of your in-app personalization body text.","image_url":"https://asset-templates.exponea.com/misc/media/canyon/canyon.jpg","title_text_color":"#000000","title_text_size":"22px","body_text_color":"#000000","body_text_size":"14px","background_color":"#ffffff","message_position":"top","buttons":[{"button_text":"Action","button_type":"deep-link","button_link":"https://www.bloomreach.com","button_text_color":"#2dbaee","button_background_color":"#ffffff"}]}}]}
5. ```
Picking top message '{message name}' for eventType {eventType}
```
4. ```
In-app message data preloaded, picking a message to display
The single message is randomly picked from filtered messages with same highest priority for `eventType`
```
In-app messages were preloaded in the local cache. The SDK will procees with the logic to select an in-app message to display.
5. ```
6. ```
Picking in-app message for eventTypes ["payment"]. 2 messages available: ["Payment in-app message", "App load in-app message"].
```
This log message includes a list of **all** in-app messages received from the server and preloaded in the local cache. If you don't see your message here, it's possible it wasn't available yet the last time the SDK request in-app messages. If you have confirmed the message was available when the last preload occurred, the current user may not match the audience targeted by the in-app message. Check the in-app message set up in Engagement.
6. ```
Message 'App load in-app message' failed event filter. Event: [ExponeaSDK.DataType.properties(["value": ExponeaSDK.JSONValue.string("99")]), ExponeaSDK.DataType.timestamp(nil), ExponeaSDK.DataType.eventType("payment")]. Message filter: {"filter":[],"event_type":"session_start"}
```
The SDK tells you why this particular message was not selected. In this example, the message failed the event filter - the event type was set for `session_start`, but `payment` was tracked.
7. ```
7. ```
Got {X} messages available to show. [{message1 name}, {message2 name}, ...].
```
All `X` messages has been collected for registered 'show requests'. Process continues with selecting of message with highest priority.
```
8. ```
1 messages available after filtering. Picking highest priority message.
```
After applying all the filters, there is one in-app message left that satisfies the criteria to be displayed. If more than one messages is eligible, the SDK will select the one that has the highest priority configured in Engagement.
8. ```
Got 1 messages with highest priority. ["Payment in-app message"]
```
If there is more than one in-app message with the highest priority, the SDK randomly picks one.
9. ```
Attempting to show in-app message 'Payment in-app message'
```
An in-app message has been selected to be displayed in the app.
Picking top message '{message name}' to be shown.
```
The single message is randomly picked from all filtered messages. This message is going to be shown to user.
```
10. ```
Will attempt to present in-app message on main thread with delay 0.0.
```
A message display request is posted to the main thread, where it will be displayed in the top-most `presentedViewController`. If a failure happens after this point, refer to [Troubleshoot In-App Message Display Issues](#troubleshoot-in-app-message-display-issues) above for pointers.
Only logging in-app message for control group '${message.name}'
```
A/B testing In-app message or message without payload is not shown to user but 'show' event is tracked for your analysis.
```
11. ```
In-app message presented.
```
Everything went well and you should see your message. It was presented in the top-most `presentedViewController`. In case you don't see the message, it's possible that the view hierarchy changed and message is no longer on screen. Refer to [Troubleshoot In-App Message Display Issues](#troubleshoot-in-app-message-display-issues) above for details.
Attempting to show in-app message '{message name}'
```
In-app message that meant to be show to user (not A/B testing) is going to be shown
```
12. ```
Posting show to main thread with delay {X}ms.
```
Message display request is posted to the main thread with delay of `X` milliseconds. Delay is configured by `Display delay` in In-app message settings. Message will be displayed in the last resumed Activity.
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ extension ExponeaInternal {
if var ids = customerIds {
// Check for overriding cookie
if ids["cookie"] != nil {
ids.removeValue(forKey: "cookie")
Exponea.logger.log(.warning, message: """
You should never set cookie ID directly on a customer. Ignoring.
""")
}
ids["cookie"] = dependencies.trackingManager.customerIds["cookie"]
data.append(.customerIds(ids))
}

Expand Down
49 changes: 25 additions & 24 deletions ExponeaSDK/ExponeaSDK/Classes/ExponeaInternal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,8 @@ public class ExponeaInternal: ExponeaType {
repository: repository,
customerIdentifiedHandler: { [weak self] in
// reload in-app messages once customer identification is flushed - user may have been merged
guard let inAppMessagesManager = self?.inAppMessagesManager,
let trackingManager = self?.trackingManager,
guard let trackingManager = self?.trackingManager,
let inAppContentBlocksManager = self?.inAppContentBlocksManager else { return }
inAppMessagesManager.preload(for: trackingManager.customerIds)
if let placeholders = configuration.inAppContentBlocksPlaceholders {
inAppContentBlocksManager.loadInAppContentBlockMessages {
inAppContentBlocksManager.prefetchPlaceholdersWithIds(ids: placeholders)
Expand All @@ -277,33 +275,34 @@ public class ExponeaInternal: ExponeaType {
repository: repository,
database: database,
flushingManager: flushingManager,
inAppMessageManager: inAppMessagesManager,
trackManagerInitializator: { trackingManager in
let trackingConsentManager = TrackingConsentManager(
trackingManager: trackingManager
)
self.trackingConsentManager = trackingConsentManager
let inAppMessagesManager = InAppMessagesManager(
repository: repository,
displayStatusStore: InAppMessageDisplayStatusStore(userDefaults: userDefaults),
trackingConsentManager: trackingConsentManager
)
self.inAppMessagesManager = inAppMessagesManager
},
userDefaults: userDefaults,
onEventCallback: { type, event in
self.inAppMessagesManager?.onEventOccurred(of: type, for: event)
self.inAppMessagesManager?.onEventOccurred(of: type, for: event, triggerCompletion: nil)
self.appInboxManager?.onEventOccurred(of: type, for: event)
}
)

self.trackingManager = trackingManager

let trackingConsentManager = TrackingConsentManager(
trackingManager: trackingManager
)
self.trackingConsentManager = trackingConsentManager

self.appInboxManager = AppInboxManager(
repository: repository,
trackingManager: trackingManager,
database: database
)

let inAppMessagesManager = InAppMessagesManager(
repository: repository,
displayStatusStore: InAppMessageDisplayStatusStore(userDefaults: userDefaults),
trackingConsentManager: trackingConsentManager
)
self.inAppMessagesManager = inAppMessagesManager

processSavedCampaignData()
configuration.saveToUserDefaults()

Expand Down Expand Up @@ -569,14 +568,16 @@ public extension ExponeaInternal {

@objc
func openAppInboxList(sender: UIButton!) {
let window = UIApplication.shared.keyWindow
guard let topViewController = InAppMessagePresenter.getTopViewController(window: window) else {
Exponea.logger.log(.error, message: "Unable to show AppInbox list - no view controller")
return
onMain {
let window = UIApplication.shared.keyWindow
guard let topViewController = InAppMessagePresenter.getTopViewController(window: window) else {
Exponea.logger.log(.error, message: "Unable to show AppInbox list - no view controller")
return
}
let listView = Exponea.shared.appInboxProvider.getAppInboxListViewController()
let naviController = UINavigationController(rootViewController: listView)
naviController.modalPresentationStyle = .formSheet
topViewController.present(naviController, animated: true)
}
let listView = Exponea.shared.appInboxProvider.getAppInboxListViewController()
let naviController = UINavigationController(rootViewController: listView)
naviController.modalPresentationStyle = .formSheet
topViewController.present(naviController, animated: true)
}
}
6 changes: 4 additions & 2 deletions ExponeaSDK/ExponeaSDK/Classes/Flushing/FlushingManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class FlushingManager: FlushingManagerType {
private var isFlushingData: Bool = false
/// Used for periodic data flushing.
private var flushingTimer: Timer?

private let customerIdentifiedHandler: () -> Void
internal var inAppRefreshCallback: EmptyBlock?
private var customerIdentifiedHandler: () -> Void

public var flushingMode: FlushingMode = .immediate {
didSet {
Expand Down Expand Up @@ -123,6 +123,7 @@ class FlushingManager: FlushingManagerType {
// Check if we have any data otherwise bail
guard !events.isEmpty || !customers.isEmpty else {
isFlushingData = false
inAppRefreshCallback?()
completion?(.success(0))
return
}
Expand Down Expand Up @@ -184,6 +185,7 @@ class FlushingManager: FlushingManagerType {
)
if trackingObject is CustomerTrackingObject {
customerIdentifiedHandler()
inAppRefreshCallback?()
}
try database.delete(flushableObject.databaseObjectProxy)
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protocol FlushingManagerType {

/// Returns true if event database contains data that needs to be flushed to exponea servers
func hasPendingData() -> Bool
var inAppRefreshCallback: EmptyBlock? { get set }
}

/// Result of flushing operation
Expand Down
Loading

0 comments on commit 55d7962

Please sign in to comment.