amplify-swift icon indicating copy to clipboard operation
amplify-swift copied to clipboard

PinPoint events are not cached while offline and are not sent later

Open frankiesimon opened this issue 3 years ago • 5 comments

Describe the bug

When my app is offline events that are recorded are not cached anywhere and are not sent when the app goes back online or in a subsequent app session (restarting it).

When debugging I got to AWSPinpointAnalyticsPlugin+ClientBehavior.swift -> flushEvents().

public func flushEvents() {
        if !isEnabled {
            log.warn("Cannot flushEvents. Analytics is disabled. Call Amplify.Analytics.enable() to enable")
            return
        }

        pinpoint.submitEvents().continueWith { (task) -> Any? in
            guard task.error == nil else {
                // TODO: some error mapping
                let error = task.error! as NSError
                Amplify.Hub.dispatchFlushEvents(AnalyticsErrorHelper.getDefaultError(error))
                return nil
            }

            if let pinpointEvents = task.result as? [AWSPinpointEvent] {
                // TODO: revist this, this is exposing internal implementation
                Amplify.Hub.dispatchFlushEvents(pinpointEvents)
            }

            return nil
        }
    }

If internet is down the task has an error and there are no pinpointEvents parsed from the task.result, it flushes an "error" event but ignores the rest. As far as I can tell this is what loses the events. If there is some caching behavior I didn't see it.

I'm having trouble understanding if this is a bug or if this was never implemented.

We are porting our code from AWSMobileAnalytics and this behavior was definitely supported there. Documented in the FAQ here:

https://aws.amazon.com/mobileanalytics/faqs/

Q: Is data cached when a user’s device is offline? Yes, when using the AWS Mobile SDK, data is cached on the user’s device and is uploaded when a network connection is next established.

It's a big deal for us and I couldn't understand if this is a known regression in Amplify iOS.

By the way, it seems that Amplify Android SDK does have support for "retryable errors" (though we also found a bug in the implementation there preventing offline events from being cached).

Steps To Reproduce

Steps to reproduce the behavior:
1. Setup Amplify with pinpoint
2. Start app without internet and record events
3. Go online and record more events
4. Offline events were not sent.

Expected behavior

Expected offline events to be cached and sent later on.

Amplify Framework Version

1.28.1

Amplify Categories

Analytics, Auth

Dependency manager

Swift PM

Swift version

5.6.1

CLI version

10.0.0

Xcode version

13.4.1 13F100

Relevant log output

No response

Is this a regression?

Yes

Regression additional context

We are porting our code from AWSMobileAnalytics and this behavior was definitely supported there. Documented in the FAQ here:

https://aws.amazon.com/mobileanalytics/faqs/

Q: Is data cached when a user’s device is offline? Yes, when using the AWS Mobile SDK, data is cached on the user’s device and is uploaded when a network connection is next established.

Device

iPhone 8 Simulator / real device

iOS Version

15.5

Specific to simulators

No response

Additional context

No response

frankiesimon avatar Oct 06 '22 09:10 frankiesimon

Hi @frankiesimon, thanks for opening this issue.

Amplify Analytics on iOS works like this:

  1. Events recorded with Amplify.Analytics.record(event:) are cached.
  2. After a configurable amount of time has passed (60 seconds by default), events will be submitted to Pinpoint.
  3. If the submission fails for an event, Amplify will try to submit it again on the next run.

Amplify will attempt to submit an event a total of 4 times (the initial attempt + 3 retries). Events that exceed that threshold will be discarded.

Currently, there is no distinction between a connectivity-related failure and any other retryable error, so if the device is offline during 4 attempts, those events will be discarded.

I've verified that as long as the connectivity is resumed before the 3rd retry, events are successfully submitted.

As this has been the behaviour from the very beginning, we will discuss among the teams and post updates in here.


In the meantime, if you need an urgent solution you can disable the automatic flush of events by adding autoFlushEventsInterval: 0 to the amplifyconfiguration.json file:

"analytics": {
    "plugins": {
        "awsPinpointAnalyticsPlugin": {
            "pinpointAnalytics": {
                "appId": "[AppId]",
                "region": "[Region]"
            },
            "pinpointTargeting": {
                "region": "[Region]"
            },
            "autoFlushEventsInterval": 0
        }
    }
}

Then you can temporary implement your own automatic submission of events that checks for the network connectivity. For example, you can create a NWPathMonitor object:

private let networkMonitor = NWPathMonitor()

Then after calling Amplify.configure(), you can start monitoring for connectivity changes and create a Timer for flushing events:

networkMonitor.start(queue: DispatchQueue(label: "NetworkMonitor"))

Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [weak networkMonitor] _ in
    guard let networkMonitor = networkMonitor else { return }
    if networkMonitor.currentPath.status == .satisfied {
        Amplify.Analytics.flushEvents()
    } else {
        print("No connectivity, skipping.")
    }
}

ruisebas avatar Oct 06 '22 15:10 ruisebas

Thank you -- this is extremely helpful. I do believe this behavior needs to be clearly documented somewhere in the context of writing how to record events or how the flush interval affects this behavior. Could you point to where in the Amplify code this retry behavior actually happens?

I think that's it's problematic to tie the flush and offline behaviors together. In our case, one of the main reasons to move from AWSMobileAnalytics to Amplify is the ability to switch to a much-shorter-than-60-seconds interval of flushing events. With the behavior you described, while we reap the benefits of a shorter interval with online sessions we are actually punished with the offline sessions since a shorter interval leaves less chance to recover.

As for the suggested solution of calling flushEvents on our own - could you help me understand something? Will the cached events remain eligible for "flush"ing as long as the app is active? When would they be deleted/lost? If I restart the app are the events from the previous session still up for flushing or are they gone?

frankiesimon avatar Oct 06 '22 16:10 frankiesimon

I confirmed the suggested solution works and cached events were sent both if 1. I went online during the session AND 2. I restarted the app after going online.

I still would like to understand if these events are persisted across sessions and what would be a reason to drop events if a user doesn't go online for a long while.

frankiesimon avatar Oct 06 '22 16:10 frankiesimon

Amplify Analytics uses the AWSPinpoint SDK underneath, which defines the retry logic I mentioned. Specifically, in the AWSPinpointEventRecorder class.

Events are persisted in a local SQLite db, so they will remain available even if the app is closed. If the app is deleted however, they will be lost.

Additionally, there is a 5MB file size limit for the database: if said size is reached, the oldest event will be deleted when a new one is saved.

But since an event is deleted from the database every time it is successfully submitted to Pinpoint, it is highly unlikely that you would reach that size during the offline period.

ruisebas avatar Oct 06 '22 17:10 ruisebas

Thank you for the reference, seeing the retrying code really helps. I think the documentation should reference this behavior in the context of recording events. Even as I know it now, I can't seem to find any relevant Google results except mentions of missing feature / bugs in the JS / Android repositories. It's probably sensible to explain in the context of either recording events or the flush interval - I believe offline events can be important to many scenarios.

frankiesimon avatar Oct 07 '22 07:10 frankiesimon

This issue has been fixed in both v1 and v2:

  • For v2.x, in release 2.0.1
  • For v1.x, starting from release 1.28.1. The fix was included in AWS SDK for iOS 2.28.1, so just update your dependencies and validate that you get this version.

ruisebas avatar Oct 31 '22 13:10 ruisebas