Skip to content

schibsted/pulse-sdk-android

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

7 Commits
ย 
ย 

Repository files navigation

Android Pulse Tracker

Build Status

The most recent production version is 9.2.7.

Please do not use this SDK in production without:

You can find Javadoc at https://pages.github.schibsted.io/spt-dataanalytics/event-sdk-android/javadoc/index.html

Table of Contents

Getting Started

If your are using Pulse for the first time, we recommend reading the Introduction to Pulse.
If you are looking to integrate with Pulse and start capturing data, see our Integration Guide

Understand the Purpose of event-formats

All events must comply with event definitions that can be found in event-formats repository. These definitions were developed because there are many web and mobile applications in Schibsted that send usage reports to data stakeholders (analysts, data scientists, etc). Having a common event format lets them gather and process this data easier.

For example, imagine we have two apps: "A" and "B". Both apps want to send an event when user signs in. The app "A" sends:

"event":"sign-in"
"account":"[email protected]"

The app "B" sends:

"action":"log-in"
"actor":"[email protected]"

As you can see, though the meaning of these messages is the same, their form is different. This can be a huge problem if you want to share events with other entities within Schibsted, especially if they have many more fields and not 2 but 20 apps that send them.

event-formats repository is a joint effort to standardise event formats and facilitate easy exchange of data between stakeholders. Event and object definitions there are described using json schema.

It is very important that you understand what those definitions are and how to build events according to the schemas.

There is also event-formats-jvm repository. It is supposed to be JVM implementation of those definitions. Unfortunately, as of today it has some problems that prevent developers from using it fully. We have plans to refactor and improve it. Until then, however, using event-formats-jvm is discouraged.

Since version 7, you can use this SDK without a dependency on event-formats-jvm.

This SDK is designed to work in multithreaded environment. Internally it uses 2 threads.

Setup Schibsted Repository

The SDK is released on Schibsted artifactory and Sonatype.

To use Schibsted artifactory, you need to include this repository with its credentials (you might already have it):

val artifactoryContext = properties["artifactory_context"]?.toString() ?: System.getenv("ARTIFACTORY_CONTEXT")
maven {
    url = uri("$artifactoryContext/libs-release")
    credentials {
        username = properties["artifactory_user"]?.toString() ?: System.getenv("ARTIFACTORY_USER")
        password = properties["artifactory_pwd"]?.toString() ?: System.getenv("ARTIFACTORY_PWD")
    }
}

You can either set the credentials as environment variables:

export [email protected]
export ARTIFACTORY_PWD=<artifactory-password>
export ARTIFACTORY_CONTEXT=https://artifacts.schibsted.io/artifactory

Or as gradle properties, suggested at ~/.gradle/gradle.properties:

[email protected]
artifactory_pwd=<artifactory-password>
artifactory_context=https://artifacts.schibsted.io/artifactory

Sonatype artifact is available from mavenCentral() repository.

Add event-sdk-android Library

Once Schibsted artifactory is set up, add the following line to your build.gradle:

implementation("com.schibsted.spt.tracking:event-sdk-android:<version>")

Since version 7 this SDK doesn't require apps to use event-formats-jvm. All objects and events (defined in event-formats repository) can be built without it. If you still want to use it, you can add the following line to your build.gradle:

implementation("com.schibsted:event-formats-jvm:<version>")

Google Play Services

The SDK uses Google Play services to fetch advertising ID and location. Currently it is build with version 18.0.1 AdIdentifier and 21.0.1 Location. The SDK doesn't contain any version-specific code. If you use a newer version of Google Play services, Gradle should be able to automatically upgrade this dependency. If you want use an older version of Google Play Services, you might want to exclude it from the SDK. In this case make sure that both play-services-ads and play-services-location are explicitly included in your build.gradle:

implementation("com.schibsted.spt.tracking:event-sdk-android:<version>") {
    exclude(group = "com.google.android.gms")
}
implementation("com.google.android.gms:play-services-ads:<your-gp-services-version>")
implementation("com.google.android.gms:play-services-location:<your-gp-services-version>")

Since version 7 you can provide custom AdvertisingIdProvider and LocationProvider. This way you can have finer control over when and how to fetch these values, and even use this SDK on devices without Google Play services at all:

AdvertisingIdProvider customAdvertisingIdProvider = ...;
LocationProvider customLocationProvider = ...;
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
        .advertisingIdProvider(customAdvertisingIdProvider)
        .locationProvider(customLocationProvider)
        ... // other stuff
        .build();
