Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Feature: Save Undispatched Events For Offline Tracking #377

Closed

Conversation

bobunmeng
Copy link

Matomo dispatches event every 30seconds.

Issues:

  • No possible offline tracking options (outside class has no access to necessary variables that can work around offline feature)
  • Events cannot be sent if the user quits the app before the dispatch interval.

Solutions:

  • Create a new variable for storing events (events that haven't been dispatched / events that are still in the memory) in MatomoUserDefaults
  • Create a public function for caching those events (if developer doesn't want to, they can just ignore this.) - the function is in MatomoTracker and should be written in "appWillTerminate" - so when the user quits the app, the events are saved.
    ** This is also great for offline tracking. Because the events are saved no matter in which scenario.
  • Right when the matomo object is initialized, the object is set with the queue of the saved events.

Consequence:

  • The events will not go wasted even when they haven't been dispatched yet. The saved events will be sent with the same old session which allows the consistency of tracking data in the server.
  • The app offline/online state will not matter anymore. If the app is quit, during offline/online, the events are stored.

@brototyp
Copy link
Member

Hey @bobunmeng, thanks a lot for your PR. Really cool that you are taking on this task. I think it's something that can improve the Matomo SDK quite a bit. I've got a counter proposal for the technical implementation though:

In the SDK the Queue is the component that has the responsibility to store all events until they are dispatched to the server. Right now there is only one implementation, the MemoryQueue. We could add another implementation, the UserDefaultsBackedQueue (or so) which doesn't store the events in a local array in memory but actually stores it in the UserDefaults.
The upsides of this implementation would be that we have one component that is responsible to store the events (and the component itself can decide how to store them). Additionally, the user of the SDK can decide which Queue to use.

@bobunmeng
Copy link
Author

Thank you @brototyp . There is always a greater idea out there. I will try to make this work more smoothly and please help check it when it is done. This is very important for the organizaion I am working for as well.

@bobunmeng
Copy link
Author

@brototyp The update is complete. I hope it is better than the previous implementation. Thank you.

Copy link
Member

@brototyp brototyp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really nice to me. I've got a few small comments in the code.

I was also wondering, if we should limit the number of events somehow. Imagine a device that is offline for a long time. That could mean we would be collecting tons of events which could slow down tracking operations quite a bit (adding another event to the queue) and such.

What do you think about adding another optional parameter to the initializer, with which we can limit the number of events that will be queued with a reasonable default value. And every time we queue the next events, we make sure to ensure these limits. The user of the SDK can then decide if they want to change this value.

Comment on lines -117 to 126
@objc convenience public init(siteId: String, baseURL: URL, userAgent: String? = nil) {
/// - useBackedQueue: An optional parameter for using UserDefaultsBackedQueue instead of the default memory queue.
@objc convenience public init(siteId: String, baseURL: URL, userAgent: String? = nil, useBackedQueue: Bool = false) {
let validSuffix = baseURL.absoluteString.hasSuffix("piwik.php") ||
baseURL.absoluteString.hasSuffix("matomo.php")
assert(validSuffix, "The baseURL is expected to end in piwik.php or matomo.php")

let queue = MemoryQueue()
let dispatcher = URLSessionDispatcher(baseURL: baseURL, userAgent: userAgent)
let queue: Queue = useBackedQueue ? UserDefaultsBackedQueue(suiteName: "\(siteId)\(dispatcher.baseURL.absoluteString)") : MemoryQueue()
self.init(siteId: siteId, queue: queue, dispatcher: dispatcher)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would not even need any changes to this initializer here. The user of the SDK could use the init(siteId: String, queue: Queue, dispatcher: Dispatcher) directly and use the UserDefaultsBackedQueue as a queue there. What do you think?


extension UserDefaultsBackedQueue {
internal struct Key {
static let queueEvents = "PiwikBackedQueueEvents"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this?

Suggested change
static let queueEvents = "PiwikBackedQueueEvents"
static let queuedEvents = "MatomoQueuedEvents"

Comment on lines +6 to +8
init(suiteName: String?) {
userDefaults = UserDefaults(suiteName: suiteName)!
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about passing in an instance of UserDefaults directly?

Suggested change
init(suiteName: String?) {
userDefaults = UserDefaults(suiteName: suiteName)!
}
init(userDefaults: UserDefaults) {
userDefaults = userDefaults
}

@brototyp
Copy link
Member

Hey @bobunmeng, thanks for the changes. Looks really nice. I left a few comments from my side. Can you also do mea favor an rebase this pr onto develop (or merge develop into here)? That should also fix the build issues here.

@bobunmeng
Copy link
Author

@brototyp I have merged with the current develop branch. I will check your comments soon. Thanks :)

@brototyp
Copy link
Member

Hi @bobunmeng, I took the liberty to fork your PR and polish it a little bit to make it mergeable. Do you mind having a look at it to give me your feedback if you have some? It's here: #408

@brototyp
Copy link
Member

brototyp commented Sep 6, 2024

#408 got merged. Closing this PR

@brototyp brototyp closed this Sep 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants