Skip to content

Commit ba2e4b5

Browse files
zhu-xiaoweixiaoweii
andauthored
feat: support sending events when app move to background (#21)
Co-authored-by: xiaoweii <[email protected]>
1 parent 3dfe309 commit ba2e4b5

File tree

9 files changed

+64
-20
lines changed

9 files changed

+64
-20
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ do {
108108
configuration.sessionTimeoutDuration = 1800000
109109
configuration.isTrackScreenViewEvents = true
110110
configuration.isLogEvents = true
111-
configuration.isCompressEvents = true
112-
configuration.isLogEvents = true
111+
configuration.isCompressEvents = true
113112
} catch {
114113
print("Failed to config ClickstreamAnalytics: \(error)")
115114
}

Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ extension AWSClickstreamPlugin {
7979
log.error("Device is offline, skipping submitting events to Clickstream server")
8080
return
8181
}
82-
analyticsClient.submitEvents()
82+
analyticsClient.submitEvents(isBackgroundMode: false)
8383
}
8484

8585
func getEscapeHatch() -> ClickstreamContext {

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

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

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

2323
typealias SessionProvider = () -> Session?
@@ -124,7 +124,7 @@ class AnalyticsClient: AnalyticsClientBehaviour {
124124
try eventRecorder.save(event)
125125
}
126126

127-
func submitEvents() {
128-
eventRecorder.submitEvents()
127+
func submitEvents(isBackgroundMode: Bool = false) {
128+
eventRecorder.submitEvents(isBackgroundMode: isBackgroundMode)
129129
}
130130
}

Sources/Clickstream/Dependency/Clickstream/Analytics/EventRecorder.swift

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
import Amplify
99
import Foundation
10+
#if canImport(UIKit)
11+
import UIKit
12+
#endif
1013

1114
/// AnalyticsEventRecording saves and submits clickstream events
1215
protocol AnalyticsEventRecording {
@@ -15,8 +18,8 @@ protocol AnalyticsEventRecording {
1518
func save(_ event: ClickstreamEvent) throws
1619

1720
/// Submit locally stored events
18-
/// - Returns: A collection of events submitted to Clickstream
19-
func submitEvents()
21+
/// - Parameter isBackgroundMode: whether use background mode to send request
22+
func submitEvents(isBackgroundMode: Bool)
2023
}
2124

2225
/// An AnalyticsEventRecording implementation that stores and submits clickstream events
@@ -46,8 +49,7 @@ class EventRecorder: AnalyticsEventRecording {
4649
try dbUtil.saveEvent(storageEvent)
4750
if clickstream.configuration.isLogEvents {
4851
setLogLevel(logLevel: LogLevel.debug)
49-
log.debug("saved event: \(event.eventType)")
50-
log.debug(eventJson)
52+
logEventPrettier(event: event)
5153
}
5254
while try dbUtil.getTotalSize() > Constants.maxDbSize {
5355
let events = try dbUtil.getEventsWith(limit: 5)
@@ -61,10 +63,20 @@ class EventRecorder: AnalyticsEventRecording {
6163
}
6264

6365
/// submit an batch events, add the processEvent() as operation into queue
64-
func submitEvents() {
66+
func submitEvents(isBackgroundMode: Bool = false) {
6567
if queue.operationCount < Constants.maxEventOperations {
6668
let operation = BlockOperation { [weak self] in
67-
_ = self?.processEvent()
69+
if isBackgroundMode {
70+
#if canImport(UIKit)
71+
let taskId = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
72+
self?.log.debug("Start background task")
73+
_ = self?.processEvent()
74+
UIApplication.shared.endBackgroundTask(taskId)
75+
self?.log.debug("Background task is the end")
76+
#endif
77+
} else {
78+
_ = self?.processEvent()
79+
}
6880
}
6981
queue.addOperation(operation)
7082
} else {
@@ -106,7 +118,6 @@ class EventRecorder: AnalyticsEventRecording {
106118
} catch {
107119
log.error("Failed to send event:\(error)")
108120
}
109-
log.info("Send \(totalEventSend) events in one submit")
110121
return totalEventSend
111122
}
112123

@@ -133,6 +144,15 @@ class EventRecorder: AnalyticsEventRecording {
133144
}
134145
return BatchEvent(eventsJson: eventsJson, eventCount: eventCount, lastEventId: lastEventId)
135146
}
147+
148+
func logEventPrettier(event: ClickstreamEvent) {
149+
var attributesStr = "attributes:{\n"
150+
for (key, value) in event.attributes {
151+
attributesStr += " \"\(key)\": \(value)\n"
152+
}
153+
attributesStr += "}"
154+
log.debug("Event saved, event name: \(event.eventType)\n\(attributesStr)")
155+
}
136156
}
137157

138158
extension EventRecorder: ClickstreamLogger {}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class AutoRecordEventClient {
9393
}
9494
recordEvent(event)
9595
}
96+
clickstream.analyticsClient.submitEvents(isBackgroundMode: true)
9697
}
9798

9899
func updateEngageTimestamp() {

Tests/ClickstreamTests/Clickstream/EventRecorderTest.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class EventRecorderTest: XCTestCase {
2020
var eventRecorder: EventRecorder!
2121
var clickstream: ClickstreamContext!
2222
var server: HttpServer!
23+
var activityTracker: MockActivityTracker!
2324

2425
override func setUp() async throws {
2526
do {
@@ -52,6 +53,23 @@ class EventRecorderTest: XCTestCase {
5253
netWorkType: NetWorkType.Wifi)
5354
eventRecorder = try! EventRecorder(clickstream: clickstream)
5455
dbUtil = eventRecorder.dbUtil
56+
57+
activityTracker = MockActivityTracker()
58+
let sessionClient = SessionClient(activityTracker: activityTracker, clickstream: clickstream)
59+
clickstream.sessionClient = sessionClient
60+
let sessionProvider: () -> Session? = { [weak sessionClient] in
61+
guard let sessionClient else {
62+
fatalError("SessionClient was deallocated")
63+
}
64+
return sessionClient.getCurrentSession()
65+
}
66+
let analyticsClient = try AnalyticsClient(
67+
clickstream: clickstream,
68+
eventRecorder: eventRecorder,
69+
sessionProvider: sessionProvider
70+
)
71+
clickstream.analyticsClient = analyticsClient
72+
clickstream.networkMonitor = MockNetworkMonitor()
5573
} catch {
5674
XCTFail("Fail to setup EventRecorder error:\(error)")
5775
}
@@ -342,7 +360,7 @@ class EventRecorderTest: XCTestCase {
342360

343361
eventRecorder.submitEvents()
344362
XCTAssertEqual(1, eventRecorder.queue.operationCount)
345-
Thread.sleep(forTimeInterval: 0.3)
363+
Thread.sleep(forTimeInterval: 0.5)
346364
let totalEvent = try dbUtil.getEventCount()
347365
XCTAssertEqual(0, totalEvent)
348366
XCTAssertTrue(eventRecorder.bundleSequenceId == 3)
@@ -376,9 +394,6 @@ class EventRecorderTest: XCTestCase {
376394
eventRecorder.submitEvents()
377395
eventRecorder.submitEvents()
378396
XCTAssertEqual(2, eventRecorder.queue.operationCount)
379-
Thread.sleep(forTimeInterval: 0.5)
380-
let totalEvent = try dbUtil.getEventCount()
381-
XCTAssertEqual(0, totalEvent)
382397
}
383398

384399
func testProcessEventQueueReachedMaxOperationCount() throws {
@@ -390,6 +405,13 @@ class EventRecorderTest: XCTestCase {
390405
}
391406
XCTAssertTrue(eventRecorder.queue.operationCount <= 1_000)
392407
}
408+
409+
func testBackgroundModeAutoSendRequest() throws {
410+
activityTracker.callback?(.runningInForeground)
411+
try eventRecorder.save(clickstreamEvent)
412+
activityTracker.callback?(.runningInBackground)
413+
XCTAssertTrue(eventRecorder.queue.operationCount > 0)
414+
}
393415
}
394416

395417
extension String {

Tests/ClickstreamTests/IntegrationTest.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ class IntegrationTest: XCTestCase {
228228
configuration.isCompressEvents = true
229229
configuration.isLogEvents = true
230230
configuration.authCookie = "authCookie"
231-
ClickstreamAnalytics.recordEvent("testEvent")
231+
ClickstreamAnalytics.recordEvent("testEvent", [
232+
"isLogEvent": true
233+
])
232234
Thread.sleep(forTimeInterval: 0.2)
233235
let eventCount = try eventRecorder.dbUtil.getEventCount()
234236
XCTAssertEqual(1, eventCount)

Tests/ClickstreamTests/Mock/MockAnalyticsClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class MockAnalyticsClient: AnalyticsClientBehaviour {
135135
}
136136

137137
var submitEventsCount = 0
138-
func submitEvents() {
138+
func submitEvents(isBackgroundMode: Bool = false) {
139139
submitEventsCount += 1
140140
submitEventsExpectation?.fulfill()
141141
}

Tests/ClickstreamTests/Mock/MockEventRecorder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class MockEventRecorder: AnalyticsEventRecording {
1919
}
2020

2121
var submitCount = 0
22-
func submitEvents() {
22+
func submitEvents(isBackgroundMode: Bool = false) {
2323
submitCount += 1
2424
}
2525
}

0 commit comments

Comments
 (0)