Documentation Index
Fetch the complete documentation index at: https://docs.mutalabs.com/llms.txt
Use this file to discover all available pages before exploring further.
Muta provides a flexible event system that works with any analytics provider (Mixpanel, AppsFlyer, Amplitude, Firebase, etc.). Events are emitted during key user interactions, allowing you to track and analyze your placement performance.
Basic Integration
Here’s a simple example of how to integrate analytics with your Muta placements:
import MutaSDK
class AnalyticsManager {
var subscription: Subscription?
func setupTracking() {
subscription = Muta.shared.on { event in
switch event {
case .flowStarted(let event):
Analytics.track("Flow Started", properties: [
"placement_id": event.placementId,
"flow_name": event.flowName ?? "",
"total_screens": event.totalScreens,
"timestamp": event.timestamp
])
case .flowCompleted(let event):
var properties: [String: Any] = [
"placement_id": event.placementId,
"flow_name": event.flowName,
"screen_index": event.screenIndex,
"total_screens": event.totalScreens,
"timestamp": event.timestamp
]
// Include collected variables if present
if let variables = event.eventData?["variables"] as? [String: Any] {
properties["variables"] = variables
}
Analytics.track("Flow Completed", properties: properties)
case .custom(let event):
Analytics.track(event.type, properties: [
"placement_id": event.placementId,
"flow_name": event.flowName ?? "",
"event_data": event.eventData ?? [:],
"timestamp": event.timestamp
])
// Handle other events...
}
}
}
deinit {
subscription?.unsubscribe()
}
}
Available Events
1. Flow Started (flowStarted)
Emitted when a placement flow begins displaying.
case flowStarted(FlowStartedEvent)
// Properties: placementId, flowName?, totalScreens, timestamp
2. Screen Viewed (screenViewed)
Emitted when a user views a new screen in the flow.
case screenViewed(ScreenViewedEvent)
// Properties: placementId, flowName?, screenIndex, totalScreens, screenName?, timestamp
3. Flow Completed (flowCompleted)
Emitted when a user successfully finishes the flow.
case flowCompleted(FlowCompletedEvent)
// Properties: placementId, flowName, screenIndex, totalScreens, screenName, timestamp, eventData?
// eventData contains variables collected during the flow
Variable Structure
When a flow completes, eventData["variables"] contains a dictionary where:
- Key: Variable ID (e.g.,
var_1758310510757_as20wkeo4)
- Value: Dictionary containing:
id (String): Unique variable identifier
name (String): Human-readable variable name from the Muta editor
type (String): Variable type (e.g., “text”)
value (Any): The collected value
defaultValue (Any): The default value
Example:
case .flowCompleted(let event):
if let variables = event.eventData?["variables"] as? [String: [String: Any]] {
for (varId, varData) in variables {
let name = varData["name"] as? String ?? ""
let value = varData["value"] as? String ?? ""
print("Variable '\(name)': \(value)")
}
}
Output:
Variable 'MultipleChoice1': Family
Variable 'MultipleChoice2': Fluent
4. Flow Abandoned (flowAbandoned)
Emitted when a user exits the flow before completion.
case flowAbandoned(FlowAbandonedEvent)
// Properties: placementId, flowName?, screenIndex, totalScreens, lastScreenIndex, screenName?, timestamp
5. Custom Events
Emitted when users trigger actions configured with “Emit Event” behavior in the Muta web editor.
case custom(CustomEvent)
// Properties: type, placementId, flowName?, screenIndex?, timestamp, eventData?
Setting Up Custom Events
- In the Muta web editor, add an “Emit Event” behavior to any element
- Give your event a unique name (e.g.,
signup_started, premium_selected)
- Optionally add custom data to pass along with the event
Listening for Custom Events
subscription = Muta.shared.on { event in
if case .custom(let customEvent) = event {
switch customEvent.type {
case "signup_started":
// Handle signup
navigateToSignup()
case "plan_selected":
if let planType = customEvent.eventData?["planType"] as? String {
selectPlan(planType)
}
default:
// Track any custom event
analytics.track(customEvent.type, properties: customEvent.eventData ?? [:])
}
}
}
6. Error Events (error)
Emitted when there are network or placement errors.
case error(ErrorEvent)
// Types: .network(message, timestamp) or .placement(message, code, timestamp, placementId)
Integration Examples
Mixpanel Example
import MutaSDK
import Mixpanel
class MixpanelAnalytics {
var subscription: Subscription?
func setupTracking() {
subscription = Muta.shared.on { event in
switch event {
case .flowCompleted(let event):
Mixpanel.mainInstance().track(
event: "Onboarding Complete",
properties: [
"flow_name": event.flowName,
"total_screens": event.totalScreens
]
)
case .flowAbandoned(let event):
Mixpanel.mainInstance().track(
event: "Onboarding Abandoned",
properties: [
"flow_name": event.flowName ?? "",
"last_screen_index": event.lastScreenIndex,
"total_screens": event.totalScreens
]
)
case .custom(let event):
// Track custom events
Mixpanel.mainInstance().track(
event: event.type,
properties: event.eventData ?? [:]
)
// Example: Track form submission
if event.type == "form_submitted" {
if let formData = event.eventData {
Mixpanel.mainInstance().track(
event: "Form Submitted",
properties: formData
)
}
}
default:
break
}
}
}
deinit {
subscription?.unsubscribe()
}
}
AppsFlyer Example
import MutaSDK
import AppsFlyerLib
class AppsFlyerAnalytics {
var subscription: Subscription?
func setupTracking() {
subscription = Muta.shared.on { event in
var eventName = ""
var properties: [String: Any] = [:]
switch event {
case .flowStarted(let e):
eventName = "onboarding_flow_started"
properties = [
"placement_id": e.placementId,
"flow_name": e.flowName ?? "",
"total_screens": e.totalScreens,
"timestamp": e.timestamp
]
case .screenViewed(let e):
eventName = "onboarding_screen_viewed"
properties = [
"placement_id": e.placementId,
"flow_name": e.flowName ?? "",
"screen_index": e.screenIndex,
"total_screens": e.totalScreens,
"screen_name": e.screenName ?? ""
]
case .flowCompleted(let e):
eventName = "onboarding_flow_completed"
properties = [
"placement_id": e.placementId,
"flow_name": e.flowName,
"screen_index": e.screenIndex,
"total_screens": e.totalScreens,
"screen_name": e.screenName
]
case .flowAbandoned(let e):
eventName = "onboarding_flow_abandoned"
properties = [
"placement_id": e.placementId,
"flow_name": e.flowName ?? "",
"screen_index": e.screenIndex,
"total_screens": e.totalScreens,
"last_screen_index": e.lastScreenIndex
]
case .custom(let e):
// Use the custom event type as the event name
eventName = e.type
properties = [
"placement_id": e.placementId,
"flow_name": e.flowName ?? "",
"screen_index": e.screenIndex ?? -1
]
// Merge custom event data
if let eventData = e.eventData {
properties.merge(eventData) { _, new in new }
}
case .error(let error):
eventName = "onboarding_error"
switch error {
case .network(let message, _):
properties = [
"error_message": message,
"error_type": "network"
]
case .placement(let message, let code, _, let placementId):
properties = [
"error_message": message,
"error_code": code,
"error_type": "placement",
"placement_id": placementId
]
@unknown default:
break
}
@unknown default:
break
}
if !eventName.isEmpty {
AppsFlyerLib.shared().logEvent(eventName, withValues: properties)
}
}
}
deinit {
subscription?.unsubscribe()
}
}
Best Practices
-
Event Naming: Use consistent event naming conventions across your analytics platform.
-
Data Enrichment: Add additional context to events when needed (e.g., user ID, app version).
-
Error Handling: Implement proper error handling for analytics tracking.
-
Custom Event Documentation: Document your custom events and their expected data structure.
-
Performance: Consider batching events for better performance, especially for high-frequency events.
-
Privacy: Ensure you’re not tracking sensitive information in your analytics events.