Docs Menu

Event Library - Swift SDK

On this page

The Event Library enables you to track a user's activity while using a Sync-enabled mobile application. The Event Library can record read and write transactions. Developers can also configure custom events to record button presses, the data displayed in the UI, or other important details.

When you use the Event Library, you can specify the events you want to record. This means opening two realms:

  • The user realm, where the user does the reading and writing in the client application
  • The event realm, where the Event Library records scoped and custom events

The data from both realms syncs to your App Services App. The client user never interacts directly with the event realm or its data; and the event realm Sync user can even be different from the user realm.

Because the Event Library generates a large amount of data:

  • The client device must have enough capacity to store the data
  • Expect Device Sync usage for the event realm to be higher than the reading and writing in the user realm
  • The App Services App's backing Atlas cluster must have enough storage capacity to handle the data generated by the Event Library

The Event Library stores data in an AuditEvent collection in your linked Atlas data source. Enable Development Mode in your App Services App to let Atlas create the collection and infer the schema from the uploaded events.

To enable event recording, set the Event.Configuration property on the Realm.Configuration.

You can initialize the EventConfiguration in either of two ways:

  • Use the default-initialized configuration when you don't need to specify details
  • Pass additional parameters to customize the event configuration

If you don't need to specify particular parameters, you can use the default-initialized EventConfiguration:

var config = user.configuration(partitionValue: "Some partition value")
config.eventConfiguration = EventConfiguration()

You can pass optional parameters to customize the EventConfiguration:

  • metadata: String dictionary of metadata fields to append to each event
  • syncUser: The user to use for syncing event realm(s). If nil, defaults to the user from the Realm.Configuration.
  • partitionPrefix: String prefix to append to the event partition value
  • logger: Custom logger to use for events. If nil, defaults to the logger from the user realm.
  • errorHandler: Custom error handler to use when opening the event realm. If nil, defaults to the error handler from the user realm.
let eventSyncUser = try await app.login(credentials: Credentials.anonymous)
var config = user.configuration(partitionValue: "Some partition value")
config.eventConfiguration = EventConfiguration(metadata: ["username": "Jason Bourne"], syncUser: eventSyncUser, partitionPrefix: "event-")

After you define your event configuration, you can invoke the event recording functionality with the new events property on a realm. This returns an Event instance tied to that realm.

let realm = try! Realm(configuration: config)
let events = realm.events!

The Event Library records read and write events within the context of a scope. The scope is the period during which the Event Library watches for and records realm activities. You can set different scopes to record different types of events. For example, you might have scopes for specific user flows such as "login", different scopes for different screens, or different scopes for specific compliance-related activities.

Use beginScope(activity: "some activity") to begin recording a new event with the given activity name. This records activities that occur within the scope of the event as read or write events.

  • Read events: run queries and instantiate objects. When the scope ends, the event realm records these activities as read events.
  • Write events: modify objects. When the scope ends, the event realm records the initial state of the object, as well as the new values of any properties that change durign the scope of the event.

Using beginScope to record an event opens the event realm if it is not already open. The SDK opens event realms on the background thread and reports errors to the error callback.

Use endScope() to finish recording the event. When you end the recording, the Event Library saves the event to disk locally. Then, if the device has a network connection, the SDK asynchronously sends the data to the server.

// Read event
events.beginScope(activity: "read object")
let person = realm.objects(Person.self).first!
print("Found this person: \(person.name)")
events.endScope()
events.beginScope(activity: "mutate object")
// Write event
try! realm.write {
// Change name from "Anthony" to "Tony"
person.name = "Tony"
}
events.endScope()

You can pass an optional completion block to endScope() when you finish recording. The SDK calls this block when the event data has been successfully persisted - not when the event realm upload has completed.

events.beginScope(activity: "mutate object with completion")
// Write event
try! realm.write {
// Add a userId
person.userId = "tony.stark@starkindustries.com"
}
events.endScope(completion: { error in
if let error = error {
print("Error recording write event: \(error.localizedDescription)")
return
}
print("Successfully recorded a write event")
})

The Event Library lets you record button clicks or other events that do not involve database reads and writes. Use recordEvent to record a custom event. This function takes these parameters:

  • activity: the activity name. This is an arbitrary string, such as "user registration."
  • eventType: the type of event. This is an arbitrary string, such as "pressed Submit button."
  • data: an optional data payload for the event.
  • completion: an optional completion handler. The Event Library calls this completion block once the event has been saved to the event realm, or if an error occurs. A nil error indicates success.