// Now you may exclude "com.google.android.gms" from dependencies

Other Dependencies

This SDK uses Gson to build and modify events.

Room architecture component is used for database operations. Note, Room v1.1.0 has a huge bug that renders database unusable after migrations. DO NOT USE Room v1.1.0! At least 1.1.1-rc1 or 1.1.1 is absolutely required (these versions are identical).

LiveData architecture component is used to expose environment ID, JWE token and other details your app might need.

This SDK uses the popular Retrofit 2, OkHttp3 and Okio for networking.

Permissions

This SDK will NEVER request any permissions directly. Client applications must declare them in their manifest and ask user to grant them in runtime.

Some permissions are vital for this SDK. They are:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

They are obviously required for networking.

Some actions, like fetching device's location, require additional permissions. If they are denied, the respective features will be disabled (for example, location details will not be included in events). Very likely, you will want to add them to the Manifest:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Proguard

The library (in the aar format) includes consumerProguardFiles which means that you do not need to set Proguard settings for this library manually.

API Overview

Our public API consists of these classes/interfaces:

  • PulseEnvironment (javadoc) provides details about device, OS, tracker itself, client application, location, advertising ID, etc. Basically, it is a description of environment this SDK runs in.
  • GlobalPulseTracker (javadoc) contains methods that affect all tracker instances immediately: reset environment ID, enable or disable tracking, set user ID. etc.
  • PulseTracker (javadoc) contains the core API that builds and posts events.
  • PulseTrackerFactory (javadoc) creates instances of PulseTracker using provided PulseEnvironment.
  • Transform (javadoc) is the core abstraction for manipulating an event: you can add, change or remove properties of an event. By calling PulseTracker.update(Transform) you create new instance of PulseTracker that will apply this transform to every event it tracks. You don't have to use transforms; you can use use Gson to create your events instead.

We consider example app a part of documentation. It contains multiple classes that demonstrate how to create PulseEnvironment, initialize PulseTracker, manipulate and post events. It has many comments that explain almost every line.

Internal Packages

We tried to separate internal packages (com.schibsted.pulse.tracker.internal) from public API. Unfortunately, at the moment it is impossible to exclude external dependencies from compile classpath while leaving them in the runtime classpath. Until version 4.6 Gradle doesn't respect compile-runtime separation for JARs, and for AARs it won't be respected at least till version 5.0. Please, don't ever use them.

Workflow

The whole process of setting up and using this SDK is following:

  • Create an instance of PulseEnvironment using PulseEnvironment.Builder. Very likely, all you need for production builds is:

    PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, appClientId)
            .build();

    For development builds, you will probably want to enable debug features, such as HTTP logging, faster dispatches, dev deploy stage, etc. Please, study PulseTrackerProvider class in the example app to see how to configure PulseEnvironment.

    The SDK also enables you to use different environments to send events and CIS requests. While developing you dont want to bloat production environments with not-real user data. To do so simply pass environment name as in example ChangeEnvironmentExample

    builder.setConfigurationOption(ConfigurationOptions.CONFIGURATION_NAME, "development");
    builder.setConfigurationOption(ConfigurationOptions.CONFIGURATION_NAME, "production");
    builder.setConfigurationOption(ConfigurationOptions.CONFIGURATION_NAME, "preproduction");

    We strongly suggest to create it in Application.onCreate(). Once PulseEnvironment is created, DON'T EVER ATTEMPT to create a different PulseEnvironment! You MUST NEVER use different PulseEnvironment instances in the same app.

  • Create an instance of PulseTracker using PulseTrackerFactory:

    PulseTracker rootTracker = PulseTrackerFactory.create(pulseEnvironment);

    Since version 7 you don't have to keep it as singleton. You can have as many instances as you want.

  • Now, configure the global state in GlobalPulseTracker:

    GlobalPulseTracker global = rootTracker.global();
    global.enableTracking(true);
    global.setUserAnonymous();
    // etc.
  • Now that you have your root tracker instance, you can create child trackers with their associated Transforms. Transforms add, change or remove properties from event's JsonObject. You can add a Transform to a PulseTracker once, and then use this PulseTracker to send multiple events. The Transform will be applied on each event posted to this PulseTracker instance.

    It is very likely that you will want to have a number of properties added to every event you send. Please, take a look at TrackerEventProperties in our example app. It does exactly that: adds deployStage, deployTag, provider, @id, actor, schema, creationDate, device, tracker, location and session to each posted event.

    The output of Transforms is a regular JsonObject with key-value pairs in it.

  • Finally, you post events. You can build an event using Transforms only, or using JsonObject alone, or you can combine them both. To post an event, call PulseTracker.track(JsonObject) method. If you pass JsonObject into it, its properties will be copied into the JsonObject built by Transforms. Please, note the sequence: first Transforms add their properties, then properties from your JsonObject are copied on top of them. Prior to version 7 the sequence was opposite.

    Please, take a look at LaunchEventSender in the example app to see how we send a simple "Launch" event. There are also multiple other examples there that teach different ways of constructing events with Transforms and Gson.

