Skip to content

Commit 5e1b524

Browse files
authored
feat: support record screen view event manually (#50)
1 parent 16328f2 commit 5e1b524

File tree

12 files changed

+264
-30
lines changed

12 files changed

+264
-30
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,24 @@ let item_book: ClickstreamAttribute = [
165165
ClickstreamAnalytics.recordEvent("view_item", attributes, [item_book])
166166
```
167167

168+
#### Record Screen View events manually
169+
170+
By default, SDK will automatically track the preset `_screen_view` event when ViewController triggers `viewDidAppear`.
171+
172+
You can also manually record screen view events whether or not automatic screen view tracking is enabled, add the following code to record a screen view event with two attributes.
173+
174+
* `SCREEN_NAME` Required. Your screen's name.
175+
* `SCREEN_UNIQUE_ID` Optional. Set the hashValue of your ViewController or UIView. If you do not set, SDK will set a default value based on the current ViewController's hashValue.
176+
177+
```swift
178+
import Clickstream
179+
180+
ClickstreamAnalytics.recordEvent(ClickstreamAnalytics.EventName.SCREEN_VIEW, [
181+
ClickstreamAnalytics.Attr.SCREEN_NAME: "HomeView",
182+
ClickstreamAnalytics.Attr.SCREEN_UNIQUE_ID: homeView.hashValue
183+
])
184+
```
185+
168186
#### Add global attribute
169187

170188
```swift

Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ extension AWSClickstreamPlugin {
4545
if let attributes = event.attribute {
4646
clickstreamEvent.addAttribute(attributes)
4747
}
48+
if event.name == Event.PresetEvent.SCREEN_VIEW {
49+
clickstream.sessionClient.onManualScreenView(clickstreamEvent)
50+
return
51+
}
4852
if let items = event.items {
4953
clickstreamEvent.addItems(items)
5054
}
5155

5256
Task {
5357
do {
54-
try await analyticsClient.record(clickstreamEvent)
58+
try analyticsClient.record(clickstreamEvent)
5559
} catch {
5660
log.error("Record event error:\(error)")
5761
}

Sources/Clickstream/ClickstreamAnalytics.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ public enum ClickstreamAnalytics {
8989
Amplify.Analytics.enable()
9090
}
9191

92+
/// ClickstreamAnalytics preset events
93+
public enum EventName {
94+
public static let SCREEN_VIEW = "_screen_view"
95+
}
96+
97+
/// ClickstreamANalytics preset attributes
98+
public enum Attr {
99+
public static let SCREEN_NAME = "_screen_name"
100+
public static let SCREEN_UNIQUE_ID = "_screen_unique_id"
101+
}
102+
92103
/// ClickstreamAnalytics preset item attributes
93104
/// In addition to the item attributes defined below, you can add up to 10 custom attributes to an item.
94105
public enum Item {

Sources/Clickstream/ClickstreamObjc.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,20 @@ import Foundation
108108
}
109109
}
110110

111+
/// ClickstreamAnalytics preset events
112+
@objcMembers public class EventName: NSObject {
113+
/// Preset event screen view
114+
public static let SCREEN_VIEW = "_screen_view"
115+
}
116+
117+
/// ClickstreamANalytics preset attributes
118+
@objcMembers public class Attr: NSObject {
119+
/// Preset attribute screen name
120+
public static let SCREEN_NAME = "_screen_name"
121+
/// Preset attribute screen unique id
122+
public static let SCREEN_UNIQUE_ID = "_screen_unique_id"
123+
}
124+
111125
/// ClickstreamAnalytics preset item keys for objective-c
112126
/// In addition to the item attributes defined below, you can add up to 10 custom attributes to an item.
113127
@objcMembers public class ClickstreamItemKey: NSObject {

Sources/Clickstream/Dependency/Clickstream/Analytics/AnalyticsClient.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protocol AnalyticsClientBehaviour {
1717

1818
func checkEventName(_ eventName: String) -> Bool
1919
func createEvent(withEventType eventType: String) -> ClickstreamEvent
20-
func record(_ event: ClickstreamEvent) async throws
20+
func record(_ event: ClickstreamEvent) throws
2121
func submitEvents(isBackgroundMode: Bool)
2222
}
2323

@@ -131,14 +131,16 @@ class AnalyticsClient: AnalyticsClientBehaviour {
131131
return true
132132
}
133133

134-
func record(_ event: ClickstreamEvent) async throws {
134+
func record(_ event: ClickstreamEvent) throws {
135135
for (key, attribute) in globalAttributes {
136136
event.addGlobalAttribute(attribute, forKey: key)
137137
}
138138
if let autoRecordClient {
139-
if autoRecordClient.lastScreenName != nil, autoRecordClient.lastScreenUniqueId != nil {
139+
if autoRecordClient.lastScreenName != nil {
140140
event.addGlobalAttribute(autoRecordClient.lastScreenName!,
141141
forKey: Event.ReservedAttribute.SCREEN_NAME)
142+
}
143+
if autoRecordClient.lastScreenUniqueId != nil {
142144
event.addGlobalAttribute(autoRecordClient.lastScreenUniqueId!,
143145
forKey: Event.ReservedAttribute.SCREEN_UNIQUEID)
144146
}
@@ -157,7 +159,7 @@ class AnalyticsClient: AnalyticsClientBehaviour {
157159
let event = createEvent(withEventType: Event.PresetEvent.CLICKSTREAM_ERROR)
158160
event.addAttribute(eventError.errorCode, forKey: Event.ReservedAttribute.ERROR_CODE)
159161
event.addAttribute(eventError.errorMessage, forKey: Event.ReservedAttribute.ERROR_MESSAGE)
160-
try await record(event)
162+
try record(event)
161163
} catch {
162164
log.error("Failed to record event with error:\(error)")
163165
}

Sources/Clickstream/Dependency/Clickstream/AutoRecord/AutoRecordEventClient.swift

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,60 @@ class AutoRecordEventClient {
3636

3737
func onViewDidAppear(screenName: String, screenPath: String, screenHashValue: String) {
3838
if !clickstream.isEnable { return }
39-
if !isSameScreen(screenName, screenPath, screenHashValue) {
39+
if !isSameScreen(screenName, screenHashValue) {
4040
if lastScreenName != nil {
4141
recordUserEngagement()
4242
}
43-
recordScreenView(screenName, screenPath, screenHashValue)
43+
recordViewScreenAutomatically(screenName, screenPath, screenHashValue)
4444
}
4545
}
4646

47-
func recordScreenView(_ screenName: String, _ screenPath: String, _ screenUniqueId: String) {
47+
func recordViewScreenAutomatically(_ screenName: String, _ screenPath: String, _ screenUniqueId: String) {
4848
if !clickstream.configuration.isTrackScreenViewEvents {
4949
return
5050
}
5151
let event = clickstream.analyticsClient.createEvent(withEventType: Event.PresetEvent.SCREEN_VIEW)
52+
recordScreenViewEvent(event, screenName, screenPath, screenUniqueId)
53+
}
54+
55+
func recordViewScreenManually(_ event: ClickstreamEvent) {
56+
if let screenName = event.attribute(forKey: Event.ReservedAttribute.SCREEN_NAME) as? String {
57+
var screenUniqueId = event.attribute(forKey: Event.ReservedAttribute.SCREEN_UNIQUEID) as? String
58+
if screenUniqueId == nil {
59+
screenUniqueId = lastScreenUniqueId
60+
}
61+
if isSameScreen(screenName, screenUniqueId) {
62+
return
63+
}
64+
if lastScreenName != nil {
65+
recordUserEngagement()
66+
}
67+
recordScreenViewEvent(event, screenName, lastScreenPath, screenUniqueId)
68+
} else {
69+
let errorEvent = clickstream.analyticsClient.createEvent(withEventType:
70+
Event.PresetEvent.CLICKSTREAM_ERROR)
71+
errorEvent.addAttribute(Event.ErrorCode.SCREEN_VIEW_MISSING_SCREEN_NAME,
72+
forKey: Event.ReservedAttribute.ERROR_CODE)
73+
errorEvent.addAttribute("record an screen view event without the required screen name attribute",
74+
forKey: Event.ReservedAttribute.ERROR_MESSAGE)
75+
recordEvent(errorEvent)
76+
}
77+
}
78+
79+
func recordScreenViewEvent(_ event: ClickstreamEvent, _ screenName: String,
80+
_ screenPath: String?, _ screenUniqueId: String?)
81+
{
5282
let eventTimestamp = event.timestamp
53-
event.addAttribute(screenPath, forKey: Event.ReservedAttribute.SCREEN_ID)
54-
if lastScreenName != nil, lastScreenPath != nil, lastScreenUniqueId != nil {
83+
if screenPath != nil {
84+
event.addAttribute(screenPath!, forKey: Event.ReservedAttribute.SCREEN_ID)
85+
}
86+
if lastScreenName != nil {
5587
event.addAttribute(lastScreenName!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_NAME)
88+
}
89+
if lastScreenPath != nil {
5690
event.addAttribute(lastScreenPath!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_ID)
91+
}
92+
if lastScreenUniqueId != nil {
5793
event.addAttribute(lastScreenUniqueId!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_UNIQUEID)
5894
}
5995
let previousTimestamp = getPreviousScreenViewTimestamp()
@@ -64,12 +100,11 @@ class AutoRecordEventClient {
64100
if lastEngageTime > 0 {
65101
event.addAttribute(lastEngageTime, forKey: Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP)
66102
}
67-
recordEvent(event)
68-
69-
isEntrances = false
70103
lastScreenName = screenName
71104
lastScreenPath = screenPath
72105
lastScreenUniqueId = screenUniqueId
106+
recordEvent(event)
107+
isEntrances = false
73108
lastScreenStartTimestamp = eventTimestamp
74109
UserDefaultsUtil.savePreviousScreenViewTimestamp(storage: clickstream.storage, timestamp: eventTimestamp)
75110
}
@@ -92,11 +127,9 @@ class AutoRecordEventClient {
92127
UserDefaultsUtil.getPreviousScreenViewTimestamp(storage: clickstream.storage)
93128
}
94129

95-
func isSameScreen(_ screenName: String, _ screenPath: String, _ screenUniqueId: String) -> Bool {
130+
func isSameScreen(_ screenName: String, _ screenUniqueId: String?) -> Bool {
96131
lastScreenName != nil
97-
&& lastScreenPath != nil
98132
&& screenName == lastScreenName
99-
&& screenPath == lastScreenPath
100133
&& screenUniqueId == lastScreenUniqueId
101134
}
102135

@@ -182,12 +215,10 @@ class AutoRecordEventClient {
182215
}
183216

184217
func recordEvent(_ event: ClickstreamEvent) {
185-
Task {
186-
do {
187-
try await clickstream.analyticsClient.record(event)
188-
} catch {
189-
log.error("Failed to record event with error:\(error)")
190-
}
218+
do {
219+
try clickstream.analyticsClient.record(event)
220+
} catch {
221+
log.error("Failed to record event with error:\(error)")
191222
}
192223
}
193224
}

Sources/Clickstream/Dependency/Clickstream/Event/Event.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,6 @@ enum Event {
102102
static let ITEM_CUSTOM_ATTRIBUTE_SIZE_EXCEED = 4_003
103103
static let ITEM_CUSTOM_ATTRIBUTE_KEY_LENGTH_EXCEED = 4_004
104104
static let ITEM_CUSTOM_ATTRIBUTE_KEY_INVALID = 4_005
105+
static let SCREEN_VIEW_MISSING_SCREEN_NAME = 5_001
105106
}
106107
}

Sources/Clickstream/Dependency/Clickstream/Session/SessionClient.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ protocol SessionClientBehaviour: AnyObject {
1212
func startActivityTracking()
1313
func initialSession()
1414
func storeSession()
15+
func onManualScreenView(_ event: ClickstreamEvent)
1516
}
1617

1718
class SessionClient: SessionClientBehaviour {
@@ -72,6 +73,10 @@ class SessionClient: SessionClientBehaviour {
7273
clickstream.analyticsClient.submitEvents(isBackgroundMode: true)
7374
}
7475

76+
func onManualScreenView(_ event: ClickstreamEvent) {
77+
autoRecordClient.recordViewScreenManually(event)
78+
}
79+
7580
private func respond(to newState: ApplicationState) {
7681
if !clickstream.isEnable { return }
7782
switch newState {

Tests/ClickstreamTests/Clickstream/AnalyticsClientTest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class AnalyticsClientTest: XCTestCase {
274274
analyticsClient.addGlobalAttribute(1, forKey: "metric_1")
275275

276276
do {
277-
try await analyticsClient.record(event)
277+
try analyticsClient.record(event)
278278
XCTAssertEqual(eventRecorder.saveCount, 1)
279279
guard let savedEvent = eventRecorder.lastSavedEvent else {
280280
XCTFail("Expected saved event")
@@ -300,7 +300,7 @@ class AnalyticsClientTest: XCTestCase {
300300
analyticsClient.addUserAttribute(1, forKey: "metric_1")
301301

302302
do {
303-
try await analyticsClient.record(event)
303+
try analyticsClient.record(event)
304304
XCTAssertEqual(eventRecorder.saveCount, 1)
305305
guard let savedEvent = eventRecorder.lastSavedEvent else {
306306
XCTFail("Expected saved event")

0 commit comments

Comments
 (0)