A custom event doesn't have a scope like read and write events. Instead, recording a custom event is more analogous to firing a trigger.

events.recordEvent(activity: "event", eventType: "custom event")

The Event Library converts each event object to a JSON object. Most Realm Database types have an analogous JSON representation. For example, a Realm Database String property becomes a JSON String.

This is how the Event Library represents the types that do not have a direct JSON analog:

Date
Encoded to a string in ISO 8601-1:2019 format.
Data
Excluded entirely from the event.
UUID
Encoded to a RFC-4122-compliant string.
ObjectID
Encoded to our ObjectID string representation.
Decimal128
Encoded to a string, not a number. JSON numbers are officially infinite- precision, but are rarely actually implemented as such.
List
Encoded as an array.
Set
Encoded as an array.
Dictionary
Encoded as an object.
Embedded objects
Encoded as an object.

Non-embedded object links are encoded as the primary key of the target. In read events, if the link is followed, this expands to the full object. If the link is not followed, this remains a primary key.

You can customize JSON serialization for event objects. To customize the event payload, use the CustomEventRepresentable protocol.

When an object conforms to CustomEventRepresentable, the Event Library serializes objects by:

  • Constructing an accessor object
  • Calling customEventRepresentation on that accessor object
  • Serializing the result instead of the original object

To conform to CustomEventRepresentable, your object must implement a customEventRepresentation function that defines your customized serialization.

// To customize event serialization, your object must
// conform to the `CustomEventRepresentable` protocol.
class Person: Object, CustomEventRepresentable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name: String
@Persisted var employeeId: Int
@Persisted var userId: String?
convenience init(name: String, employeeId: Int) {
self.init()
self.name = name
self.employeeId = employeeId
}
// To conform to `CustomEventRepresentable`, your object
// must implement a `customEventRepresentation` func that
// defines your customized event serialization
func customEventRepresentation() -> String {
if employeeId == 0 {
return "invalid json"
}
return "{\"int\": \(employeeId)}"
}
}

The event realm can grow quite large if the device is offline for an extended time.

To compensate for this, the Event Library automatically splits event data into multiple partitions as needed. When a partition reaches its maximum size, the Event Library closes the event realm and automatically begins writing to a new partition.

The Event Library checks whether the user has any unsynced partitions. If they do, the Event Library opens one, uploads the data, and then closes the file and deletes it. This repeats until the user has no unsynced partitions.

An event object contains:

Type of Event
event
Optional string. read or write for scoped events, or an arbitrary string for custom events.
Event Scope
scope
Optional string. The scope name passed to beginScope() to begin recording the event, or an arbitrary string for custom events.
Timestamp
timestamp
Device local time when the event scope is ended and the data is committed, or the time when the event hits the server.
Data
data
The event payload. This is a JSON blob for scoped events, or an arbitrary string for custom events.
Metadata
metadata key (string)
An optional metadata dictionary. When this dictionary contains keys and values, the key becomes a field name in the event object, and the value is stored in that field for every event.

Each event contains a payload in the data property that captures the current state of the objects being read or written to.

Payloads for custom events can be whatever the developer desires, including nil.

Important

Because the payload captures the current state of the objects being read or written to, this produces a very large amount of data. However, this must be done on the client rather than the server, as the exact data that the user views may never exist server-side. In practice, this means that the device must have the capacity to store a large amount of data if it goes offline. Additionally, Device Sync usage for the event realm may be much higher than for the reading and writing the user does in the user realm.

Example

In our Record Events examples above, these are the data payloads for the read and write events:

Read Event Payload
{
"type":"Person",
"value": [{
"_id":"62b38b3c10846041166f5deb",
"_partition":"",
"employeeId":2,
"name":"Anthony",
"userId":null
}]
}
Write Event Payload
{
"Person": {
"modifications": [{
"newValue": {
"name":"Tony"
},
"oldValue":{
"_id":"62b38b3c10846041166f5deb",
"_partition":"",
"employeeId":2,
"name":"Anthony",
"userId":null
}
}]
}
}
←  Set the Client Log Level - Swift SDKRealm Web SDK →
Give Feedback
© 2022 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2022 MongoDB, Inc.