Fetching remote configuration

When the PulseTracker instance is created, the first thing it does is fetching a configuration file from a remote server. This configuration file contains up-to-date URLs for CIS and DataCollector. The location of the file is https://sdkconfig.pulse.schibsted.com/schibsted_default/config/.

Changing Organization Name

NB! At the time of writing, any values other than schibsted are not supported by the schema. This feature exists to make this SDK "future-proof".

Organization name is used to create SDRN-formatted identifiers (you can see them in every event). For example, it's schibsted in sdrn:schibsted:client:<some-client-id>. Your app can specify different organization name:

PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, appClientId)
        .organization("adevinta") // default value is "schibsted"
        .build();

Now, your SDRN will look like sdrn:adevinta:client:<some-client-id>.

Allow Tracking

Applications must explicitly allow or prohibit tracking. If your app posts events before tracking is enabled, these events will be deleted without dispatching. The same goes for events that are posted when tracking is prohibited:

tracker.global().enableTracking(true);

Even if tracking is prohibited, we still communicate with CIS servers in order to fetch environment ID and JWE token. They are important not only for this SDK but for other components at Schibsted. CIS servers are stateless and don't store any data.

Update User Details

A complete user ID consists of two parts: login system and unique ID within this system. To combine them into SDRN-formatted string, you can use Helpers. When application starts or when user signs in/out, apps are expected to notify GlobalPulseTracker about it:

tracker.global().setUserIdentified("<sdrn-formatted-user-id>");
// or
tracker.global().setUserAnonymous();

These calls will eventually lead to network calls to CIS. CIS will generate JWE token that will be used when dispatching events to the DataCollector.

Android Advertising ID

The Android Advertising ID provided by Google Play services acts as unique identifier of a device. It is reported to CIS and is used to generate environment ID. This SDK comes with a built-in advertising ID provider that fetches the value from Advertising ID Client from Google Play services.

It is possible to set a custom AdvertisingIdProvider. You might want to do that if your app can be installed on devices without Google Play services. Set your custom implementation using PulseEnvironment.Builder (see Google Play services section of this README).

Important! Google Play store has special requirements for apps that use advertising ID. Your app must specify a Privacy Policy, in particular. Please, carefully review the current rules. You can start in Play Console Help.

Location Tracking

Your events may include Location. This SDK comes with a built-in location provider that uses Fused Location Provider API from Google Play Services.

Google Play Store will detect this background location code and require you to declare it. When removing the Google Play Services dependency, the location code will not be invoked. Reach out to the Pulse team in #pulse-support (Slack) for any questions and feature requests relating to Location.

It is possible to set a custom LocationProvider. You might want to do that if your app can be installed on devices without Google Play services. Set your custom implementation using PulseEnvironment.Builder (see Google Play services section of this README) and attach it to the posted events, for example, using Transforms (see TrackerEventProperties).

Only three digits detail after the decimal point is kept of the mentioned coordinates, for privacy reasons.

Consents

The Pulse SDK supports adding consent information to all Pulse events. tracker.global().setConsents(consents) adds the user's consents (opt-ins and opt-outs) to data processing for specific purposes, e.g. personalization. These consent purposes are used to enrich Pulse events with the consent choices made by the current user. This is legally required in some markets.

You must at all times provide valid consents. If the user modifies their consents you must pass the updated values to this SDK, using this method.

tracker.global().setConsents(consents);

Should the values be unknown or (temporarily) unavailable to your app, pass ConsentStatus.UNKNOWN, if your app is required to always provide consent information on events.

In case consents can be modified externally to the app, e.g. on a web page: do make sure that when the app comes into foreground, you request up-to-date information on consents from the Consent Management Platform, e.g SourcePoint, if not done automatically, and pass on the consents to this SDK (always).

