You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: articles/preset-errors.md
+58-89Lines changed: 58 additions & 89 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,36 +18,17 @@ A smooth experience without bugs & issues is one of the key factors contributing
18
18
Error Handling is any logic that detects that something unexpected happened and reacts to that information in some useful way. It's commonly interpreted as 'showing an error message to a user', but that's just the most basic form of Error Handling. Other common things you can do to improve your app are Empty States (Why is this empty?), Call-to-Actions (What can I do?), and Auto-Repair (resiliency against common unexpected user input). But always make sure to give some form of feedback instead of failing silently, which makes your app feel broken.
19
19
{% endnoteinfo %}
20
20
21
-
## Sending the Signal
21
+
## Using the TelemetryDeck Swift SDK
22
22
23
-
To report unexpected events to TelemetryDeck, send the event name `TelemetryDeck.Error.occurred` with the parameter `TelemetryDeck.Error.id` set to something that can be used to group errors of the same kind. For example, using the Swift SDK you could simply pass `error.localizedDescription` whenever an exception is thrown that you don't expect to happen:
24
-
25
-
```swift
26
-
do {
27
-
let object =tryJSONDecoder().decode(Object.self, from: data)
The `errorOccurred` function also accepts the same arguments as the `signal` function (namely `parameters`, `floatValue`, `customUserID`) in case you want to provide additional context info.
29
+
### Better Error Identification
45
30
46
-
## Separation of ID & Message
47
-
48
-
While the above code is a good starting point, the localized nature of the `localizedDescription` message attached to all thrown exceptions in Swift isn't optimal. The same issue will be reported with different messages simply because the text will differ based on the users language settings. And you might have even created your own error types that provide dynamic content such as the file path in the error message, which makes things even worse. To see which errors affect most users, it's best to give the same kind of error the same ID.
49
-
50
-
So, whenever possible, it's recommended that you pass a made-up value to the `TelemetryDeck.Error.id` parameter that rather represents the context of the error. The full message can be provided with the optional parameter `TelemetryDeck.Error.message`. The Swift SDK provides several convenient ways to do this:
31
+
While `error.localizedDescription` works, it's not optimal because the same error will be reported differently based on user language settings. For better error grouping, provide a consistent ID:
For your own `Error` types, you could introduce an `IdentifiableError` protocol and conform to that to make this process easier (the Swift SDK has this protocol built-in):
50
+
### Custom Error Types
79
51
80
-
```swift
81
-
protocolIdentifiableError: Error{
82
-
var id: String { get }
83
-
}
52
+
For your own error types, conform to the `IdentifiableError` protocol (built into the Swift SDK):
// User input errors (invalid format, invalid range)
79
+
TelemetryDeck.errorOccurred(
80
+
id: "ProjectForm.hourlyRateConversionFailed",
81
+
category: .userInput,
82
+
message: "Text '\(self.textFieldInput)' could not be converted to type 'Int'."
83
+
)
84
+
85
+
// App state errors (inconsistent navigation, invalid combinations)
86
+
TelemetryDeck.errorOccurred(
87
+
id: "NavigationState.invalidTransition",
88
+
category: .appState,
89
+
message: "Cannot navigate from login to dashboard without authentication"
90
+
)
109
91
```
110
92
111
-
Note that `error.localizedDescription` will be sent as the `message` by default, but you can override it.
93
+
{% noteinfo "Default Category" %}
94
+
When using `TelemetryDeck.errorOccurred(identifiableError:)`, the category defaults to `.thrownException`, but you can override it if needed.
95
+
{% endnoteinfo %}
112
96
113
-
## Built-In Error Categories
97
+
The `errorOccurred` function accepts the same optional arguments as the `signal` function (namely `parameters`, `floatValue`, `customUserID`) in case you want to provide additional context info.
114
98
115
-
Reporting exceptions that you didn't expect to happen isn't enough to cover all "unexpected behaviors" that you will encounter in your app. We found that unexpected behavior generally falls into one of the following 3 categories:
2. Unexpected **User Input** (e.g. invalid text format, invalid number format, invalid date range)
119
-
3. Unexpected **App State** (e.g. inconsistent navigation request, invalid combination of form options)
101
+
If you're not using the TelemetryDeck Swift SDK (for example, on Android, Web, or other platforms), you'll need to manually build the error signal.
120
102
121
-
Each of these has a dedicated chart in the "Errors" tab, you just need to report one of `thrown-exception`, `user-input`, or `app-state` to the parameter `TelemetryDeck.Error.category`.
103
+
### Signal Structure
122
104
123
-
Here's some guidance on when to use which category in Swift:
105
+
**Event name**: `TelemetryDeck.Error.occurred`
124
106
125
-
- A clear sign to report a `thrown-exception` error is a `do-catch` clause or uses of `try?` in Swift where you can send the error signal when it returns `nil`.
126
-
- Whenever you make use of the nil-coalescing operator `??` or unwrap an Optional with `if-let` or `guard-let`, potentially some kind of conversion of user input into another type might happen with a fallback behavior – this is a typical `user-input` error.
127
-
- Search for any uses of [`assert`](<https://developer.apple.com/documentation/swift/assert(_:_:file:line:)>) or [`assertionFailure`](<https://developer.apple.com/documentation/swift/assertionfailure(_:file:line:)>) in your code and additionally report these detected unexpected states of your app during runtime as `app-state` errors. If you weren't aware, the `assert`/`assertionFailure` functions are similar to `fatalError`/`precondition`/`preconditionFailure` with the difference that they only stop program execution during DEBUG builds, not in production builds.
107
+
**Required parameter**:
128
108
129
-
A full signal that reports a `user-input` category error could end up looking something like this in Swift:
109
+
-`TelemetryDeck.Error.id`: A consistent identifier for grouping similar errors
2.**User Input** (`user-input`): Invalid text format, invalid number format, invalid date range
122
+
3.**App State** (`app-state`): Inconsistent navigation request, invalid combination of form options
123
+
124
+
### When to Use Each Category
158
125
159
-
Please note that when calling the `TelemetryDeck.errorOccurred(identifiableError:)` function, the category is implicitly set to `.thrownException`, but you can override it if needed.
126
+
-**thrown-exception**: Use in `try-catch` blocks or when handling `null`/`nil` returns from operations that might fail
127
+
-**user-input**: Use when converting or validating user input with fallback behavior
128
+
-**app-state**: Use for assertion failures or unexpected application states that shouldn't occur in normal operation
Copy file name to clipboardExpand all lines: articles/preset-purchases.md
+54-28Lines changed: 54 additions & 28 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,61 +14,87 @@ If you are offering In-App Purchases in your app, you might have noticed some de
14
14
15
15
That's why you might want to set up a signal in your application to track purchases in your app through TelemetryDeck with just a couple of seconds delay, providing you with the live data you want.
16
16
17
+
You can use these methods to include your purchase data in TelemetryDeck:
18
+
19
+
- Use the TelemetryDeck Swift SDK directly
20
+
- If you're already using RevenueCat, you can use the RevenueCat Integration
21
+
- If you're using FreemiumKit, you can connect that to TelemetryDeck
22
+
23
+
See the sections below for a detailed description.
24
+
17
25
{% notewarning "Live Data vs. Correct Data" %}
18
26
We do not offer any intelligence to correct once reported purchases, such as when users make refunds, or to detect subscription renewals. Therefore, our insights focus on more recent data. For longer-term or 100% correct data, refer to official sources.
19
27
{% endnotewarning %}
20
28
21
-
## Sending the Signal
29
+
## Using the TelemetryDeck Swift SDK
22
30
23
-
To report purchases to TelemetryDeck, send the event name `TelemetryDeck.Purchase.completed` with the `floatValue` parameter set to the USD amount the user purchased. For example, using the Swift SDK you can get the price from `StoreKit.Transaction` like so:
31
+
If you're using the TelemetryDeck Swift SDK, tracking purchases is incredibly simple. Just call the convenience method when you receive a StoreKit transaction:
24
32
25
33
```swift
26
-
let priceValue =NSDecimalNumber(decimal: transaction.price??Decimal()).doubleValue
Make sure to convert any currencies to USD before sending them as signals. You can use [an API like this](https://www.exchangerate-api.com/docs/standard-requests) which offers 1,500 requests per month free of charge to get current exchange rates if needed. You could also fetch & hard-code them to your app for a rough estimate if you expect more than 1,500 purchases per month.
37
+
That's it! This method automatically:
38
+
39
+
- Extracts the price from the transaction
40
+
- Converts the currency to USD (using hard-coded exchange rates)
41
+
- Determines if it's a subscription or one-time purchase
42
+
- Includes the storefront country and currency codes
43
+
- Sends the properly formatted signal to TelemetryDeck
44
+
45
+
{% noteinfo "Requirements" %}
46
+
The `purchaseCompleted` convenience function is only available on iOS 15 or higher. It accepts the same optional arguments as the `signal` function (namely `parameters` and `customUserID`) in case you want to provide additional context info.
47
+
{% endnoteinfo %}
48
+
49
+
## Using TelemetryDeck with RevenueCat
50
+
51
+
If you use [RevenueCat](https://revenuecat.com) and followed their [setup guide](https://www.revenuecat.com/docs/getting-started/making-purchases), you will have a `Purchases.shared.purchase(package:)` call somewhere in your code. The closure of this function gets a RevenueCat-specific `transaction`[wrapper](https://github.com/RevenueCat/purchases-ios/blob/11f3962192271cdbbb70096ff5a693b8a0e48f49/Sources/Purchasing/StoreKitAbstractions/StoreTransaction.swift) as its first parameter. You can get the native `StoreKit.Transaction` type by calling `transaction.sk2Transaction` and then pass it to `TelemetryDeck.purchaseCompleted()`. If you use RevenueCats built-in paywalls, they currently don't provide access to `transaction`, which we reported in [this issue](https://github.com/RevenueCat/purchases-ios/issues/4007).
52
+
53
+
## Using FreemiumKit
54
+
55
+
If you use [FreemiumKit](https://freemiumkit.app), just add their SDKs `.onPurchaseCompleted` view modifier to your main view. It passes the `transaction` parameter to the closure, which you can directly pass to `TelemetryDeck.purchaseCompleted(transaction: transaction)`. Read the related section in their [setup guide](https://freemiumkit.app/documentation/freemiumkit/setupguide#Direct-Access-to-StoreKit-Transactions) to learn more.
56
+
57
+
## Manual Signal Structure for Other Platforms
58
+
59
+
{% notewarning "Only Needed for Non-Swift Platforms" %}
60
+
The following section describes the manual signal structure only necessary if you are NOT using the TelemetryDeck Swift SDK. Swift developers should use the `purchaseCompleted` convenience method described above.
33
61
{% endnotewarning %}
34
62
35
-
The Swift SDK ships with a more convenient API that handles reading the price from the StoreKit transaction and converting to USD (with hard-coded non-live currency conversion) for you like so:
63
+
If you're reporting purchases from other platforms (Android, Web, etc.), you'll need to manually construct and send the purchase signal with the following structure:
The `purchaseCompleted` convenience function in the Swift SDK also automates the extraction of the values explained in the next section. Optionally, it accepts the same arguments as the `signal` function (namely `parameters` and `customUserID`) in case you want to provide additional context info. The function is only available on iOS 15 or higher.
43
-
{% endnoteinfo %}
67
+
-**Event name**: Must be `TelemetryDeck.Purchase.completed`
68
+
-**`floatValue`**: The purchase amount in USD
44
69
45
-
{% noteinfo "RevenueCat" %}
46
-
If you use [RevenueCat](https://revenuecat.com) and followed their [setup guide](https://www.revenuecat.com/docs/getting-started/making-purchases), you will have a `Purchases.shared.purchase(package:)` call somewhere in your code. The closure of this function gets a RevenueCat-specific `transaction`[wrapper](https://github.com/RevenueCat/purchases-ios/blob/11f3962192271cdbbb70096ff5a693b8a0e48f49/Sources/Purchasing/StoreKitAbstractions/StoreTransaction.swift) as its first parameter. You can either access fields directly from that or get the native `StoreKit.Transaction` type by calling `transaction.sk2Transaction`. If you use RevenueCats built-in paywalls, they currently don't provide access to `transaction`, which we reported in [this issue](https://github.com/RevenueCat/purchases-ios/issues/4007).
When sending purchase signals manually, you MUST convert the transaction value to USD yourself before sending. You can use [an API like this](https://www.exchangerate-api.com/docs/standard-requests) which offers 1,500 requests per month free of charge to get current exchange rates. Alternatively, you could fetch & hard-code exchange rates in your app for a rough estimate if you expect more than 1,500 purchases per month.
72
+
{% endnotewarning %}
48
73
49
-
{% noteinfo "FreemiumKit" %}
50
-
If you use [FreemiumKit](https://freemiumkit.app), just add their SDKs `.onPurchaseCompleted` view modifier to your main view. It passes the `transaction` parameter to the closure, which is exactly what we need so you can just copy & paste the above code as-is. Read the related section in their [setup guide](https://freemiumkit.app/documentation/freemiumkit/setupguide#Direct-Access-to-StoreKit-Transactions) to learn more.
51
-
{% endnoteinfo %}
74
+
### Optional but Recommended Payload Keys
52
75
53
-
## Attaching the Payload
76
+
To get more detailed insights, include these additional parameters:
54
77
55
-
Optionally (but recommended), there are two additional payload keys that will give you additional insights:
78
+
-`TelemetryDeck.Purchase.type`: Either `subscription` or `one-time-purchase`
79
+
-`TelemetryDeck.Purchase.countryCode`: The country code of the storefront
80
+
-`TelemetryDeck.Purchase.currencyCode`: The currency code of the storefront
56
81
57
-
-`TelemetryDeck.Purchase.type`: Pass either `subscription` or `one-time-purchase` to see the type distribution.
58
-
-`TelemetryDeck.Purchase.countryCode`: Pass the country code of the storefront to see the country distribution.
59
-
-`TelemetryDeck.Purchase.currencyCode`: Pass the currency code of the storefront to see currency distribution.
82
+
### Example Manual Implementation (Swift)
60
83
61
-
In Swift getting all values and sending them looks like this:
84
+
Here's what the manual implementation looks like if you need to customize it or understand what the convenience method does internally:
62
85
63
86
```swift
87
+
// Convert price to USD first (you need to handle currency conversion)
88
+
let priceInUSD =convertToUSD(transaction.price, from: transaction.currencyCode)
0 commit comments