Avo Functions overview

10 minute read

Avo generated file containing Avo Functions

The Avo file is the code representation of the tracking plan you define in Avo for a given source. You can think of a source as a platform that sends events. For example if you set up an iOS/Swift source the generated file would be a .swift file. Since the tracking plans are different for each customer the content of this file is also different. We generate the files and you can download them either with the Avo CLI or in the Avo web interface in the Implement tab.

First of all, feel free to explore your generated file. It's deliberately designed to be easily readable. Our code generation is under active development and some advanced or new features may slightly differ from one platform to another. The generated file is the best source of truth when looking for implementation details!

You should never have to make changes to the Avo generated file. If you find yourself in the situation where you have to edit the file please contact us.

Make sure all the events you want to implement using Avo Functions have the source attached to it, where "Implement with Avo" has been checked. Learn more about how to configure events in your tracking plan here.

Avo Command Line Interface

The Avo CLI is the easiest way to interact with Avo from your development environment. Run

Terminal
Copy
$
npm install -g avo

to install the CLI. Then login with

Terminal
Copy
$
avo login

And pull the Avo Generated file with the following command

Terminal
Copy
$
avo pull

it will guide you through initial the setup and will create a config file called avo.json in the directory. Next time when you pull the setup will be picked from that file.

Make sure to check the avo.json file in to your source control to keep the Avo configuration is in sync across your team.

Learn more about the CLI here.

Initialization

First thing you need to do after importing the generated file is to initialize Avo. There will either be a static initAvo method or a class, named Avo by default, with a constructor, depending on your setup. Contact us if you want to change the setup. Regardless of the option, the arguments you would need to provide are the same, and based on your tracking plan. Check out the Implement tab for specific recommendations. Samples below are in Swift and the code is very similar in other programming languages:

Swift
Copy
1
2
3
4
5
6
7
8
public static func initAvo(env: AvoEnv,
requiredStringSystemProperty: RequiredStringSystemProperty,
optionalStringSystemProperty: OptionalStringSystemProperty?,
requiredIntSystemProperty: Int,
optionalFloatSystemProperty: Double?,
optionalListSystemProperty: [Int]?,
customDestination: AvoCustomDestination, debugger: NSObject,
strict: Bool = false, noop: Bool = false)
  • env - Avo is designed to act differently based on the environment. Supported values are development, staging and production. Most important differences include sending data to different projects in your analytics (to not pollute production data with dev/testing session) and ability to crash the app in development if Avo detects an error in the analytics.
  • system properties - this are properties designed to be sent with each event. You need to provide them during initialization. You can update their values later with the setSystemProperties method.
Swift
Copy
1
2
3
4
5
6
public static func setSystemProperties(
requiredStringSystemProperty: RequiredStringSystemProperty,
optionalStringSystemProperty: OptionalStringSystemProperty?,
requiredIntSystemProperty: Int,
optionalFloatSystemProperty: Double?,
optionalListSystemProperty: [Int]?);

Read more about property types later in the Avo Functions section.

  • custom destinations - there are a few ways to manage how actual calls to analytics libraries are done. One is to allow Avo to manage everything, in that case you only need to add tracking library dependency to your project. Another is to manage the calls yourself, using a Custom Destination. It's basically a callback Avo will make after an Avo function is triggered. In that case Avo does all the validations and enrichment, but you take care of passing the data to your analytics destination. There is a third advanced mixed option, where you provide an initialized SDK to Avo, contact us if you want to explore it.
  • debugger - mobile platforms (Android, iOS, React Native) support the Avo Analytics Debuggers, provide one here if you want to connect it to Avo events. You don't need to provide the debugger on the web to use it there.
  • strict - if this is set to true Avo would crash your app in the development environment when it sees errors. Note that the Avo validation will never crash your app if env is set to Production.
  • noop - if this flag is set to true Avo won't make any network calls (no tracking) in development and staging environments. Note that the noop flag is dismissed in production.
  • destination options (not shown in the example) - some platforms, for example JavaScript, allow you to provide destination options for the analytics destinations that will be passed during tracking SDK initialization. This allows you to pass config/options through Avo to the init functions of the underlying analytics SDKs.
  • avoLogger (not shown in the example) - custom logger implementation to control logs logic. Can be used to disable logs. More about custom loggers.
  • inspector (not shown in the example) - instance of the Avo Inspector to automatically send Avo functions data to the Avo Inspector (on the supported platforms)

What's inside the initAvo method / Avo constructor?

There are a few things happening, like setting the flags, system properties and reporting implementation status in development mode to the Avo servers, so you can check whether the event has ever been successfully invoked in development, on the Avo website.

But the most important part is initialization of the analytics destinations. Depending on the setup of your tracking plan you might see something like