A consent purpose consists of a category and an id, where category is a textual representation of a consent purpose. The purpose categories are pre-defined. The id is optional and can be used to identify the consent purpose from the Consent Management Platform (CMP).

Use the optional ConsentSource parameter to specify if the values are defaults or cached. Possible values are CMP,CACHED,DEFAULT see ConsentSource

Example

The code example below shows a valid Consent object passed through the tracker.global().setConsents(consents) method.

Consents consents = (new Consents(
                new ConsentPurposes(
                        new ConsentPurpose(ConsentStatus.ACCEPTED, "1"), // CMP_ANALYTICS
                        new ConsentPurpose(ConsentStatus.ACCEPTED), // CMP_MARKETING
                        new ConsentPurpose(ConsentStatus.ACCEPTED), // CMP_ADVERTISING
                        new ConsentPurpose(ConsentStatus.REJECTED) // CMP_PERSONALIZATION
                ),
                ConsentSource.CACHE
        ));

tracker.global().setConsents(consents);

See how to set and use the Consents methods in the example code.

WARNING: Be aware that the SDK is not checking the validity of the data you provide through the tracker.global().setConsents() method. You are responsible for verifying that the data you provide through this interface is legally obtained and otherwise valid, this SDK will only transport the consent values, not validate them.

Require opt-in to use targeted advertising

If the legislation for your area requires you to have an opt-in to data processing for advertising purposes from the end-user before performing targeted advertising, enable the AdvertisingOptIn mode by calling PulseEnvironment.Builder.requireAdvertisingOptIn(true); when configuring the Pulse SDK in your app. This SDK will then not provide ad identifiers through its API, and not store ad identifiers on the device.

Consent synchronisation for Hybrid Mode

When opening web pages coming from Schibsted organisation (using Hybrid Mode) it is expected that the application uses the latest of user consents. It is important in order to avoid privacy issues and having always the content accurate to users decision. That is why, while implementing WebView you should always make sure that:

  1. You are applying official SourcePoint documentation for synchronising consents.
  2. You are always attaching consent changes into the all the opened WebViews by:
    • Using available SourcePoint methods whenever possible.
    • Recreating the Webview object when consents change, if you are unable to update the Webview objects in any other way.

Posting Events

When you track an event using PulseTracker.track(JsonObject), the JsonObject is first modified by adding the Consent key+values if a ConsentProvider is set and it returns data. After that it will be constructed by Transforms. Then it's merged with the JsonObject that you passed into PulseTracker.track(JsonObject) method. It is then serialized into a json string and saved into a local database.

Then another component periodically fetches new events from the database, updates their "special" values (like environment ID, JWE token, user ID, event ID, etc.) and dispatches them to the DataCollector. We try to batch events together to save network calls, when possible. Since 7.3.0 we also compress these batches with gzip if size of a batch exceeds 800 bytes.

In production mode events are dispatched every 2 minutes. If network is not available, events will be dispatched when connection is re-established, or saved till the next session. When an event is successfully dispatched, it is deleted from database.

This SDK keeps maximum 1_000 events in database.

The DataCollector API will not accept POST requests larger then 100_000 bytes. For this reason this SDK will silently discard events that are longer then 100_000 bytes when serialized into json string.

Sessions

Schema version 228 added new property to tracker events - "session". The purpose is to track a continuous period of user activity. Each tracker event within the session window should hold a reference to that session. To add sessions to your events, just set the "session" property to placeholder. Pulse Tracker will populate it with a proper session object before saving the event to database:

JsonObject event = ...;
event.addProperty("session", com.schibsted.pulse.tracker.JsonObjectFactories.PLACEHOLDER);

We strongly recommend to upgrade schema for all your tracker-events to at least: schema 228 "http://schema.schibsted.com/events/tracker-event.json/228.json" and your engagement-events to schema 254 "http://schema.schibsted.com/events/engagement-event.json/254.json". Lower versions don't support sessions.

Xandr Ad Identifiers PPID

DEPRECATED - will be removed soon!

For observing Xandr Ad Identifiers (PPID) values (PPID1, PPID2, AdId) you should use getXandrAdIdentifiersLiveData() in PulseEnvironment via observing their LiveData representation.

tracker.getPulseEnvironment().getXandrAdIdentifiersLiveData();

Advertising Identifiers including PPID

To start observing Advertising Identifiers values (PPID1,PPID2, AdId) you should specify advertising vendors you want to request.

builder.advertisingVendors(List.of(AdvertisingVendors.XANDR, AdvertisingVendors.ADFORM, AdvertisingVendors.DELTA));

For observing Advertising Identifiers you should use getAdvertisingIdentifiersLiveData() in PulseEnvironment via observing their LiveData<AdvertisingIdentifiers> representation.

tracker.getPulseEnvironment().getAdvertisingIdentifiersLiveData();

Pulse Hybrid Mode

If your application opens any site tracked by the Pulse Web SDK inside WebView, you should integrate pulse tracker hybrid mode with your WebView so you can natively track events and utilise native JWE Token.

    PulseHybridWebController webController = new PulseHybridWebController();
    tracker.setupHybridMode(webView,
        webController,
        true,
        json -> trackEventFromWebView(json);
        );

example

Optional "Common_Transforms" Dependency

Since version 7.6.0 we publish a new optional artifact:

implementation("com.schibsted.spt.tracking:common_transforms:<version>")

So far, it contains only one class: TrackerEventProperties. Essentially, it is a drop-in replacement for CommonProperties class from our example app that you no longer need to write yourselves. If added to your tracker instance, it will add a number of most common properties to all tracked events: "@id", "schema", "deployStage", "deployTag", "provider", "actor", "creationDate", "device", "tracker", "location" and "session". Of course, you can overwrite these values, as usual.

To use TrackerEventProperties, just create its instance and update your tracker:

Transform trackerEventProperties = new TrackerEventProperties();
PulseTracker trackerWithTransform = tracker.update(trackerEventProperties);

trackerWithTransform will now be able to automatically add the common properties to all events it tracks.

Observing Environment ID, JWE Token, etc.

You can observe environment ID, JWE token and a few other changing properties using LiveData objects exposed from PulseEnvironment. You must remember that both environment ID and JWE token are assigned by CIS server and require a successful network call. Network is not always available on mobile devices. When environment ID is reset it might take significant time until we actually obtain the new value from the server and post it to LiveData. The same goes for JWE token. Its value changes every time environment ID is reset or when user signs in/out. It might take long time until LiveData is updated with the correct values.

You can see how this work in the example app. Start the application, note environment ID, JWE token, etc, turn off internet and press "Reset Environment ID". See that the displayed values don't change. Turn internet back on, and see when they are updated.

This SDK can correctly work in these constraints and use accurate values when dispatching events. But there is no guarantee that third-party SDKs can handle it properly.

Debugging

This SDK provides a number features for development and debugging purposes. The previous versions used PulseEnvironment.Builder.debug(boolean) flag to enable them all at once. Version 7.1.0 introduced more modular controls that will allow developers to tailor SDK's behavior to their needs: so called configuration options. We plan to increase the number of available configuration options in future releases.

You can see how to use them in our example app. The following code will emulate the behavior of now deprecated PulseEnvironment.Builder.debug(boolean) flag:

PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
        .setConfigurationOption(ConfigurationOptions.INTERVAL_BETWEEN_DISPATCHES,
                TimeUnit.SECONDS.toMillis(10))
        ... // other stuff
        .build();

Since version 7.5.1 we have changed the recommended way to log HTTP communication. Instead of setting a configuration option (the old way), initialize and configure your own instance of HttpLoggingInterceptor, and add it to PulseEnvironment, for example:

Interceptor httpLogger = new HttpLoggingInterceptor()
        .setLevel(HttpLoggingInterceptor.Level.BODY);
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
        .addInterceptor(httpLogger)
        ... // other stuff
        .build();

Please, note that HTTP logging requires additional dependency added to your build.gradle:

implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")

In development or testing builds you might also want to add deployStage and deployTag properties to your events. deployStage lets you separate development events from the production ones, and deployTag adds additional possibilities for filtering and examining events in Pulse Console. To set deploy stage and tag, first add them to PulseEnvironment:

PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
        .deployStage("dev") // or "pro", or "pre"
        .deployTag("<some-custom-tag>")
        ... // other stuff
        .build();

Note, setting deployStage and deployTag in PulseEnvironment alone won't add deployStage and deployTag properties to your events. You also need to fetch these values from PulseEnvironment and add them to your events directly. If you use common_transforms artifact (added in 7.6.0), it will add them for you.

You might ask why we have these methods in PulseEnvironment anyway? Because other SDKs at Schibsted also do their own logging. If you use those SDKs, they need to know the proper deploy stage and tag as well, and PulseEnvironment is the place to find them.