Swift
Copy
1
2
3
4
5
6
7
8
9
10
11
12
if __ENV__ == .prod {
amplitudeDestination = AmplitudeDestination()
amplitudeDestination?.make("production Amplitude key is automatically here based on the data you provided in the tracking plan"")
mixpanelDestination = MixpanelDestination()
mixpanelDestination?.make("production Mixpanel key is automatically here based on the data you provided in the tracking plan"")
}
if __ENV__ == .dev {
amplitudeDestination = AmplitudeDestination()
amplitudeDestination?.make("development Amplitude key is automatically here based on the data you provided in the tracking plan"")
mixpanelDestination = MixpanelDestination()
mixpanelDestination?.make("development Mixpanel key is automatically here based on the data you provided in the tracking plan"")
}

The keys are set up in the Avo web UI and automatically fetched by the codegen, so you don't need to do anything here, just know that it is there. The API keys in Avo are version controlled, and synced across all your sources, this way you don't need to worry if correct API key is being used or not, Avo takes care of it for you.

Avo Functions

Avo functions are actual callable functions generated for events defined in your tracking plan.

On some server platforms Avo functions are async, e.g. on Node.js they return a Promise and on C# you can await on the Avo Functions

For example if you have an event defined with name App Opened connected to your source, the Avo functions interface will look like

Swift
Copy
1
2
3
4
5
6
7
8
/**
App Opened: This event is sent immediately after the user opens the app [This description is defined in the Tracking Plan on avo.app]
- SeeAlso: [App Opened](https://www.avo.app/link-to-the-event-in-your-tracking-plan)
*/
public func App Opened(
beverage: Beverage,
userId: String?) {...}

Property types

At this point you would notice that the parameters can have various types.

There are a few dimensions in which the parameters can differ:

Type

Avo supports the following types: string, int, float, bool and object.

Single value / list

You would be required to provide a list of things if the property is marked as a list.

Required / Optional

In Avo we made a design decision that you always need to provide a value of a property, even if it is optional to mitigate the risk of accidentally not providing the value and damaging the data. But if the property is marked as optional you would be able to provide null as it's value and it won't be sent to your analytics tool.

Value Constraints

You can notice that in the example above the first argument is not a String, but some other type. It happens because there is a constraint specified in the tracking plan that limits the possible values of this parameter. Avo has generated an enum for you:

Swift
Copy
1
2
3
4
5
6
public enum Beverage : String {
case beer = "beer"
case wine = "wine"
case cocktail = "cocktail"
case water = "water"
}

The second parameter has no constraints, so it is a String (though it is optional, because it's marked as optional in the tracking plan).

In general, constraints can be added to properties to validate against:

string properties: a finite list of allowed string values int and float properties: a minimum and maximum value object properties: exact structure of the allowed keys and types of values in the object

Avo can add helper parameters that are not defined in the tracking plan, but are known to be required to make that specific call, for example in server environments Avo usually adds a userId parameter to each function, so server can provide user id to the analytics tracking services.

What's happening inside the Avo Functions

Avo makes sure that the provided properties are correct according to the tracking plan with runtime validations, development invocations are reported to Avo servers so you can check the invocation status on the Avo website. If Avo Inspector is connected, the event shape is sent there. Read more about Avo Inspector and how to connect it to the Avo generated code. The last step is actually sending the provided data to all the configured tracking destinations. There are different classes of events that can be tracked: identify, log event, update user properties, unidentify, log revenue, log page view, but everything is handled inside the Avo file and delivered in a correct form to your destinations. If you are using a Custom Destination the data will be provided to the callback object you supplied during initialization. Tracking code would look similar to this:

Swift
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// destination Amplitude
var amplitudeDestinationEventProperties: [String: Any] = [:]
// amplitudeDestinationEventProperties will be populated with all the provided properties to this Avo Function and all the system properties.
amplitudeDestination?.logEvent("Logged Out", amplitudeDestinationEventProperties)
amplitudeDestination?.unidentify()
// destination Mixpanel
var mixpanelDestinationEventProperties: [String: Any] = [:]
// mixpanelDestinationEventProperties will be populated with all the provided properties to this Avo Function and all the system properties.
mixpanelDestination?.logEvent("Logged Out", mixpanelDestinationEventProperties)
mixpanelDestination?.unidentify()
// destination Custom dest
var customDestEventProperties: [String: Any] = [:]
// customDestEventProperties will be populated with all the provided properies to this Avo Function and all the system properties.
customDest.logEvent(eventName: "Logged Out", eventProperties: customDestEventProperties)
customDest.unidentify()

This particular event has log event and unidentify actions. The methods called above will vary based on the actions of the given event. Read more about actions here.

Data Validations

Avo helps developers avoid mistakes when implementing analytics, it makes sure that your code does exactly what it is supposed to do based on the tracking plan defined in Avo, in two ways.

First, Avo is utilizing the type system of a programming language you work with. For example in the code snippet above the optional properties are supported on the language level in Swift, so we make the interface in a way you can't provide null values where they are not expected. We try to do as many validations as possible this way, so they are performed in the compile time.

Second, it's runtime validations. In cases when the language does not provide us with tool to check in the compile time, like in JavaScript, we do runtime checks, looking like this

JavaScript
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assertString: function assertString(propertyId, propName, str) {
if (typeof str !== 'string') {
var message =
propName +
' should be of type string but you provided type ' +
typeof str +
' with value ' +
JSON.stringify(str);
return [
{
tag: 'expectedStringType',
propertyId: propertyId,
message: message,
actualType: typeof str,
},
];
} else {
return [];
}
}

A check like this is run when the Avo function is triggered. Then the message is delivered to the logs, debugger or as a crash in the strict mode depending on your setup.