Advanced Testing Features

You can now change SDK's behavior so that can be easily tested on emulators or actual devices:

PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
        .setConfigurationOption(ConfigurationOptions.ENABLE_ADVANCED_TESTING, true)
        ... // other stuff
        .build();

The effects are:

  • PulseTracker doesn't use background threads and runs synchronously;
  • in-memory database is used instead of a usual on-disk database;
  • all instances returned by PulseTrackerFactory are fully isolated from each other;
  • tracker does not observe connectivity changes and does not try to re-schedule failed network operations to a later time.

A few other useful ConfigurationOptions:

  • CONFIGURATION_URL, CIS_URL and DATA_COLLECTOR_URL override URLs specified in PulseEnvironment.Builder or in configuration file.
  • By default tracker uses default configuration in configuration file. You can now make it choose a different one by setting CONFIGURATION_NAME.

Testing Tools

We have created a set of tools that simplify PulseTracker testing and checking if your events are valid and comply to a schema:

androidTestImplementation("com.schibsted.spt.tracking:testing:<version>")

Please, take a look at README in Testing package to learn more. Testing tools are available since v7.3.0.

Notes for SDKs that Depend on PulseTracker

SDKs MUST NOT create a new instance of PulseEnvironment. Instead, request your client apps to provide it. Alternatively, request an instance of PulseTracker and fetch a PulseEnvironment from it:

PulseEnvironment env = pulseTrackerProvidedByClientApp.getPulseEnvironment();

SDKs MUST NOT call methods in GlobalPulseTracker unless you know what you do. The reason is every call to GlobalPulseTracker affects all PulseTracker instances in the app. Calling them may lead to unexpected consequences.

SDKs should suggest to client apps to create an instance of PulseTracker and call its global() methods even if they do not use Pulse SDKs themselves:

pulseTracker.global().enableTracking(true);
pulseTracker.global().setUserIdentified(userId);
pulseTracker.global().setUserAnonymous();
// etc.

Otherwise events may not be dispatched to DataCollector or may contain incorrect information.

We recommend that SDKs use app's PulseEnvironment instance to create a new PulseTracker for their own needs. Re-using app's PulseTracker is not advisable because it may contain transforms that will mess up your events.

Notes about Multi-processing

Pulse SDKs are designed to work in multi-threaded environments but multi-processing is completely different story. Every process will initialize its own Application and its own PulseTracker. But all of them will attempt to operate with the same database file. This will certainly prevent Pulse SDK from working properly and make database inconsistent.

What are the symptoms of the problem? If you see exceptions saying that database is locked, or returned null instead of non-null, or migration failed (for example, "column X already exists"), and so on - these errors mean you spawned multiple processes that modify the same database simultaneously.

If you actually need to use multiple processes, you will have to implement a ContentProvider that wraps Pulse SDK (or implement another kind of IPC).

Some libraries can also spawn processes "behind the scenes". One popular example is LeakCanary. If you use LeakCanary, you have to check whether your Application runs in analyzer process (by calling LeakCanary.isInAnalyzerProcess(Context)) and prevent it from initializing your app. See LeakCanary docs.

Common Mistakes

  • Attaching user ID to your event (outside of actor property) a HUGE mistake. It makes it hard to anonymize such data, and may lead to violation of GDPR and other laws.
  • Some event require lists of objects to be attached. There is no limitation on how many items such lists may hold. You must be careful though: they might quickly grow out of control. We have seen apps that attempted to send megabytes-long events because they forgot to manage list size, and added hundreds or thousands items there.

Migration from 7.5.1 to 7.6.0

At this point sessions are optional. You can add them your events manually or by using optional common_transforms dependency.

You can add sessions to your events on your own by adding "session" property to your events:

event.addProperty("session", JsonObjectFactories.PLACEHOLDER);

Before saving the event to database, tracker will replace the placeholder with the actual session object.

If you decided to add sessions, you also need to your tracker-events to schema 228 and your engagement-events to schema 254.

If you decided to use optional common_transforms dependency, take a look at its source code. It is very likely that it duplicates some parts of your own code.

Contact

In case of a Pulse SDK related issue we expect to keep an open communication on our Slack channels:

When, for technical reasons Slack cannot work for you, please make sure to use GitHub issues link instead. We will provide all the necessary answers within the relevant Slack threads (or in Github issue when your case is inserted there). Thank you for your cooperation!

Other

See the tagging guides in Pulse Console/Tagging Plans