# Introduction

The Sentiance Platform is a mobile intelligence solution that transforms real-world behavioral data into actionable insights. By leveraging advanced on-device technology, Sentiance enables organizations to better understand user mobility patterns, lifestyle behaviors, and contextual interactions, all while maintaining strong privacy standards.

At its core, the platform combines a powerful Mobile SDK with data models and Cloud-based Analytical tools. Together, they allow you to seamlessly integrate behavioral intelligence into your mobile application and gain meaningful, real-time insights about your users.

***

## Why choose the Sentiance Platform

Our Platform and SDK is designed with both developers and end users in mind. \
It delivers high-quality insights while remaining lightweight, efficient, and privacy-first.

#### Key Benefits

* **Cross-Platform Support**\
  Available on all major mobile platforms, with seamless native integration for iOS and Android, or through a hybrid approach using Flutter or React Native.
* **Easy & Fast Integration**\
  Developer-friendly architecture and clear documentation ensure a smooth setup process.
* **Accuracy & Battery Efficiency**\
  Advanced on-device processing provides highly accurate insights while minimizing battery consumption.
* **Optimized Data Collection**\
  Smart data collection strategies ensure that data is processed as efficient as possible.
* **Lower Operational Costs**\
  OnDevice intelligence reduces backend processing and infrastructure costs.
* **Privacy by Design**\
  User privacy is a top priority. Data processing is optimized to maximize privacy and control.

***

## Sentiance Platform Essentials

Before integrating the Sentiance Platform into your mobile application, it is important to understand its core building blocks. Familiarity with these essential concepts will make the integration process straightforward and help you implement features in the most effective way for your specific use case.

{% columns %}
{% column width="41.66666666666667%" %}

#### [Features Catalog](/getting-started/features-catalog)

Start by exploring the platform’s feature set and capabilities.
{% endcolumn %}

{% column width="58.33333333333333%" %}

* What insights are available
* How each feature works
* How the insights can be used in your application
  {% endcolumn %}
  {% endcolumns %}

{% columns %}
{% column width="41.66666666666667%" %}

#### [The Sentiance User](/sdk/appendix/user-creation-and-management)

How your users are linked to the Sentiance platform
{% endcolumn %}

{% column width="58.33333333333333%" %}

* How users are created and managed
* How users are linked to your internal systems
* How devices are associated with users
  {% endcolumn %}
  {% endcolumns %}

{% columns %}
{% column width="41.66666666666667%" %}

#### [Insights Control Tower](/getting-started/insights-control-tower)&#x20;

Dashboard for your Sentiance application
{% endcolumn %}

{% column width="58.33333333333333%" %}

* Business users
* Developer actions
* Analytics and data
  {% endcolumn %}
  {% endcolumns %}

{% columns %}
{% column width="41.66666666666667%" %}

#### [SDK Deep Dive](/sdk/appendix)

technical details of the Sentiance platform
{% endcolumn %}

{% column width="58.33333333333333%" %}

* SDK Integration
* Implementing features
* Advanced concepts
* Best Practices
  {% endcolumn %}
  {% endcolumns %}


# Features Catalog

## **First things first**

Sentiance transforms smartphone sensor data into actionable insights, enabling you to build personalized applications with a strong focus on safety and sustainability.

The platform collects data from built-in mobile sensors such as:

* **GPS** for location and movement tracking
* **Accelerometer** for motion detection and activity patterns
* **Gyroscope** for orientation and rotational movement

These raw data streams are intelligently processed by advanced data science models running directly inside your application through the integrated Mobile SDK. The processed data is turned into Insights that you can directly access and interact with in your Mobile Application.

**At the heart of Sentiance Insights lies motion intelligence**.\
To unlock the full value of the platform, users need to be in movement while carrying their device. Motion enables the system to generate rich, contextual insights about the users behavior and mobility patterns.

## **Sentiance Terminology**

There are general terms that we will cover that are parts of the core building blocks of the Sentiance platform. lets cover these general terms

* **ICT**\
  Abbreviation of **Insights Control Tower** -> The dashboard of your Sentiance application
* **App**\
  The general name for the environment of your Sentiance account. All of your users and the processed data are associated with a single, specific app. This is the highest level of segregation
* **Detections**\
  Represent the Sentiance SDK's recognition of specific user activities or mobility patterns. Detections are generated by an active SDK instance collecting and processing sensor and location data.
* **Sentiance user**\
  A Sentiance user represents an individual from your platform within the Sentiance ecosystem.

  Each of your users is linked to Sentiance through a unique identifier from your business system. Once linked, the Sentiance platform assigns a unique **Sentiance User ID**
* **Business user**\
  The term describes an internal team member in your organization, such as an analyst, developer, or stakeholder. Unlike a Sentiance user, this refers to someone interacting with or working on your Sentiance Platform.&#x20;
* **Events**

  A structured record produced by the SDK that captures a meaningful occurrence such as a change in user behavior, movement, or context and makes it available for analysis.
* **Transports**\
  A transport is the detection of a user’s movement from one location to another.\
  It includes information about the trip, such as timing, behavior, waypoints and much more

These are the important terms that are used throughout the Sentiance Platform, there are more terms that are specific to features that you will encounter soon.

***

## Features overview

Processed data is transformed into structured insights across these main domains:

{% hint style="success" %}
Tap on individual links to instantly access detailed information about each feature.
{% endhint %}

{% columns fullWidth="true" %}
{% column width="33.33333333333333%" %}

#### [Driving Insights](/getting-started/features-catalog/driving-insights)

Full insights about car and motorcycle transport, provides real-time feedback on driving patterns, risky behaviors, and overall safety on the road.
{% endcolumn %}

{% column width="33.333333333333336%" %}

#### [Mobility Insights](/getting-started/features-catalog/mobility-insights)

Identification and classification of all transportation used by the user, including walking, biking, public transport, and driving.&#x20;
{% endcolumn %}

{% column width="33.333333333333336%" %}

#### [Lifestyle Insights](/getting-started/features-catalog/lifestyle-insights)

Insights into the user’s \
lifestyle, profile, and potential interests. done by automatically analyzing behavior and movement patterns of the user&#x20;
{% endcolumn %}
{% endcolumns %}

{% columns fullWidth="true" %}
{% column width="33.33333333333333%" %}

#### [Crash Insights](/getting-started/features-catalog/crash-insights)

Provides real-time notifications and detailed information about crashes involving the user.
{% endcolumn %}

{% column width="33.333333333333336%" %}

#### [Smart Geofences](/getting-started/features-catalog/smart-geofences)

Real-time detection of when your users enter or exits custom predefined locations
{% endcolumn %}

{% column width="33.333333333333336%" %}

{% endcolumn %}
{% endcolumns %}

All of these behavioral and OnDevice features run directly on the users device, these ensure

* Maximum privacy,&#x20;
* Full data ownership
* Full control of sharing and synchronization to your own systems

#### [Engagement Platform](/getting-started/features-catalog/engagement)

Alongside our fully on-device features, the Sentiance Platform offers the **Engagement Platform**, enabling gamification and higher user engagement in your app with features such as:

* Streaks
* Challenges
* Badges
* Achievements
* Communication campaigns (e.g., push notifications)
* Social groups

The Engagement Platform operates server-to-server, allowing direct interaction with multiple users data and user groups.

#### [Insights Control Tower (ICT)](/getting-started/insights-control-tower)

The "dashboard" of your Sentiance application. As you enroll into the Sentiance Platform and start using our solution, you will get access to ICT for the management of your Sentiance apps. \
Including below features:

* Processed data analytics
* Sentiance Users management
* Business Users management
* Developer configuration
* And much more...

{% hint style="warning" %}
**Important note:** The Sentiance Platform collects, processes, and provides access to data for a limited period. Long term storage of collected data must be handled by your own application logic. \
**You remain the owner of the data.**
{% endhint %}


# Driving Insights

Learn how to understand your driving behavior and uncover insights to improve safety, efficiency, and overall performance.

## Introduction to Driving Insights

<div><figure><img src="/files/BJt1ywajWuaRcBNmZmoj" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/9B2fLz9gMSPlYdwURyfZ" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/NRGsYsVWNc8jHkm5zoU1" alt="" width="563"><figcaption></figcaption></figure></div>

Sentiance's Driving Insights is an advanced telematics solution designed to analyze driving behavior directly on users' smartphones.&#x20;

By leveraging smartphone sensor data, this technology provides real-time feedback on driving patterns, risky behaviors, and overall safety on the road without transmitting personal information off the device, ensuring user privacy.

## Features Overview

{% hint style="success" %}
All of these features and functionalities depend on the users movement and data collection by the SDK.&#x20;
{% endhint %}

{% stepper %}
{% step %}

#### **Driving details**

Includes information such as time of day, trip duration, distance, and a full record of the route taken, based on GPS waypoints. including the mode of transport.\
Find full list of driving details below.&#x20;

<details>

<summary><strong>Driving details</strong></summary>

* **Time**\
  Start and end time of the driving event.
* **Duration**\
  Time in minutes that the driving event took
* **Distance**\
  the total distance in kilometers
* **Complete trajectory**\
  complete collection of the route that the user took based on the GPS waypoints
* **Transport mode** \
  determines what type of mode the user used to transport from A to B\
  This can be one of the following modes:&#x20;
  * walk,&#x20;
  * bi-cycle,
  * run,&#x20;
  * car,&#x20;
  * motorbike,&#x20;
  * bus,&#x20;
  * train,&#x20;
  * tram,
  * idle<br>
* **Driver role** \
  indicates the role of the user, was this user the Driver or a Passenger

</details>
{% endstep %}

{% step %}

#### **Driving events**

Events collected during a trip are determined by driver behavior and vehicle motion. These events provide insights into driving patterns, form the basis for driving scores, and include additional relevant information. See below for the types of driving events that can be triggered.

<details>

<summary><strong>Driving events</strong></summary>

* **Harsh events:**&#x20;
  1. **Turning**

     This refers to the force that pushes a car to the side when making a sharp turn. It's the feeling of being pushed to the side in your seat as the car takes a curve quickly.
  2. **Braking**

     This refers to a sudden decrease in speed or a "hard brake" when driving a vehicle. It's like stepping on the brakes abruptly, causing the car to slow down quickly.
  3. **Accelerating**

     This refers to a sudden increase in speed or a "hard acceleration" when driving a vehicle. It's like stepping on the gas pedal quickly, causing the car to speed up rapidly.
* **Speeding event**\
  Measures adherence to speed limits by detecting when the driver surpasses speed limit of the road they are driving on. By default a 5 km/h buffer is applied.
* **Phone usage event**\
  Events predicted using ML models to detect active phone handling while driving
* **Call Event**\
  Indicates instances when any type(phone, whatsapp, facebook) of call was made during driving.
* **Wrong Way Driving event**\
  Indicates an instance when a driver has driven in the wrong direction in a one-way street.

</details>
{% endstep %}

{% step %}

#### **Driving scores**

The driving events are used to generate scores for the trip. These scores consist of multiple sub-scores that reflect different aspects of driving performance.\
See below for what type of scores can be generated and what they mean

<details>

<summary><strong>Driving scores</strong></summary>

Our platform uses a flexible scoring system that combines raw events, applies thresholds, and normalizes results at both trip and population levels.

When you start using the platform, you’ll receive a set of default scores. These have been optimized to be broadly applicable and practical.<br>

* **Overall**\
  Aggregated and averaged score calculated from all scores recorded for a driver over a period of time(7, 14 or 30 days)
* **Smooth**\
  The smooth driving score measures how smoothly you drive. High accelerations, heavy braking, and heavy turning result in a lower score.
* **Legal**\
  The legal driving score measures how well you adhere to speed limits. The higher your score, the more you respect the speed limits.
* **Focus**\
  The proportion of time that the user is focused while driving, being focused means: not using the phone.
* **Attention**\
  This score combines phone calls and phone usages during driving to determine attentiveness.
* **Call While Moving**\
  Scores the proportion of time that the user is on a call while driving their car at higher speeds.

</details>
{% endstep %}
{% endstepper %}

### Additional notes

* The SDK requires sufficient detection data to process and provide reliable insights. transports of at least 3 minutes in duration and/or 2 km (1.2 miles) in distance provide most accurate driving insights


# Mobility Insights

Learn how to gain insights into user movement and behavior from mobility data.

## Introduction to Mobility Insights

<div><figure><img src="/files/WcIzj5f9NdgPbVvu4NBw" alt=""><figcaption></figcaption></figure> <figure><img src="/files/IcxBCvkCPlxkYRpyiLPM" alt=""><figcaption></figcaption></figure> <figure><img src="/files/5PGYr30K2c47yuiQRvWL" alt=""><figcaption></figcaption></figure></div>

Our platform can help you understand how your users move around and travel using different modes of transportation. We can provide details about their mobility patterns, which means we can track how they go from one place to another, whether they use a car, bicycle, train, etc.

Mobility insights are derived from location and sensor data. This data is processed by ML models to identify the transport modes used during a trip from location A to location B.

A single trip may consist of multiple segments for example, biking to a train station, taking the train, and then a bus to the final destination. Each segment includes distance, duration, and additional contextual details, which together form the complete trip overview.

## Features Overview

{% stepper %}
{% step %}

#### **Transport Mode Detection**

Identifies the mode(s) of transport used during a trip. Multiple modes can be detected within a single journey, see below for a list of our official supported transport modes

* Walking
* Running
* Bicycle
* Motorcycle
* Car
* Bus
* Tram
* Metro
* Train
* Idle
  {% endstep %}

{% step %}

#### **Modal Split**

Aggregates total distance, duration, and number of trips per transport mode. This provides a clear overview of the user’s mobility patterns and insights into their preferred mode of transport
{% endstep %}

{% step %}

#### **Event Timeline**

Mobility Insights can detect when a user starts moving and when they become stationary. These events can be captured and made available in your application either in real time or retrieved on demand.

* **Transport Event**\
  Represents a moment when the user was in movement
* **Stationary Event**\
  Represents a moment when the user was not in movement or stopped moving
* **Off The Grid Event**\
  Represents a moment when the users movement status could not be determined due to different reasons:
  * Airplane mode was turned on
  * GPS or Sensors were not available
  * External system related reason
    {% endstep %}
    {% endstepper %}


# Lifestyle Insights

Learn real-world behaviors, habits, and lifestyles to power hyper-personalized experiences.

{% hint style="info" %}
This feature is currently in **Early Access** and is still under active development
{% endhint %}

## Introduction to Lifestyle Insights

<div><figure><img src="/files/7JegvcKINJvLJGergCfx" alt=""><figcaption></figcaption></figure> <figure><img src="/files/zO3j68j4IB8PhyDcLPp5" alt=""><figcaption></figcaption></figure> <figure><img src="/files/iQilRwSbCDuOu7oD4ict" alt=""><figcaption></figcaption></figure></div>

With Lifestyle insights you will gain a deeper understanding of your users by identifying the places they frequently visit such as home, work, and favorite venue types. These insights reveal daily routines, habits, and preferences.

We also provide Semantic Time, which categorizes activities based on a user’s personal timeline (e.g., morning, afternoon, evening). This helps you understand not just *where* users go, but *when* and *how* they structure their day.

Together, these insights enable personalized and relevant user engagement.\
The insights can be grouped in the topics below

## Features Overview

{% stepper %}
{% step %}

### Lifestyle Profiling

Lifestyle Profiling segments users into different lifestyle profiles. Segments are actionable labels assigned to users based on long-term behavioral patterns.

They describe lifestyle characteristics such as:

* Commuting behavior
* Geographic living area
* Driving style
* Social activity level

These segments help you tailor experiences and build more relevant and better user experience. The segments can be divided into these sub-categories:

<details>

<summary><strong>Profiles</strong></summary>

Profiles are a special type of Segment that apply to every user. Unlike regular segments (which a user may or may not belong to), every user always has a Profile.\
Each Profile has a **level** that indicates the degree to which it applies to that user:

* LIMITED
* MODERATE
* HIGH

<table><thead><tr><th width="165.098876953125">Segment</th><th>Description</th></tr></thead><tbody><tr><td>Physical Activity</td><td>evaluates how much the user is involved in physical activities</td></tr><tr><td>Mobility</td><td>evaluates how much the user is on the move</td></tr><tr><td>Social Activity</td><td>evaluates how much the user is involved in social engagement</td></tr></tbody></table>

</details>

<details>

<summary><strong>Leisure</strong></summary>

<table><thead><tr><th width="213.171875">Segment</th><th>Description</th></tr></thead><tbody><tr><td>Bar goer</td><td>enjoys evenings out at a pub or bar</td></tr><tr><td>Fresh food enthousiast</td><td>shops for food often</td></tr><tr><td>Healthy biker</td><td>frequently bikes for long distances</td></tr><tr><td>Healthy walker</td><td>frequently walks for long distances</td></tr><tr><td>Nature lover</td><td>likes to go out to a park, public garden, zoo or nature reserve</td></tr><tr><td>Resto lover</td><td>likes eating out</td></tr><tr><td>Shopaholic</td><td>shops a lot</td></tr><tr><td>Sportive</td><td>sports regularly</td></tr></tbody></table>

</details>

<details>

<summary><strong>Mobility</strong></summary>

<table><thead><tr><th width="253.2196044921875">Segment</th><th>Description</th></tr></thead><tbody><tr><td>Die hard driver</td><td>uses the car for almost every trip</td></tr><tr><td>Easy commuter</td><td>has an easy commute to/from work</td></tr><tr><td>Frequent flyer</td><td>frequently flies</td></tr><tr><td>Green commuter</td><td>mostly sticks to walking and biking for commutes</td></tr><tr><td>Heavy commuter</td><td>has a heavy commute to/from work</td></tr><tr><td>Long commuter</td><td>lives far from their work location</td></tr><tr><td>Normal commuter</td><td>has an average commute time and distance</td></tr><tr><td>Public transports user</td><td>often travels with public transports</td></tr><tr><td>Public transports commuter</td><td>often commutes with public transports</td></tr><tr><td>Short commuter</td><td>lives close to their work location</td></tr></tbody></table>

</details>

<details>

<summary><strong>Work life</strong></summary>

<table><thead><tr><th width="189.421875">Segment</th><th>Description</th></tr></thead><tbody><tr><td>Early bird</td><td>whose first morning activity is earlier than average</td></tr><tr><td>Fulltime worker</td><td>works full-time</td></tr><tr><td>Home bound</td><td>does not leave the house very often or travels very far</td></tr><tr><td>Homebody</td><td>prefers to stay at home on weekends and outside business hours</td></tr><tr><td>Homeworker</td><td>works from home or who is unemployed</td></tr><tr><td>Late worker</td><td>works until late</td></tr><tr><td>Night owl</td><td>whose last evening activity is later than average</td></tr><tr><td>Nightworker</td><td>works at night</td></tr><tr><td>Parttime worker</td><td>works part-time</td></tr><tr><td>Sleep deprived</td><td>sleeps very little</td></tr><tr><td>Student or teacher</td><td>a student or teacher</td></tr><tr><td>Uber parent</td><td>a parent who drives his/her kids to school, kindergarten or day care</td></tr><tr><td>Work life balancce</td><td>has a good balance between work and home life</td></tr><tr><td>Work traveller</td><td>works a lot remotely (travelling or in remote environments)</td></tr><tr><td>Workaholic</td><td>works more than average</td></tr></tbody></table>

</details>

{% endstep %}

{% step %}

### Venue Type Detection

Detects the type of place that the user has visited (e.g., drinks, sports, education, shopping) based on location context, time of day and users mobility behavior. \
Below is a list of the venue types that can be detected.

<details>

<summary>Venues</summary>

<table><thead><tr><th width="239.1934814453125"></th><th></th></tr></thead><tbody><tr><td>UNKNOWN</td><td>Unknown venue</td></tr><tr><td>DRINK_DAY</td><td>Cafes, coffee bars, tea rooms, etc</td></tr><tr><td>DRINK_EVENING</td><td>Bars, pubs and in general places where one goes for drinks in evenings.</td></tr><tr><td>EDUCATION_INDEPENDENT</td><td>Educational institutions visited by the user on his own for their own studies. High schools, universities, colleges, etc.</td></tr><tr><td>EDUCATION_PARENTS</td><td>Schools and kindergartens visited by parents. </td></tr><tr><td>HEALTH</td><td>Hospitals, clinics, emergency rooms.</td></tr><tr><td>INDUSTRIAL</td><td>Buildings tagged as “industrial” on OSM, built for some manufacturing process.</td></tr><tr><td>LEISURE_BEACH</td><td>Beaches, resorts and swimming areas.</td></tr><tr><td>LEISURE_DAY</td><td>Bowling, billiards and other entertainment places. </td></tr><tr><td>LEISURE_EVENING</td><td>Cinemas, theatres and music halls. </td></tr><tr><td>LEISURE_MUSEUM</td><td>Museums</td></tr><tr><td>LEISURE_NATURE</td><td>Forests, lakes, national parks, etc. </td></tr><tr><td>LEISURE_PARK</td><td>City parks, gardens, zoos.</td></tr><tr><td>OFFICE</td><td>Office buildings. For example, of private lawyers, notaries or company representatives.</td></tr><tr><td>RELIGION</td><td>Churches, mosques and other religion related buildings.</td></tr><tr><td>RESIDENTIAL</td><td>Apartment blocks, houses. </td></tr><tr><td>RESTO_MID</td><td>Food courts, restaurants, snack bars. </td></tr><tr><td>RESTO_SHORT</td><td>Ice cream, fast food, donut stores.</td></tr><tr><td>SHOP_LONG</td><td>Supermarkets, malls, wholesales, shopping centres.</td></tr><tr><td>SHOP_SHORT</td><td>Small grocery stores, butchers, bakers. </td></tr><tr><td>SPORT</td><td>Gyms, sport centres. Venues visited to exercise.</td></tr><tr><td>SPORT_ATTEND</td><td>Stadiums. Venues visited to attend a sport event.</td></tr><tr><td>TRAVEL_BUS</td><td>Bus stops.</td></tr><tr><td>TRAVEL_CONFERENCE</td><td>Conference, convention, exhibition centres.</td></tr><tr><td>TRAVEL_FILL</td><td>Gas stations.</td></tr><tr><td>TRAVEL_HOTEL</td><td>travel_hotel- Hotels, motels, guest rooms, etc.</td></tr><tr><td>TRAVEL_LONG</td><td>Airports</td></tr><tr><td>TRAVEL_SHORT</td><td>Public transport stations, railway stations.</td></tr></tbody></table>

</details>

{% endstep %}

{% step %}

### Home & Work Detection

Automatically identifies home and work locations based on the user’s behavioral patterns. These locations are typically detected during the first week of using Sentiance.

The detected location will seamlessly addapt when a user moves from the original home or changes jobs

This includdes the detection of when the user enters and exists these locations.

{% endstep %}

{% step %}

### Physical Activity

Physical Activity segments users into activity levels based on detected behaviors such as walking, cycling, and gym visits.

Users are categorized into one of the following activity levels:

* Low
* Moderate
* High

These levels represent the user’s overall physical activity based on their detected behavior patterns.
{% endstep %}
{% endstepper %}

{% hint style="success" %}
**Privacy by Design**

We do not expose exact locations. Instead, we provide venue types to preserve user privacy. This approach is often more accurate for example, confidently identifying that a user is at a bar without pinpointing which specific one.
{% endhint %}


# Crash Insights

Learn from real-time crash detection and context to generate actionable safety insights.

## Introduction to Crash Insights

<div><figure><img src="/files/JarXCfnSIwkDbaF1amlI" alt=""><figcaption></figcaption></figure> <figure><img src="/files/WyhQXhXvbBmTVjtX1SmB" alt=""><figcaption></figcaption></figure> <figure><img src="/files/ybpL8RXWIeJ9De04a8y3" alt=""><figcaption></figcaption></figure></div>

Sentiance Crash Detection uses device sensors to identify vehicle crashes within minutes of impact. This enables faster emergency response and more efficient accident management.

By leveraging advanced ML models, the SDK distinguishes between actual crashes, false positives, and other driving events.

**Key Benefits**

* Direct access to detailed crash insights
* Easy integration of emergency services into your app
* Potentially life-saving intervention through rapid detection
* Post-crash data analysis and investigation

**Supported Vehicles**

* Cars
* Motorcycles&#x20;

## Features Overview

{% stepper %}
{% step %}

### Crash Detection

Your app can receive near real-time crash notifications. Each detected crash includes data from just before, during, and immediately after the impact.

**Available crash data:**

* **Severity**\
  The calculated intensity of the detected crash based on delta-V and pre-impact speed this can be of the following values:
  * *Low*
  * *Medium*
  * *High*
* **Confidence**\
  Indicates the likelihood that the detected event is a true crash.
* **Magnitude (G-force)**\
  The peak G-force measured during impact.
* **Delta-V**\
  The change in velocity detected at impact.
* **Speed**\
  The user’s speed at the last known GPS location before impact.
  {% endstep %}

{% step %}

### Crash Forensics

The Crash Forensics provides detailed analysis of detected crash events in Insights Control Tower and provides a complete overview of the crash event and the events before impact including:

* Map view of the crash location
* Route taken before the crash
* Risky driving events captured during the trip
* Additional road and contextual information
  {% endstep %}

{% step %}

### **Crash Forensics – Full Trip Report**

Crash Forensics – Full Trip Report offers an on-demand, detailed breakdown of the entire trip in which the crash occurred.

* Available on demand
* Covers trips up to 30 days in the past (can be extended if needed)
* Includes full contextual data leading up to the crash
  {% endstep %}
  {% endstepper %}

{% hint style="info" %}
**Important:** Both Crash Forensics solutions require personal and sensitive data to be transmitted to the Sentiance backend.
{% endhint %}

### Additional notes

**Phone Placement**

* For optimal performance on motorcycles, the mobile phone must be securely mounted on the handlebars.
* In cars, a wider range of phone placements is supported, while still ensuring consistent and reliable detection.<br>

**ML Model Variants**

* Detection insights vary depending on the vehicle type (motorcycle or car). This is due to the use of different machine learning models, each tailored to the unique physical dynamics of the vehicle.


# Smart Geofences

## Introduction to Smart Geofences

Smart Geofences enable you to define geographic points of interest and receive near real-time, privacy-focused notifications when users interact with these locations. Interactions include:

* Entering a location
* Exiting a location
* Being present within a defined area

{% hint style="info" %}
All processing is powered by privacy-centric, flexible on-device intelligence to ensure user data remains protected.
{% endhint %}

## Features Overview

{% stepper %}
{% step %}

### Defining Points of Interest

You can choose the predefined places of interest for which you want to receive entry and exit notifications. When users enter or leave the defined radius, the Smart Geofence triggers an event.

You can define a large number of locations:

* Up to approximately 10,000 points of interest by default
* The capacity can be extended significantly if required
* A recommended minimum radius of 100 meters per location
* The radius can be increased to cover larger areas

{% hint style="success" %}
The list of locations is fully configurable and can be dynamically updated or expanded without requiring a full application release. This ensures excellent scalability as usage and demand increase.
{% endhint %}

The Points of Interest can be defines on two different levels (scopes):

1. **General Points of Interest**\
   Defined by you and uploaded to the backend. These apply to all Sentiance users within your application.
2. **Group-Level Points of Interest** (Requires Engagement Platform)\
   Individual end-users can define their own points of interest for the specific group they belong to.
   {% endstep %}

{% step %}

### Event notificaitons

Whenever a Smart Geofence event is triggered (e.g., a user enters or exits a predefined location), your app will be notified of such events.

The notification includes:

* Geofence identifier (can be multiple)
* Time of event
* Type of event (entry or exit)
* (Optional) location of when this event was triggerred

This enables your application to trigger automated workflows, enhance engagement strategies, and implement location-based business logic in real time.
{% endstep %}
{% endstepper %}


# Engagement

## Introduction to Engagement Platform

The Engagement Platform is built on scientifically validated behavior change models. It provides a structured framework to support users in improving their driving and mobility behavior and allow for healthy gamification and improve engagement in your application.

The platform is designed to:

* Increase user engagement
* Promote safer driving through gamification
* Encourage long-term behavior change
* Provide insights into your users progress
* Track and analyze improvements in daily mobility behavior

## Features Overview

All engagement features can operate independently or be combined to create a comprehensive engagement strategy.

{% stepper %}
{% step %}

### Streaks

Streaks motivate users to maintain consistent safe driving behavior by rewarding consecutive “good” trips.

Examples of qualifying trips:

* Perfect legal score
* No phone handling
* Smooth driving
* No speeding

Progress is visualized over consecutive days or over a custom-defined time period (e.g., daily, weekly, every 3 days). This continuous reinforcement encourages habit formation.
{% endstep %}

{% step %}

### Challenges

Challenges help users improve specific aspects of their driving or mobility behavior by setting measurable goals within a defined timeframe.

Examples:

* 3 consecutive days without speeding
* 4 trips in a row without phone handling
* Improving legal score over one week

Challenges create clear objectives and short-term milestones that drive focused behavior improvement.
{% endstep %}

{% step %}

### Badges

Badges provide visual recognition for specific achievements or milestones.

They:

* Highlight user progress
* Reward positive behavior
* Reinforce accomplishments
* Increase motivation through visible recognition

Badges can be awarded for milestones, consistency, improvement, or specific safe driving actions.
{% endstep %}

{% step %}

### Leaderboards

Leaderboards allow users to compare their performance with others and focus on specific achievements or safety metrics, fostering healthy competition and promoting safe driving behavior. leaderboards can be configured for.

* The entire user base
* Specific groups (e.g., teams, families, fleets)
  {% endstep %}

{% step %}

### Communication Messages

The platform supports in-app messages and push notifications triggered by configurable rules or custom logic. This enables personalized communication strategies based on behavior, progress, or engagement level. these messages can target:

* The entire user base
* Specific users (e.g., teams, families, fleets, or with specific [segments](/getting-started/features-catalog/lifestyle-insights#segmentation))
* Defined user groups
  {% endstep %}

{% step %}

### Smart Tips

Smart Tips provide educational content designed to inform and guide users toward safer driving and mobility behavior.

Content can:

* Be displayed directly within the application
* Be delivered via push notifications
* Target the entire user base or specific groups

Smart Tips combine behavioral insights with contextual education to reinforce positive habits.
{% endstep %}

{% step %}

### Campaign Management

Campaign Management is a centralized overview of engagement campaigns and scheduled communications. It presents campaigns and messages in a visual timeline and calendar format, allowing organizations to track communication plans and monitor when engagement initiatives are deployed.&#x20;

This capability supports coordinated outreach strategies and improves visibility into how user engagement programs are executed.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
**Additional note**

The Engagement Platform is a separate and independent platform from the Sentiance SDK. While it operates independently, it relies on mobility and behavioral data collected by the SDK running on the user’s device. To enable Engagement features, relevant user data must be shared to the Sentiance Backend.
{% endhint %}


# Insights Control Tower

ICT provides direct access to the insights collected from your users, offering both detailed individual-level analytics and  insights across your entire user base. ICT enables you to monitor performance, analyze behavior, from one centralized environment.

### ICT Roles and Access overview

ICT uses role-based access to manage platform features. Depending on their responsibilities, users can hold one or multiple roles as described below.

* **Spectator**\
  Spectators role enables the user to explore and interact with available data in ICT. They have access to the insights dashboards and can interact with visualizations, filters, and datasets to analyze insights that have been detected for the end users . This role is read-only and does not allow changes to configurations or data pipelines.
* **Developer**\
  Developer role enables access to configuration and developer-focused functionalities. This role enables integration of the Sentiance SDK in your application and supports tasks such as setting up [API keys](/getting-started/insights-control-tower/developer-dashboard/api-keys), managing and generating data offloads, and configuring data access. Developers can also access the user base, view end-user activity, and monitor SDK status per user for debugging and analytical purposes.
* **Admin**\
  Admin role enables full control over user and access management. They can create and manage ICT users, assign roles and permissions. Additionally, with the Admin role, users can create campaigns and schedule messages for the engagement platform

### ICT Bundles

Sentiance provides different bundles of the Insights Control Tower, and the available dashboards and functionalities depend on your bundle

<table><thead><tr><th width="330.66796875" valign="top">ICT Basic</th><th>ICT Premium</th></tr></thead><tbody><tr><td valign="top"><p></p><p>The Basic bundle allows the integration of the SDK and provides a overview of:</p><p></p><ul><li><p>Users information</p><ul><li>Total users</li><li>Active users</li><li>SDK integration and status</li></ul></li><li>Developer tasks</li><li>Admin tasks</li></ul></td><td><p></p><p>The Premium bundle expands analytical capabilities and enables deeper interaction with the data.</p><p></p><ul><li>Everything included in ICT Basic</li><li><p>Advanced Insights exploration</p><ul><li>Driving Insights</li><li>Mobility Insights</li><li>Lifestyle Insights</li><li>Engagement</li></ul></li><li>Dynamic data interaction</li><li>Custom dashboard creation</li></ul></td></tr></tbody></table>

{% hint style="info" %}
**Important note**\
The capabilities in your Insights Control Tower depend on your contracted package. \
Some features require data synchronization to the Sentiance backend before they can be enabled.
{% endhint %}


# Client Dashboard


# General insights

## General Overview

This section provides a quick overview of your user base, with the purpose of monitoring the number of Sentiance users and their SDK status

You can quickly access:

* Total user base
* Active users
* Total SDK installations
* SDK status across user devices

***

### General Insights Tabs

{% stepper %}
{% step %}

### Overview Tab

The Overview tab provides key statistics and visual dashboards, including:

* User and SDK statistics
* Active user metrics with visual diagrams:
  * Monthly Active Users (MAU)
  * Weekly Active Users (WAU)
  * Daily Active Users (DAU)<br>
    {% endstep %}

{% step %}

### Users Tab

Allows to search for Sentiance users and displays individual user insights and metrics.

**Available Information**

* User list
* Sentiance ID
* External ID (the unique identifier used to create and authenticate the user in the Sentiance Platform)

**Filtering Options**

You can filter users by:

* SDK status
* SDK version
* User permissions

**Export Functionality**

* Download the users list as a file
* Any active filters will be applied to the exported results

**Deleted users**

* A list of users who have been deactivated or removed
  {% endstep %}
  {% endstepper %}

{% hint style="info" %}
**Important:**\
Only users who have been active within the past 6 months are displayed in this view. Users without activity during this period are not included.
{% endhint %}


# Driving Insights

Driving Insights provides a detailed view of driving behavior across your user population. It analyzes trips, safety scores, and risky driving events such as speeding, distraction, harsh braking, and acceleration. This enables you to monitor driver performance, identify emerging risk patterns, and support initiatives aimed at improving safety outcomes and reducing incidents.

These insights are presented in the following tabs:

{% stepper %}
{% step %}

### Population Data

Displays key metrics related to detected transports across your user base. This includes visualizations showing how averages evolve over a selected time period.\
Metrics include:

* Total active users
* Total distance traveled
* Total number of transports
* Average distance and transports per day
* And more
  {% endstep %}

{% step %}

### User Scores

Provides an overview of driving performance through driving scores. Each driver has individual scores per category, as well as a combined overall score:\
This section also offers direct access to the user’s scorecard for more detailed insights.
{% endstep %}

{% step %}

### User Risk Data

Displays risk-related information for individual drivers, including total trips and distance traveled. It also highlights key risk indicators such as:

* **Speeding**: Percentage of trips where speeding was detected
* **Distraction**: Percentage of trips where the user was distracted (e.g., phone handling or calls)
* **Non-smooth driving**: Percentage of trips with harsh driving events
* **Wrong-way driving**: Number of times the user drove against the correct road direction
  {% endstep %}

{% step %}

### User Scorecard

Provides a detailed view of an individual driver, including:

* Driving performance
* Driving scores
* Overview and in-depth insights into recent transports
  {% endstep %}
  {% endstepper %}

{% hint style="info" %}
Please note that certain features and dashboards require **ICT Premium.**
{% endhint %}


# Mobility Insights

Mobility Insights gives you the ability to understand your users’ mobility patterns. It provides detailed information about the modes of transport they use.

These insights are available and organized into the tabs below.

{% stepper %}
{% step %}

#### Population Mobility

This tab provides

* A high-level overview of the mobility types across your user base.
* Average metrics and analysis for each mobility type.
* A comparison of transport modes across key dimensions such as: trip volume, duration, distance and more.
  {% endstep %}

{% step %}

#### Individual Mobility & Driver Details

This section allows you to analyze mobility insights at the individual user level.

A complete list of users is available. By selecting a specific user, you can navigate to the Driver Details page, which provides a summary of the trips performed by that user, including metrics such as distance traveled and travel time.&#x20;

**Driver Details**

Provides deeper insights into the mobility behavior of a single user, including:

* Comparison of different transport modes used by the user
* Trip metrics and summaries
* The ability to filter results for a specific time period
  {% endstep %}

{% step %}

#### Mobility Evolution

This tab visualizes the distribution of users, trips, distance, and duration over a selected time period.

It helps you understand mobility trends across your entire user base and track how these patterns evolve over time.
{% endstep %}
{% endstepper %}


# Crash Insights

Crash Insights is a dashboard that provides an overview of recent crashes detected by the Sentiance SDK, enabling analytics users to access crash forensics reports and review user-submitted crash feedback. Crash feedback refers to cases where users confirm whether a detected crash actually occurred.

These insights are presented in the following tabs:

{% stepper %}
{% step %}

### **Detected Crashes**

Provides a list of crashes detected by the SDK, along with details such as timestamps, confidence levels, and user IDs.
{% endstep %}

{% step %}

### **Crash Feedback**

A subset of detected crashes, this tab shows a filtered list of events for which users have provided feedback, confirming whether the crash actually occurred.
{% endstep %}

{% step %}

### **Crash Forensics**

Provides detailed reports for a specific crash, including metrics such as:

* Speed at impact
* Road conditions
* Detected events leading up to the crash
* Delta-V
* Magnitude
* Severity
* Confidence level
* And more
  {% endstep %}
  {% endstepper %}


# Developer Dashboard


# API Keys

### Generate API Key

ICT allows developers to generate a user API Key and communicate with the Sentiance Backend:

1. Login to **Insights Control Tower**
2. Navigate to the "**Developer**" tab
3. Under "**API Keys**", click on button "Create API Key"
4. Choose a name for this new API Key, the name is for visual purposes only and has no operational impact. Choose a name that would help you identify the key.
5. Select the permission scopes. These scopes define what operations an API Key can perform. <br>

   <table><thead><tr><th width="282.80029296875">Scope name</th><th>Scope description</th></tr></thead><tbody><tr><td><strong>USER_READ</strong></td><td>Use this scope to read user data. </td></tr><tr><td><strong>USER_DELETE</strong></td><td>Use this scope to delete a user along with all historical data.</td></tr><tr><td><strong>USER_LINK</strong></td><td>Use this scope to perform <br>User Creation &#x26; Authentication.</td></tr><tr><td><strong>OFFLOADS_READ</strong></td><td>Use this scope to list Offloads available for download.</td></tr><tr><td><strong>OFFLOADS_GENERATE_URL</strong></td><td>Use this scope to generate URLs at which offloads can be downloaded.</td></tr><tr><td><strong>FAKE_DATA_INSERT</strong></td><td>Use this scope to inject fake.<br>Engagement platform only!</td></tr></tbody></table>
6. Click on create API Key.
7. The API key is now available for copy or download. Please copy and store it securely. For security reasons, this API key is displayed only once and cannot be viewed again.
8. After creation success, store your API Key in a safe environment and update your backend system to include the newly created API Key

{% hint style="danger" %}
**Do NOT share your API Keys and keep your credentials in a secure environment.** \
**Never include your API Keys in you client side application**
{% endhint %}


# Webhooks


# Offloads

Sentiance provides data offloads with Sentiance Insights to the clients on a daily basis.


# Admin


# Business Users Management

A **Business User** is someone who has access to ICT to perform specific operational, analytical, or administrative tasks. Each user must have at least one role but can be assigned multiple roles depending on their responsibilities as described [here](/getting-started/insights-control-tower)

## Managing Business Users

#### Inviting a New User

1. Navigate to the **Admin** tab
2. Click **Invite User**
3. Enter the user’s email address
4. Select the appropriate role(s)

The invited user will receive an email to set up their ICT account (if they do not already have one).\
Once account setup is complete, they will gain access to ICT. Their name and account status will appear in the user list.

#### Removing a User

1. Go to the list of Business Users
2. Locate the user you want to remove
3. Click the **Remove** button
4. Confirm removal

You can verify the action by checking that the user no longer appears in the list.

#### Modifying User Permissions

1. Go to the Business Users list
2. Select the user whose role you want to update
3. Click **Add Scope** (or manage roles)
4. Add or remove roles as needed
5. Click **Save**

The updated roles and permissions will apply immediately.

#### Reset MFA

To reset the MFA for a user, just locate the user in the users list and click on reset MFA. This will alow the user to reset their MFA and regain access to ICT.

{% hint style="success" %}
This role-based structure ensures secure access control while giving each team member the tools they need to operate effectively within ICT.
{% endhint %}


# Campaign Management


# SDK Integration

Learn how to integrate the Sentiance SDK into your mobile app, with step-by-step guides for Android, iOS, React Native and Flutter.

{% content-ref url="/pages/z1P6I4BwiswNwR8FVzmM" %}
[1. Requirements](/getting-started/sdk-integration/1.-requirements)
{% endcontent-ref %}

{% content-ref url="/pages/uFKQ8tv4p4kU7ZkLrE5K" %}
[2. Including the SDK](/getting-started/sdk-integration/2.-including-the-sdk)
{% endcontent-ref %}

{% content-ref url="/pages/3o4XUFXr0n3NgBigBkPB" %}
[3. SDK Initialization](/getting-started/sdk-integration/3.-sdk-initialization)
{% endcontent-ref %}

{% content-ref url="/pages/mhkvzedxNzMwdMLidirG" %}
[4. User Creation](/getting-started/sdk-integration/4.-user-creation)
{% endcontent-ref %}

{% content-ref url="/pages/Ctg0cqIEU1Q9WQqTMEM8" %}
[5. Enabling Detections](/getting-started/sdk-integration/5.-enabling-detections)
{% endcontent-ref %}

{% content-ref url="/pages/CsTIC5bmfAPcZFZGz874" %}
[6. SDK Status Updates](/getting-started/sdk-integration/6.-sdk-status-updates)
{% endcontent-ref %}


# 1. Requirements

Before you begin, review the supported platforms, minimum versions, and other prerequisites for the Sentiance SDK.

{% content-ref url="/pages/3A1XvtDGeEUCu2y1q2aF" %}
[Android](/getting-started/sdk-integration/1.-requirements/android)
{% endcontent-ref %}

{% content-ref url="/pages/XBYgscThqpRuWPPxV5yY" %}
[iOS](/getting-started/sdk-integration/1.-requirements/ios)
{% endcontent-ref %}

{% content-ref url="/pages/E6gJArJ8hszKlwenCmld" %}
[React Native](/getting-started/sdk-integration/1.-requirements/react-native)
{% endcontent-ref %}

{% content-ref url="/pages/BHRM9ZWdYDhYspSQiopZ" %}
[Flutter](/getting-started/sdk-integration/1.-requirements/flutter)
{% endcontent-ref %}


# Android

## Platform

### Android 7.0 (API Level 24) or Higher

This is the minimum supported Android version. If you support an older Android version, you can still include the Sentiance SDK, however, detections will not run.&#x20;

If you face any build issues, check out [this](https://docs.sentiance.com/important-topics/sdk/troubleshooting/android#manifest-merger-failed-uses-sdk-minsdkversion-x-cannot-be-smaller-than-version-y-declared-in-library) troubleshooting guide.

### Google Play Services

The Sentiance SDK relies on several Google Play Services features, such as Fused Location and  Geofencing.

***

## Permissions

### Location <a href="#location" id="location"></a>

To collection location data in the background, the SDK requires the [background location access permission](https://developer.android.com/reference/android/Manifest.permission#ACCESS_BACKGROUND_LOCATION). This is shown as "**Allow all the time**" in the permission dialog.

The SDK automatically adds the `ACCESS_FINE_LOCATION` and `ACCESS_COARSE_LOCATION` permissions to your app's manifest, however **you must add** the `ACCESS_BACKGROUND_LOCATION` permission yourself.

{% code title="AndroidManifest.xml" %}

```xml
<manifest...>
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION">
    ...
```

{% endcode %}

{% hint style="warning" %}
**This permission is mandatory**

Without this permission, SDK detections will not work. This permission enables the SDK to collect location data in the background, and make use of [Geofencing](https://developer.android.com/develop/sensors-and-location/location/geofencing).
{% endhint %}

### Activity Recognition <a href="#activity-recognition-android-10" id="activity-recognition-android-10"></a>

To get notified of activity updates (e.g. walking, running), the SDK requires the [activity recognition permission](https://developer.android.com/reference/android/Manifest.permission#ACTIVITY_RECOGNITION).

{% code title="AndroidManifest.xml" %}

```xml
<manifest...>
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION">
    ...
```

{% endcode %}

{% hint style="info" %}
**This permission is optional**

When granted, this permission helps improve the quality of the detections. Therefore, it is **highly recommended** to ask the user to grant this permission.
{% endhint %}

### Foreground Service (auto-granted) <a href="#foreground-service-auto-granted" id="foreground-service-auto-granted"></a>

Apps targeting API level 28 and above must have the `FOREGROUND_SERVICE` permission specified in the application manifest file.

The Sentiance SDK **automatically adds this permission** to your app's manifest, and specifies the [service types](https://developer.android.com/about/versions/14/changes/fgs-types-required) **location** and **shortService**.

### Wake Locks (auto-granted) <a href="#foreground-service-auto-granted" id="foreground-service-auto-granted"></a>

To collect sensor data in the background (e.g. accelerometer and gyroscope data), the Sentiance SDK makes use of [Android WakeLocks](https://developer.android.com/develop/background-work/background-tasks/awake/wakelock) to temporarily prevent the device from sleeping.

WakeLocks are held only when necessary, for example, while the user is on the move.&#x20;

{% hint style="info" %}
For the complete list of Manifest permissions added by the SDK, see [this page](/sdk/appendix/android/manifest-permissions).
{% endhint %}

***

## Runtime

### Google Play Services - Geofence Usage Limits

The Sentiance SDK uses the Geofence feature provided by Google Play Services. There is currently a per-app limit of [100 geofences](https://developers.google.com/android/reference/com/google/android/gms/location/LocationStatusCodes#public-static-final-int-geofence_too_many_geofences), and a total of [5 PendingIntents](https://developers.google.com/android/reference/com/google/android/gms/location/LocationStatusCodes#public-static-final-int-geofence_too_many_pending_intents) that can be set at any point in time. This limit is enforced by Google Play Services.

For proper operation and detections, please ensure that the Sentiance SDK can set **2 geofences**, and can consume **1 PendingIntent**, at any given time.

***

Get started by [including the SDK](/getting-started/sdk-integration/2.-including-the-sdk/android).


# iOS

## Platform

### iOS 15.0 or Higher

This is the minimum supported iOS version. If you support an older iOS version, you can still include the Sentiance SDK, however, detections will not run.

### Xcode 26 or Higher

We recommend using the latest stable Xcode version and compiling against the latest iOS SDK version.

***

## Permissions

### Location <a href="#location" id="location"></a>

To collection location data in the background, the SDK requires the "**Always"** location permission. Additionally, **Precise Location** must be enabled in the app's location settings.

{% hint style="warning" %}
**This permission is mandatory**

Without this permission, SDK detections will not work. This permission enables the SDK to collection location data in the background, and make use of [region monitoring](https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions?changes=la\&language=objc).
{% endhint %}

### Motion & Fitness <a href="#motion-and-fitness" id="motion-and-fitness"></a>

To collect motion activity data (e.g. walking, running), the SDK requires the Motion & Fitness permission.

{% hint style="info" %}
**This permission is optional**

When granted, this permission helps improve the quality of the detections. Therefore, it is **highly recommended** to ask the user to grant this permission.&#x20;
{% endhint %}

### Permission Considerations

<details>

<summary>Read more...</summary>

Apple provides some useful tips about [requesting permissions](https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/requesting-permission/) as part of their Human Interface Guidelines. Below are some additional things to consider for an optimal user experience when requesting permissions:

#### Privacy <a href="#privacy" id="privacy"></a>

* Request the permission only when your app clearly needs it.
* Explain why your app needs the permission.
* When your app requests permission for background locations or motion activities, a message will be shown to the user. You must configure this message by setting the value for the following keys in **Info.plist:**
  * `NSLocationAlwaysUsageDescription`
  * `NSLocationWhenInUseUsageDescription`
  * `NSLocationAlwaysAndWhenInUseUsageDescription`
  * `NSMotionUsageDescription`
* Keep the text short and specific. Be polite so the user does not feel pressured. There’s no need to include your app name.

#### Consistency <a href="#consistency" id="consistency"></a>

* Request a permission at launch only when necessary for your app to function.
* Use the system-provided alerts.

</details>

***

## Runtime

### iOS Region Monitoring Limits <a href="#motion-and-fitness" id="motion-and-fitness"></a>

The Sentiance SDK uses the [region monitoring](https://developer.apple.com/documentation/corelocation/monitoring-the-user-s-proximity-to-geographic-regions) feature provided by iOS. There is currently a per-app limit of 20 regions that can be monitored at any point in time. This limit is enforced by iOS.

For proper operation and detections, please ensure that the Sentiance SDK can monitor **2 regions** at any given time. If the SDK does not obtain at least two geofences required for its internal operation, it will trigger an off-the-grid event.

***

Get started by [including the SDK](/getting-started/sdk-integration/2.-including-the-sdk/android).


# React Native

## Platform

### React Native 0.60 or Higher

For [React Native CLI](https://reactnative.dev/docs/getting-started-without-a-framework) users, this is the minimum supported React Native version.

### Expo 54 or Higher

For [Expo](https://expo.dev/) integrations, this is the minimum supported Expo version.

### Android

For Android specific requirements, see [this page](/getting-started/sdk-integration/1.-requirements/android).

### iOS

For iOS specific requirements, see [this page](/getting-started/sdk-integration/1.-requirements/ios).

***

Get started by [including the SDK](/getting-started/sdk-integration/2.-including-the-sdk/react-native).


# Flutter

## Platform

### Dart 3.4.4 or Higher

This is the minimum required Dart language version.

### Flutter 3.22.3 or Higher

This is the minimum supported Flutter framework version.

### Android

For Android specific requirements, see [this page](/getting-started/sdk-integration/1.-requirements/android).

### iOS

For iOS specific requirements, see [this page](/getting-started/sdk-integration/1.-requirements/ios).

***

Get started by [including the SDK](/getting-started/sdk-integration/2.-including-the-sdk/flutter).


# 2. Including the SDK

Learn how to add the Sentiance SDK and its required dependencies to your project configuration.

{% content-ref url="/pages/23Rd3DLfSJdwWS1zqkoq" %}
[Android](/getting-started/sdk-integration/2.-including-the-sdk/android)
{% endcontent-ref %}

{% content-ref url="/pages/Ras4HX1uYxs8PK40vMIk" %}
[iOS](/getting-started/sdk-integration/2.-including-the-sdk/ios)
{% endcontent-ref %}

{% content-ref url="/pages/eyw2Vdae8UZEajSQFANq" %}
[React Native](/getting-started/sdk-integration/2.-including-the-sdk/react-native)
{% endcontent-ref %}

{% content-ref url="/pages/Ic5zwhjqipySuPPvaWUh" %}
[Flutter](/getting-started/sdk-integration/2.-including-the-sdk/flutter)
{% endcontent-ref %}


# Android

## External Dependencies

The Sentiance SDK has the following external library dependencies. These are automatically included during the build.

* **Google Play Services**: version <mark style="color:$success;">**`18.0.0`**</mark>. A higher version is also acceptable.
* **TensorFlow Lite**: a custom build based on version <mark style="color:$success;">**`2.16.2`**</mark>, supporting 16 KB pages. If your app depends on a different version, please reach out to us to address possible incompatibility issues.

## Steps

{% stepper %}
{% step %}

### Update Your Gradle Build Files

Add the Sentiance repository to your **project-level** <mark style="color:$success;">**`settings.gradle.kts`**</mark> file:

```kotlin
dependencyResolutionManagement {
    ..
    repositories {
        ..
        maven { url = uri("https://repository.sentiance.com") }
    }
}
```

Add the Sentiance dependencies to your **module-level** <mark style="color:$success;">**`build.gradle.kts`**</mark> file:

```kotlin
plugins {
    ..
}

android {
    ..
}

dependencies {
    ..
    implementation(platform(libs.sentiance.sdk.bom))
    implementation(libs.sentiance.sdk)
}
```

{% endstep %}

{% step %}

### Update Your Version Catalog

In your <mark style="color:$success;">**`libs.versions.toml`**</mark> file, specify the Sentiance SDK BOM version. See [Versions & Changelog](/sdk/versions-and-changelog) for the latest.&#x20;

```toml
[versions]  
..
sentianceSdkBom = "x.y.z"  
```

Next, declare the Sentiance library dependencies:

```toml
[libraries]  
..
sentiance-sdk-bom = { group = "com.sentiance", name = "sdk-bom", version.ref = "sentianceSdkBom"}  
sentiance-sdk = { group = "com.sentiance", name = "sdk" }  
```

{% endstep %}

{% step %}

### Sync and Verify Your Build

After adding the dependencies, sync your project with Gradle.

If the configuration is correct, the build output should show the SDK artifacts being downloaded from the Sentiance repository:

```log
> Task :prepareKotlinBuildScriptModel UP-TO-DATE
Download https://repository.sentiance.com/com/sentiance/sdk-bom/x.y.z/sdk-bom-x.y.z.pom, took 1 s 68 ms
Download https://repository.sentiance.com/com/sentiance/sdk/x.y.z/sdk-x.y.z.pom, took 312 ms
Download https://repository.sentiance.com/com/sentiance/sdk/x.y.z/sdk-x.y.z.aar, took 2 s 611 ms
Download https://repository.sentiance.com/com/sentiance/sdk/x.y.z/sdk-x.y.z-sources.jar, took 266 ms

BUILD SUCCESSFUL in 10s
```

{% endstep %}

{% step %}

### ProGuard and DexGuard Setup

{% hint style="success" %}

### Proguard Rules <a href="#proguard-rules" id="proguard-rules"></a>

The Sentiance SDK includes all necessary Proguard rules. These are automatically applied to your app during the build process.
{% endhint %}

{% hint style="warning" %}

### **DexGuard Rules**

If you are using DexGuard, please add the following rule:

```python
-keepresourcexmlelements manifest/application/meta-data@name=com.sentiance.sdk.**
```

This makes sure that metadata defined in the SDK library manifest files do not get stripped.
{% endhint %}
{% endstep %}

{% step %}

### Next: Initialize the SDK

After having included the Sentiance SDK, proceed to [initializing the SDK](/getting-started/sdk-integration/3.-sdk-initialization/android#steps).
{% endstep %}
{% endstepper %}


# iOS

## External Dependencies

The Sentiance SDK has the following external library dependencies. These are automatically included during the build.

* **TensorFlowLiteC**: version <mark style="color:$success;">**`2.17.0`**</mark>. If your app depends on a different version, please reach out to us to address possible incompatibility issues.
* **Protobuf**: version <mark style="color:$success;">**`3.18`**</mark>. A higher version is also acceptable.
* **UnzipKit**: version <mark style="color:$success;">**`1.9`**</mark>. A higher version is also acceptable.

## Steps

{% stepper %}
{% step %}

### Add the Dependency

There are several ways to include the Sentiance SDK in your project. Choose the method that best fits your setup.\
\
The latest SDK version can be found at [Versions & Changelog](/sdk/versions-and-changelog).

<details>

<summary>Swift Package Manager (Recommended)</summary>

[Swift Package Manager](https://developer.apple.com/documentation/xcode/swift-packages) is a tool for managing dependencies which simplifies adding 3rd-party frameworks to your projects.

### Steps <a href="#steps" id="steps"></a>

1. In your Xcode project, go to File -> Add Package Dependencies.
2. In the search bar, enter the URL: <https://github.com/sentiance/sentsdk-package>
3. Select **sentsdk-package** from the results.
4. Choose "**Up to Next Minor Version**" as the Dependency Rule. The latest SDK version can be found at [Versions & Changelog](/sdk/versions-and-changelog).
5. Select your project from the "**Add to Project**" section, and click **Add Package**. Wait for the product selection dialog to load.
6. A dialog will appear showing the following packages:
   * <mark style="color:$success;">**`ProtocolBuffersObjC`**</mark>
   * <mark style="color:$success;">**`SENTSDK`**</mark>
   * <mark style="color:$success;">**`TensorFlowLiteC`**</mark>
   * <mark style="color:$success;">**`UnzipKit`**</mark>
7. On the same dialog box, use "**Add to Target**" to add all packages to your app.
8. Finally, in your project build settings, add the following to the **Other Linker Flags**:&#x20;
   * <mark style="color:$success;">**`-lz`**</mark>&#x20;
   * <mark style="color:$success;">**`-lc++`**</mark>&#x20;
   * <mark style="color:$success;">**`-all_load`**</mark>

{% hint style="info" %}
If at any point you need to redo the package installation, you can remove the SENTSDK and its packages through your Project > select your target > General > Frameworks, Libraries, and Embedded Content.
{% endhint %}

</details>

<details>

<summary>Cocoapods</summary>

[CocoaPods](https://cocoapods.org/) is an open source dependency manager for iOS projects. **It is scheduled for end of life in December 2026.**

### Steps

1. Make sure you are using CocoaPods **v1.10.0** or above.
2. In your <mark style="color:$success;">**`Podfile`**</mark>, add the Sentiance SDK pod dependency:

```python
## Target Development Version
platform :ios, '15.0'

target 'MyApp' do
  use_frameworks!
  
  ## Pods for your app
  pod 'SENTSDK', '~>x.y.z'  # Replace x.y.z with the latest SDK version.

end
```

3. Run <mark style="color:$success;">**`pod install --repo-update`**</mark> to install the Sentiance SDK pod. The result should look as follows:

```javascript
Updating local specs repositories
Analyzing dependencies
Downloading dependencies
Installing Protobuf (3.29.6)
Installing SENTSDK (6.21.3)
Installing TensorFlowLiteC (2.17.0)
Installing UnzipKit (1.9)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `MyApp.xcworkspace` for this project from now on.
Pod installation complete! There is 1 dependency from the Podfile and 4 total pods installed.
```

4. Open your project using the <mark style="color:$warning;">`xcworkspace`</mark> file and verify that Xcode builds your app without any issues.

{% hint style="danger" %}
If Xcode runs into rsync or sandbox errors, please [follow the steps here](https://app.gitbook.com/o/-LTy4edtsWdQgbEsKB-i/s/YBq0zewxkIeJ41RyTWOa/~/edit/~/changes/59/important-topics/faq/troubleshooting-issues/xcode-sandbox-and-rsync) to enable sandbox code.

<mark style="color:$danger;">`Sandbox: rsync(59862) deny(1) file-write-create`</mark>&#x20;
{% endhint %}

{% hint style="warning" %}
If you see target deployment warning for Protobuf and Unzipkit, you can safely ignore them:

`Protobuf: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 10.0, but the range of supported deployment target versions is 12.0 to 26.2.99.`

`Unzipkit: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 9.0, but the range of supported deployment target versions is 12.0 to 26.2.99.`
{% endhint %}

</details>

<details>

<summary>Carthage</summary>

[Carthage](https://github.com/Carthage/Carthage) is a decentralized, lightweight dependency manager for iOS.

### Steps <a href="#steps" id="steps"></a>

1. Add the following line in your <mark style="color:$success;">**`Cartfile`**</mark>:

```
binary "https://sentiance-u1-sdk-downloads.s3-eu-west-1.amazonaws.com/ios/carthage/SENTSDK.json"
```

2\. Then run the following command in the terminal:

```
carthage update --use-xcframeworks
```

3\. Drag the built **.xcframework** binary from **Carthage/Build/** into *Frameworks, Libraries and Embedded content* section under *General* tab of your application target.

4\. Depending on your Xcode setup, it might be required to add the following libraries: libz.tbd (previously libz.dylib), CoreMotion, SystemConfiguration, CoreLocation, Foundation, CallKit, CoreTelephony, CoreData.

5\. Follow steps 3, 4, and 5 of the manual integration guide.

</details>

<details>

<summary>Manual Integration</summary>

If you want to include the Sentiance SDK without using any dependency management tool, follow the steps below. Note that to keep up with new SDK patches and versions, you will need to update the SDK framework manually.

### Steps <a href="#id-1.-download-the-sdk" id="id-1.-download-the-sdk"></a>

#### **1. Download the SDK** <a href="#id-1.-download-the-sdk" id="id-1.-download-the-sdk"></a>

Download the latest [umbrella Sentiance iOS SDK](/sdk/appendix/ios/v6.x-framework-files).

#### 2. Import the Framework <a href="#manual-integration-step-2" id="manual-integration-step-2"></a>

After you've downloaded and unzipped the SDK, import it as a linked library in your Xcode project:

1. Go to the **General** tab of your target settings.
2. Click the **+** button under the **Frameworks, Libraries, and Embedded Content** heading.
3. Click **Add Other** and then **Add Files**.
4. Choose the **Sentiance XCFramework** file and click **Open**.
5. After the item has been added to the list, change the **Embed** option next to the framework to **Do Not Embed**.
6. Depending on your Xcode setup, it might be required to add the following libraries: libz.tbd (previously libz.dylib), CoreMotion, SystemConfiguration, CoreLocation, Foundation, CallKit, CoreTelephony, CoreData.

#### 3. Include the Bundles <a href="#manual-integration-step-3" id="manual-integration-step-3"></a>

Include the SDK and Protobuf bundles in your project:

1. Go to the **Build Phases** tab of your target settings.
2. Expand the **Copy Bundle Resources** row and click the **+** button.
3. Choose the **SENTSDK.bundle** file located inside the **SENTSDK library**.
4. Next, choose the **Protobuf\_Privacy.bundle** file located inside the **Protobuf library**.

#### **4. Import the Dependencies** <a href="#id-4.-import-the-dependencies" id="id-4.-import-the-dependencies"></a>

The SDK XCFramework includes all its necessary dependencies under the **External** directory. For each dependency, do the following:

1. Go to the **General** tab of your target settings.
2. Click the **+** button under the **Frameworks, Libraries, and Embedded Content** heading.
3. Click **Add Other** and then **Add Files**.
4. Go to the **Frameworks** folder inside the Sentiance framework, select the dependency .**xcframework** file and click **Open**.
5. After the item has been added to the list, update the **Embed** option next to the framework as follows:

```
ProtocolBuffers.xcframework - Do Not Embed
UnzipKit.xcframework - Do Not Embed
SENTTensorFlowLiteC.xcframework - Embed & Sign
mpde.xcframework - Embed & Sign
dskoball.xcframework - Embed & Sign
```

#### 5. Update the Build Settings <a href="#id-5.-update-the-build-settings" id="id-5.-update-the-build-settings"></a>

1. Go to the **Build Settings** tab of your target settings.
2. Look for **Other Linker Flags** in the **Linking** section.
3. Add <mark style="color:$success;">**`-lz`**</mark> , <mark style="color:$success;">**`-all_load`**</mark>, and <mark style="color:$success;">**`-lc++`**</mark>

</details>
{% endstep %}

{% step %}

### Configure the Project Capabilities

1. Open your project in **Xcode**
2. In the **Project Navigator** (left sidebar), click the <mark style="color:blue;">**blue project icon**</mark> at the top
3. Under **TARGETS**, select your app target
4. At the top of the editor, open the <mark style="color:$success;">**`Signing & Capabilities`**</mark> tab
5. Click the **“+ Capability”** button near the top-left of the editor
6. In the capability list, select **Background Modes**
7. In **Background Modes**, enable the following capabilities:
   * <mark style="color:$success;">**`Background Fetch`**</mark>
   * <mark style="color:$success;">**`Background Processing`**</mark>
   * <mark style="color:$success;">**`Location Updates`**</mark>

The configuration should appear similar to the example shown below:

<figure><img src="/files/BJMgXwLQQYsDN0yCV1go" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Include the Background Task Identifier

The Sentiance SDK occasionally schedules background work, and as such, requires a background task identifier to be specified in your project's info (Info.plist):

1. At the top of the editor, open the <mark style="color:$success;">**`Info`**</mark> tab
2. Under **Custom iOS Target Properties**, locate <mark style="color:$success;">**`Permitted background task scheduler identifiers`**</mark>
3. If the key does not exist, **click + and add it as an Array**
4. Add the following background task identifier as a String item in the array: <mark style="color:$success;">**`com.sentiance.backgroundtask.task_processing`**</mark>

The configuration should appear as follows:

<figure><img src="/files/o4VTwon13rw8Y3xyLJrQ" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Configure Privacy Permission Descriptions

There are few permissions that required to support Sentiance's core functionality. Request these permissions by **clearly explaining to users why they are needed and how the corresponding data will be used**.

1. Open your project's <mark style="color:$success;">**`Info`**</mark> tab.
2. Add the required privacy keys if they don’t already exist.
3. For each key, set a clear, user-friendly description explaining why the permission is needed and how it benefits the user:
   * <mark style="color:$success;">**`Privacy - Location Always Usage Description`**</mark>
   * <mark style="color:$success;">**`Privacy - Location When In Use Usage Description`**</mark>
   * <mark style="color:$success;">**`Privacy - Location Always and When In Use Usage Description`**</mark>
   * <mark style="color:$success;">**`Privacy - Motion Usage Description`**</mark>&#x20;

{% hint style="info" %}
In the **Info.plist** file, these privacy settings will show up as:

* NSLocationAlwaysUsageDescription
* NSLocationWhenInUseUsageDescription
* NSLocationAlwaysAndWhenInUseUsageDescription
* NSMotionUsageDescription
  {% endhint %}

The configuration should appear similar to the following example:

<figure><img src="/files/zb9Revql9rB1R1sC24zx" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Next: Initialize the SDK

After having included the sentence SDK, proceed to initializing the SDK.
{% endstep %}
{% endstepper %}


# React Native

The SDK supports React Native via both the CLI and Expo workflows.

{% content-ref url="/pages/04wSkogoQwPOXREStEu8" %}
[CLI](/getting-started/sdk-integration/2.-including-the-sdk/react-native/cli)
{% endcontent-ref %}

{% content-ref url="/pages/6dZCldqQdnHrN5aCCX0F" %}
[Expo](/getting-started/sdk-integration/2.-including-the-sdk/react-native/expo)
{% endcontent-ref %}


# CLI

If you are building your app without using a Framework.

## External Dependencies

Apart from the core React Native libraries, there are no additional external React Native library dependencies.

For external dependencies in the native SDKs, check out [this page](/getting-started/sdk-integration/2.-including-the-sdk/android#external-dependencies) for Android and [this one](/getting-started/sdk-integration/2.-including-the-sdk/ios#external-dependencies) for iOS.

## Steps

{% stepper %}
{% step %}

### **Install the Sentiance Core React Native module**

Run the command in Terminal:

{% code title="Terminal" %}

```bash
npm install @sentiance-react-native/core
```

{% endcode %}

The module should now be added to your project's **package.json** file.

{% code title="package.json" %}

```json
{
  ...,
  "dependencies": {
    ...,
    "@sentiance-react-native/core": "^6.x.x"
  }
}
```

{% endcode %}

The <mark style="color:$success;">**`@sentiance-react-native/core`**</mark> module must be installed before using any other Sentiance service.
{% endstep %}

{% step %}

### **Setup the Build Dependencies in the native folders**

#### iOS Build Dependencies

1. In the terminal, navigate to your project's iOS directory&#x20;
2. Run the command in Terminal:

{% code title="Terminal" %}

```bash
pod install --repo-update
```

{% endcode %}

#### Android Build Dependencies

Add the Sentiance repository to your **project-level&#x20;**<mark style="color:$success;">**`build.gradle`**</mark> file:

```kotlin
allprojects {
    repositories {
        ...
        maven { url "https://repository.sentiance.com" }
    }
}
```

This will allow Gradle to find and download the necessary native Sentiance SDK libraries.

{% hint style="info" %}
All Sentiance modules support [Autolinking](https://github.com/react-native-community/cli/blob/main/docs/autolinking.md), which is available since React Native v0.60, the minimum supported React Native version by Sentiance. Autolinking takes care of discovering and linking the native code dependencies to your project.
{% endhint %}
{% endstep %}

{% step %}

### Configuration and permissions

Next, we'll configure the project settings and permissions for both iOS and Android.

#### **iOS Configuration and Permissions**

Open your existing app project in Xcode.

For **project and permission settings**, follow these 3 steps from the Native iOS Setup guide:<br>

1. [Configure the Project Capabilities](/getting-started/sdk-integration/2.-including-the-sdk/ios#configure-the-project-capabilities)
2. [Include the Background Task Identifier](/getting-started/sdk-integration/2.-including-the-sdk/ios#include-the-background-task-identifier)
3. [Configure Privacy Permission Descriptions](/getting-started/sdk-integration/2.-including-the-sdk/ios#configure-privacy-permission-descriptions)

#### **Android Configuration**

When the Sentiance SDK runs in the background, it starts a foreground service. This causes Android to display a notification in the notification shade, and an icon on the system bar.

To **customize this notification**, you can update the <mark style="color:$success;">**`AndroidManifest.xml`**</mark> file as follows:

{% code title="AndroidManifest.xml" %}

```xml
<application ...>
    <meta-data android:name="com.sentiance.react.bridge.core.notification_id" android:value="1001"/>
    <meta-data android:name="com.sentiance.react.bridge.core.notification_title" android:resource="@string/app_name"/>
    <meta-data android:name="com.sentiance.react.bridge.core.notification_text" android:value="Touch to open."/>
    <meta-data android:name="com.sentiance.react.bridge.core.notification_icon" android:resource="@mipmap/ic_launcher"/>
    <meta-data android:name="com.sentiance.react.bridge.core.notification_channel_name" android:value="Detections"/>
    <meta-data android:name="com.sentiance.react.bridge.core.notification_channel_id" android:value="sentiance_detections"/>
    ...

```

{% endcode %}

By customizing the notification, you can tailor the appearance and behavior of the notification to match the branding and user experience of your application. This allows you to provide a seamless and consistent experience to your users while the Sentiance SDK operates in the background.

Please note that customizing the notification should be done carefully to ensure that it complies with Android guidelines and user preferences. By doing so, you can enhance the overall user experience and maintain a cohesive presentation of your app and the Sentiance SDK's background functionality.
{% endstep %}

{% step %}

### Next: Initialize the SDK

After having included the Sentiance SDK, proceed to [initializing the SDK](/getting-started/sdk-integration/3.-sdk-initialization/android#steps).
{% endstep %}
{% endstepper %}


# Expo

If you are using Expo to build your app.

## External Dependencies

Apart from the core React Native and Expo libraries, there are no additional external React Native library dependencies.

For external dependencies in the native SDKs, check out [this page](/getting-started/sdk-integration/2.-including-the-sdk/android#external-dependencies) for Android and [this one](/getting-started/sdk-integration/2.-including-the-sdk/ios#external-dependencies) for iOS.

## Steps

{% stepper %}
{% step %}

### **Install the Sentiance Core React Native module**

Run the command in Terminal:

{% code title="Terminal" %}

```bash
npm install @sentiance-react-native/core
```

{% endcode %}

The module should now be added to your project's **package.json** file.

{% code title="package.json" %}

```json
{
  ...,
  "dependencies": {
    ...,
    "@sentiance-react-native/core": "^x.y.z"
  }
}
```

{% endcode %}

The <mark style="color:$success;">**`@sentiance-react-native/core`**</mark> module must be installed before using any other Sentiance service.
{% endstep %}

{% step %}

### Configure the Sentiance Expo config plugin

Add the Sentiance Core config plugin to your `app.json`. The plugin takes care of initializing the SDK on app start, configuring the Android foreground service notification and background location permission, and wiring up the required platform-specific hooks on both Android and iOS.

On iOS, the plugin enables the necessary background modes and registers the background task identifier used by the SDK's processing tasks.

On Android, the Sentiance SDK runs a [foreground service](https://developer.android.com/develop/background-work/services/fgs) while operating in the background, which causes the system to display a persistent notification. The `android.notification` block lets you customize its title, text, icon, and channel to match your app's branding and deliver a consistent experience to your users.&#x20;

In addition, the plugin automatically declares the [background location permission](https://developer.android.com/reference/android/Manifest.permission#ACCESS_BACKGROUND_LOCATION) and the [activity recognition permission](https://developer.android.com/reference/android/Manifest.permission#ACTIVITY_RECOGNITION) inside the application manifest.

{% code title="app.json" %}

```json
{
  "expo": {
    "plugins": [
      "...": "...",
      [
        "@sentiance-react-native/core",
        {
          "android": {
            // Optional: customize the foreground service notification
            "notification": {
              "title": "Your App",
              "text": "Running in the background",
              "channelId": "sentiance",
              "channelName": "Sentiance",
              "icon": "notification_icon",
              "id": 1
            }
          }
        }
      ]
    ],
    "ios": {
      "...": "...",
      // Required: location & motion permission descriptions
      "infoPlist": {
        "NSLocationAlwaysAndWhenInUseUsageDescription": "This app uses your location to provide insights.",
        "NSLocationWhenInUseUsageDescription": "This app uses your location to provide insights.",
        "NSLocationAlwaysUsageDescription": "This app uses your location to provide insights.",
        "NSMotionUsageDescription": "This app uses motion data to detect your transportation mode.",
      }
    }
  }
}
```

{% endcode %}
{% endstep %}

{% step %}

### Next: Initialize the SDK

After having included the Sentiance SDK, proceed to [initializing the SDK](/getting-started/sdk-integration/3.-sdk-initialization/android#steps).
{% endstep %}
{% endstepper %}


# Flutter

{% stepper %}
{% step %}

### Add the Sentiance SDK package

Add the SDK package from pub.dev

Open a terminal and run the command below from yout flutter project root directory.

```bash
flutter pub get sentiance_core
```

{% endstep %}

{% step %}

### Native platform setup

To include the Sentiance SDK as a dependency, you must complete the native platform setup for each target platform your application supports.

<details>

<summary>iOS</summary>

1. **Add the SDK to your Podfile**

Open your Podfile and add the following line, Replace  `x.y.z` with the specific SDK version you want to install

```objective-c
pod 'SENTSDK', '~> x.y.z'
```

2. **Navigate to your ios folder in Terminal and run** <mark style="color:yellow;">`pod install`</mark>
3. **Configure Capabilities in Xcode**
   1. Open iox/Runner.xcworkspace in Xcode
   2. Go to Signing & Capabilities tab of your Runner target
   3. Add the Background Modes capability if it is not already added.
   4. Enable the following background modes
      * Location updates
      * Background fetch
      * Background processing
4. **Update Permissions in Info.plist**&#x20;
   1. If it doesn’t already exist, add \
      `Permitted background task scheduler identifiers` and include `com.sentiance.backgroundtask.task_processing` as a sub-item.
   2. Add the following keys with the appropriate usage descriptions
      * Location Always and When In Use Usage Description
      * Location Always Usage Description
      * Location When In Use Usage Description
      * Motion Usage Description

</details>

<details>

<summary>Android</summary>

#### Include the Sentiance repository

Add the Maven repository in android/build.gradle file, \
Add the lines below in the allprojects repositories\
make sure to use the correct syntax for your file.

* Kotlin syntax\
  `maven { url = uri("https://repository.sentiance.com") }`
* groovy syntax\
  `maven { url "https://repository.sentiance.com"}`<br>

#### Add dependencies

Open the build.gradle file of your app module and add the following lines in the dependencies section (add this section if it does not exist yet), replace `x.y.+` with the latest version from [Versions & Changelog](/sdk/versions-and-changelog)

<pre class="language-kotlin" data-title="android/app/build.gradle" data-expandable="true"><code class="lang-kotlin">...
dependencies {
<strong>    implementation(platform("com.sentiance:sdk-bom:x.y.+"))
</strong>}

flutter {
    source = "../.."
}
</code></pre>

#### Add Permissions to android Manifest

The SDK automatically includes some permissions, others are required to be specifically included in the Android Manifest.\
Include the permissions below in AndroidManifest.xml\
read more about permissions in ....

{% code title="AndroidManifest.xml" %}

```xml
    ...
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

```

{% endcode %}

</details>
{% endstep %}

{% step %}

### SDK Initialization

The SDK must be initialized as early as possible in the native code and on the main thread.\
Follow the instructions below to initialize the SDK for your application’s target platform.

{% tabs %}
{% tab title="iOS: AppDelegate" %}
Follow below instructions to initialize the Sentiance SDK for ios.\
Make sure that initialization happens before the `didFinishLaunchingWithOptions` method returns

1. Open ios/Runner/AppDelegate.swift
2. Import the sentiance\_core package
3. Create a SDK instance and call the initialize method
4. Perform some logic to verify the initialization

<pre class="language-swift"><code class="lang-swift">...
<strong>import sentiance_core
</strong>
...
    GeneratedPluginRegistrant.register(with: self)
<strong>    let sentianceInit = SentianceCorePlugin.shared.initialize()
</strong>    if sentianceInit.isSuccessful {
        print("Sentiance SDK initialized")
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
</code></pre>

{% endtab %}

{% tab title="Android: Application Class" %}
Follow below instructions to initialize the Sentiance SDK for Android.\
Make sure that initialization happens before the `onCreate` method returns

1. Open the file where your Application class resides. or create a Application class in a new file or in MainActivity.kt. \ <sub><mark style="color:blue;">\* Make sure to update AndroidManifest.xml to point to the newly created Application class<mark style="color:blue;"></sub>
2. Import the Sentiance core plugin
3. Create a SDK instance and call the initialize method
4. Perform some logic to verify the initialization

```kotlin
...
import com.sentiance.core_plugin.CorePlugin
import android.app.Application

class MainApplication : Application() {
    override fun onCreate() {
        val sentianceInit = CorePlugin.initialize(this)
        if(sentianceInit.isSuccessful){
            // Succesfull initialization
        } else {
            // Failed initialization
        }
    }
}
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## External Dependencies

In addition to the core Flutter SDK, the Sentiance Flutter SDKs have a dependency on the [equatable](https://pub.dev/packages/equatable) package.

For external dependencies in the native SDKs, check out [the following section](/getting-started/sdk-integration/2.-including-the-sdk/android#external-dependencies) for Android and [this section](/getting-started/sdk-integration/2.-including-the-sdk/ios#external-dependencies) for iOS.

## Steps

{% stepper %}
{% step %}

### **Install the Sentiance Core Flutter package**

Run the command in Terminal:

{% code title="Terminal" %}

```shellscript
flutter pub get sentiance_core
```

{% endcode %}

The module should now be added to your project's **pubspec.yaml** file.

{% code title="pubspec.yaml" %}

```yaml
dependencies:
  flutter:
    sdk: flutter
  sentiance_core: ^6.23.0
  ...
```

{% endcode %}
{% endstep %}

{% step %}

### **Setup the Build Dependencies in the native folders**

#### iOS Build Dependencies

1. In the terminal, navigate to your project's iOS directory&#x20;
2. Run the command in Terminal:

{% code title="Terminal" %}

```bash
pod install --repo-update
```

{% endcode %}

#### Android Build Dependencies

Add the Sentiance repository to your project-level <mark style="color:$success;">**`build.gradle`**</mark> file:

```kotlin
allprojects {
    repositories {
        ...
        maven { url "https://repository.sentiance.com" }
    }
}
```

This will allow Gradle to find and download the necessary native Sentiance SDK libraries.
{% endstep %}

{% step %}

### Configuration and permissions

Next, we'll configure the project settings and permissions for both iOS and Android.

#### **iOS Configuration and Permissions**

Open your existing app project in Xcode.

For **project and permission settings**, follow these 3 steps from the Native iOS Setup guide:<br>

1. [Configure the Project Capabilities](/getting-started/sdk-integration/2.-including-the-sdk/ios#configure-the-project-capabilities)
2. [Include the Background Task Identifier](/getting-started/sdk-integration/2.-including-the-sdk/ios#include-the-background-task-identifier)
3. [Configure Privacy Permission Descriptions](/getting-started/sdk-integration/2.-including-the-sdk/ios#configure-privacy-permission-descriptions)

#### **Android Configuration**

When the Sentiance SDK runs in the background, it starts a foreground service. This causes Android to display a notification in the notification shade, and an icon on the system bar.

To **customize this notification**, you can update the <mark style="color:$success;">**`AndroidManifest.xml`**</mark> file as follows:

{% code title="AndroidManifest.xml" %}

```xml
<application ...>
    <meta-data android:name="com.sentiance.flutter.core.notification_id" android:value="1001"/>
    <meta-data android:name="com.sentiance.flutter.core.notification_title" android:resource="@string/app_name"/>
    <meta-data android:name="com.sentiance.flutter.core.notification_text" android:value="Touch to open."/>
    <meta-data android:name="com.sentiance.flutter.core.notification_icon" android:resource="@mipmap/ic_launcher"/>
    <meta-data android:name="com.sentiance.flutter.core.notification_channel_name" android:value="Detections"/>
    <meta-data android:name="com.sentiance.flutter.core.notification_channel_id" android:value="sentiance_detections"/>
    
    ...
</application>
```

{% endcode %}

By customizing the notification, you can tailor the appearance and behavior of the notification to match the branding and user experience of your application. This allows you to provide a seamless and consistent experience to your users while the Sentiance SDK operates in the background.

Please note that customizing the notification should be done carefully to ensure that it complies with Android guidelines and user preferences. By doing so, you can enhance the overall user experience and maintain a cohesive presentation of your app and the Sentiance SDK's background functionality.
{% endstep %}

{% step %}

### Next: Initialize the SDK

After having included the Sentiance SDK, proceed to [initializing the SDK](/getting-started/sdk-integration/3.-sdk-initialization/android#steps).
{% endstep %}
{% endstepper %}


# 3. SDK Initialization

Learn how to set up and initialize the Sentiance SDK before using its features.

{% content-ref url="/pages/6FBkFuXVPG2iABaEb1qZ" %}
[Android](/getting-started/sdk-integration/3.-sdk-initialization/android)
{% endcontent-ref %}

{% content-ref url="/pages/wcetN9omUP65JbzNVdF5" %}
[iOS](/getting-started/sdk-integration/3.-sdk-initialization/ios)
{% endcontent-ref %}

{% content-ref url="/pages/2wEQlYgpIi3rHc7DOZNr" %}
[React Native](/getting-started/sdk-integration/3.-sdk-initialization/react-native)
{% endcontent-ref %}

{% content-ref url="/pages/xvEv9R0o9UXcP9g0zcE9" %}
[Flutter](/getting-started/sdk-integration/3.-sdk-initialization/flutter)
{% endcontent-ref %}


# Android

Initialization allows the SDK to perform detections in the background. You must always initialize the SDK before interacting with it. Only a limited set of SDK methods are safe to invoke on an uninitialized SDK.

## Steps

{% stepper %}
{% step %}

### Create an Application Class

Initialization must be done in the <mark style="color:$success;">**`onCreate()`**</mark> method of your <mark style="color:$success;">**`Application`**</mark> class. If you don't yet have a custom application class, first create a new class that extends <mark style="color:$success;">**`Application`**</mark>.

```kotlin
package com.example.myapp

import android.app.Application

class MyApplication: Application() {
    override fun onCreate() {
        super.onCreate()
    }
}
```

Then reference this new class in the application tag of the <mark style="color:$success;">**`AndroidManifest.xml`**</mark>

{% code title="AndroidManifest.xml" %}

```xml
<application
        android:name="com.example.myapp.MyApplication"
        ...
        >
        ...
</application>
```

{% endcode %}
{% endstep %}

{% step %}

### Initialize the SDK

In the <mark style="color:$success;">**`onCreate()`**</mark> method of your <mark style="color:$success;">**`Application`**</mark> class, call <mark style="color:$success;">**`initializeAsync()`**</mark>.

{% hint style="danger" %}
You must call **`initializeAsync()`**  on the main thread, directly from within **`onCreate()`**. Do not postpone the call or move it to a background thread, otherwise SDK detections will become unreliable.

To minimize the impact of initializing the SDK on your app's startup duration, we have designed **`initializeAsync()`** to be very light and fast.

For more information about this requirement, see [this page](/sdk/appendix/user-deletion).
{% endhint %}

```kotlin
package com.example.myapp

import android.app.Application
import android.util.Log
import com.sentiance.sdk.Sentiance

class MyApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        
        val options = SentianceOptions.Builder(this).build()
        Sentiance.getInstance(this).initializeAsync(options)
            .addOnCompleteListener { operation ->
                if (operation.isSuccessful) {
                    Log.d(TAG, "Initialization succeeded");
                } else {
                    val reason = operation.error.failureReason.name
                    val throwable = operation.error.throwable
                    Log.e(TAG, "Intialization failed with reason ${reason}", throwable);
                }
            }
    }
    
    companion object {
        private const val TAG = "Sentiance SDK"
    }
}
```

{% endstep %}

{% step %}

### Customize the Android Notification (Optional)

When the Sentiance SDK runs in the background, it starts a foreground service. This causes Android to display a notification in the notification shade, and an icon on the system bar.

You can customize this notification to tailor its appearance and behavior to match the branding and user experience of your application. This allows you to provide a seamless and consistent experience to your users while the Sentiance SDK operates in the background.

Please note that customizing the notification should be done carefully to ensure that it complies with Android guidelines and user preferences. By doing so, you can enhance the overall user experience and maintain a cohesive presentation of your app and the Sentiance SDK's background functionality.

You can specify the notification that Android should use via the SDK's initialization option:

```kotlin
override fun onCreate() {
    super.onCreate()

    val options = SentianceOptions.Builder(this)
        .setNotification(notification, notificationId)
        .build()
    Sentiance.getInstance(this).initializeAsync(options)
        ..
}
```

If the <mark style="color:$success;">**`notificationId`**</mark> that you specify here matches the ID of another notification, Android will show only one notification (the last published one). If your app has an ongoing notification of its own, you can pass the same ID to avoid multiple notifications during SDK detections.

Be sure to check our [notification management page](https://docs.sentiance.com/important-topics/sdk/appendix/android/notification-management) for more on the best practices of defining a service notification.
{% endstep %}

{% step %}

### Next: User Creation

After having successfully initialized the Sentiance SDK, you must now create a user. To do so, follow the instructions on [this page](/getting-started/sdk-integration/4.-user-creation).
{% endstep %}
{% endstepper %}


# iOS

Initialization allows the SDK to perform detections in the background. You must always initialize the SDK before interacting with it. Only a limited set of SDK methods are safe to invoke on an uninitialized SDK.

### Steps

{% stepper %}
{% step %}

### Create an AppDelegate Class

Initialization must be done in the <mark style="color:$success;">**`application:didFinishLaunchingWithOptions:`**</mark> method of your <mark style="color:$success;">**`AppDelegate`**</mark> class. If you don't already have a custom AppDelegate set up, first create a new class that extends <mark style="color:$success;">**`UIApplicationDelegate`**</mark>.

<details>

<summary>UIKit app</summary>

```swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        true
    }
}
```

</details>

<details>

<summary>SwiftUI app</summary>

{% code fullWidth="false" %}

```swift
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {        
        return true
    }
}

struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        ..
    }
}
```

{% endcode %}

</details>
{% endstep %}

{% step %}

### Initialize the SDK

In the <mark style="color:$success;">**`application:didFinishLaunchingWithOptions:`**</mark> method of your <mark style="color:$success;">**`AppDelegate`**</mark> class, call <mark style="color:$success;">**`initializeAsync()`**</mark>.

{% hint style="danger" %}
You must call **`initializeAsync()`**  on the main thread, directly from within **`application:didFinishLaunchingWithOptions:`**. Do not postpone the call or move it to a background thread, otherwise SDK detections will become unreliable.

To minimize the impact of initializing the SDK on your app's startup duration, we have designed **`initializeAsync()`** to be very light and fast.

For more information about this requirement, see [this page](/sdk/appendix/user-deletion).
{% endhint %}

```swift
import SENTSDK

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let options = SENTOptions(for: .appLaunch)
        Sentiance.shared.initializeAsync(options: options, launchOptions: launchOptions) { result, error in
            if let result {
                NSLog("Initialization succeeded")
            }
            else if let error {
                NSLog("Initialization failed with reason \(error.failureReason)")
            }
        }
        
        return true
    }
}
```

{% endstep %}

{% step %}

### Next: User Creation

After having successfully initialized the Sentiance SDK, you must now create a user. To do so, follow the instructions on [this page](/getting-started/sdk-integration/4.-user-creation).
{% endstep %}
{% endstepper %}


# React Native

The SDK supports React Native via both the CLI and Expo workflows.

{% content-ref url="/pages/4JOG6ZOfnGOBLPpAo4X9" %}
[CLI](/getting-started/sdk-integration/3.-sdk-initialization/react-native/cli)
{% endcontent-ref %}

{% content-ref url="/pages/wh5ImugwDF7KXB0WUb2K" %}
[Expo](/getting-started/sdk-integration/3.-sdk-initialization/react-native/expo)
{% endcontent-ref %}


# CLI

If you are building your app without using a Framework.

Initialization allows the SDK to perform detections in the background. You must always initialize the SDK before interacting with it. Only a limited set of SDK methods are safe to invoke on an uninitialized SDK.

Initialization requires making some changes inside the **native Android and iOS folders**. The SDK initializes **asynchronously**, so its native bootstrap may still be in progress after your app has started and your JavaScript/TypeScript code begins running. Because most SDK methods require an initialized SDK, always guard your calls by awaiting <mark style="color:$success;">`ensureInitialized()`</mark> before interacting with the SDK from JS/TS.

## Steps

{% stepper %}
{% step %}

### Initialize the Sentiance SDK for iOS

The correct way to natively initialize on iOS is to do it inside the <mark style="color:$success;">`application:didFinishLaunchingWithOptions:`</mark> method of the <mark style="color:$success;">`AppDelegate`</mark> class

{% code title="AppDelegate.swift" fullWidth="false" %}

```swift
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import RNSentianceCore

@main
class AppDelegate: RCTAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    // ...

    RNSentianceHelper().initializeAsync(launchOptions: launchOptions) { result, error in
      if error == nil {
        print("Sentiance SDK Initialization Success")
      } else {
        print("Sentiance SDK Initialization Failed: \(error!.failureReason)")
      }
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
```

{% endcode %}

Launch the iOS app from Xcode and confirm that the log shows **“Sentiance SDK Initialization Success”.**
{% endstep %}

{% step %}

### Initialize the Sentiance SDK for Android

Add the following code inside the <mark style="color:$success;">**`onCreate()`**</mark> method of your <mark style="color:$success;">**`Application`**</mark> class just after the <mark style="color:$success;">**`loadReactNative`**</mark> and <mark style="color:$success;">**`ApplicationLifecycleDispatcher`**</mark> function calls:

```kotlin
import android.util.Log
import com.sentiance.react.bridge.core.SentianceHelper

private const val TAG = "Sentiance"

override fun onCreate() {
    super.onCreate()
    // ...

    val sentianceHelper = SentianceHelper.getInstance(this)
    sentianceHelper.initializeAsync()?.addOnCompleteListener { operation ->
      if (operation.isSuccessful) {
        Log.d(TAG, "Sentiance SDK Initialization succeeded")
      } else {
        Log.e(TAG, "Sentiance SDK Initialization failed with reason " + operation.error?.failureReason)
      }
    }
}
```

Run the app, open a new Terminal window and verify logs by running the command:

```bash
adb logcat | grep Sentiance
```

{% endstep %}

{% step %}

### Ensure the SDK is initialized from JS/TS code

Because initialization runs asynchronously, <mark style="color:$success;">`await ensureInitialized()`</mark> before your first SDK call to make sure the SDK is ready. It resolves once the SDK is initialized, and throws an <mark style="color:$danger;">`SdkInitializationError`</mark> if initialization failed or was never triggered.

```typescript
import SentianceCore, {
  SdkInitializationError,
} from "@sentiance-react-native/core";

try {
  // Resolves once native async init completes (immediately if already initialized).
  await SentianceCore.ensureInitialized();
  console.log("Sentiance SDK is initialized");

  // Safe to interact with the SDK from here on.
  const userExists = await SentianceCore.userExists();
  console.log("Sentiance user exists:", userExists);
} catch (err) {
  if (!(err instanceof SdkInitializationError)) {
    console.error("Unexpected error while awaiting Sentiance SDK init", err);
  } else {
    switch (err.reason) {
      case "NOT_TRIGGERED":
        // The native initializer was never called from Application.onCreate / AppDelegate.
        console.error("Sentiance SDK init was never triggered natively");
        break;
      case "EXCEPTION_OR_ERROR":
        console.error("Sentiance SDK init hit an exception or error:", err.message);
        break;
        
      // Handle the other cases here  
    }
  }
}
```

{% endstep %}

{% step %}

### Next: User Creation

After having successfully initialized the Sentiance SDK, you must now create a user. To do so, follow the instructions on [this page](/getting-started/sdk-integration/4.-user-creation).
{% endstep %}
{% endstepper %}


# Expo

If you are using Expo to build your app.

Initialization allows the SDK to perform detections in the background. You must always initialize the SDK before interacting with it. Only a limited set of SDK methods are safe to invoke on an uninitialized SDK.

## Steps

{% stepper %}
{% step %}

### Setup the Sentiance Core Expo config plugin

The Sentiance Core config plugin is responsible for initializing the SDK during app startup. Make sure it is configured in your `app.json` before proceeding. \
\
Refer to the [Core integration guide](/getting-started/sdk-integration/2.-including-the-sdk/react-native/expo#configure-the-sentiance-expo-config-plugin) if you haven't set it up yet.
{% endstep %}

{% step %}

### Ensure the SDK is initialized from JS/TS code

The Sentiance Core config plugin initializes the SDK asynchronously, so make sure to <mark style="color:$success;">`await ensureInitialized()`</mark> before your first SDK call to make sure the SDK is ready. It resolves once the SDK is initialized, and throws an <mark style="color:$primary;">`SdkInitializationError`</mark> if initialization failed or was never triggered.

```tsx
import SentianceCore, {
  SdkInitializationError,
} from "@sentiance-react-native/core";

try {
  // Resolves once native async init completes (immediately if already initialized).
  await SentianceCore.ensureInitialized();
  console.log("Sentiance SDK is initialized");

  // Safe to interact with the SDK from here on.
  const userExists = await SentianceCore.userExists();
  console.log("Sentiance user exists:", userExists);
} catch (err) {
  if (!(err instanceof SdkInitializationError)) {
    console.error("Unexpected error while awaiting Sentiance SDK init", err);
  } else {
    switch (err.reason) {
      case "NOT_TRIGGERED":
        // The native initializer was never called from Application.onCreate / AppDelegate.
        console.error("Sentiance SDK init was never triggered natively");
        break;
      case "EXCEPTION_OR_ERROR":
        console.error("Sentiance SDK init hit an exception or error:", err.message);
        break;
        
      // Handle the other cases here  
    }
  }
}
```

{% endstep %}

{% step %}

### Next: User Creation

After having successfully initialized the Sentiance SDK, you must now create a user. To do so, follow the instructions on [this page](/getting-started/sdk-integration/4.-user-creation).
{% endstep %}
{% endstepper %}


# Flutter

Initialization allows the SDK to perform detections in the background. You must always initialize the SDK before interacting with it. Only a limited set of SDK methods are safe to invoke on an uninitialized SDK.

Initialization is asynchronous. This means your Dart code can start running before the native SDK has finished initializing. Most SDK methods fail if called before initialization completes, so in Dart you must <mark style="color:$success;">**`await SentianceCore.ensureInitialized()`**</mark> before your first SDK call and handle a possible initialization failure.

## Steps

{% stepper %}
{% step %}

### Initialize the Sentiance SDK for iOS

Make sure the initialization is triggered before the <mark style="color:$success;">**`didFinishLaunchingWithOptions`**</mark> method returns.

1. Open <mark style="color:$success;">**`ios/Runner/AppDelegate.swift`**</mark>
2. Import the <mark style="color:$success;">**`sentiance_core`**</mark> package
3. Call the <mark style="color:$success;">**`initializeAsync`**</mark> method provided by the <mark style="color:$success;">**`SentianceCorePlugin`**</mark> class
4. Log the outcome from the completion handler

{% code title="AppDelegate.swift" %}

```swift
import Flutter
import UIKit
import sentiance_core

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    SentianceCorePlugin.shared.initializeAsync(launchOptions: launchOptions) { result, error in
      if result != nil {
        print("Sentiance SDK initialization succeeded")
      } else {
        print("Sentiance SDK initialization failed, reason: \(String(describing: error?.failureReason))")
      }
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
```

{% endcode %}
{% endstep %}

{% step %}

### Initialize the Sentiance SDK for Android

1. Open the file where your Application class resides, or create a new Application class (make sure to update AndroidManifest.xml to point to the newly created Application class)
2. Locate the <mark style="color:$success;">**`onCreate`**</mark> method
3. Import the Sentiance core plugin
4. Call the static <mark style="color:$success;">**`initializeAsync`**</mark> method on the <mark style="color:$success;">**`CorePlugin`**</mark> class
5. Log the outcome from the completion listener

{% code title="MainApplication.kt" %}

```kotlin
package com.your.domain

import android.app.Application
import android.util.Log
import com.sentiance.core_plugin.CorePlugin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        CorePlugin.initializeAsync(this)
            .addOnCompleteListener { op ->
                if (op.isSuccessful) {
                    Log.d("Sentiance", "Sentiance SDK initialization succeeded")
                } else {
                    Log.e("Sentiance", "Sentiance SDK initialization failed, reason: ${op.error?.failureReason}")
                    op.error?.throwable?.let { Log.e("Sentiance", "throwable: ${it.message}") }
                }
            }
    }
}

```

{% endcode %}

Run the app, open a new Terminal window and verify logs by running the command:

```cmd
adb logcat | grep Sentiance
```

{% endstep %}

{% step %}

### Ensure the SDK is initialized from Dart code

Because initialization is asynchronous, <mark style="color:$success;">**`await SentianceCore.ensureInitialized()`**</mark> before your first SDK call. It completes once initialization finishes, and throws a <mark style="color:$primary;">**`SdkInitializationError`**</mark> if initialization failed or was never triggered.

{% code title="app.dart" %}

```dart
import 'package:sentiance_core/sentiance_core.dart';

try {
  await SentianceCore.ensureInitialized();
  // The SDK is initialized. It is now safe to call other SDK methods.
} on SdkInitializationError catch (e) {
  switch (e.reason) {
    case SdkInitializationFailureReason.notTriggered:
      // Init was never started, or was started late / off the main thread.
      // Make sure initializeAsync runs synchronously from your native entry point.
      break;
    case SdkInitializationFailureReason.exceptionOrError:
      // An error occurred during initialization, or the native reason was unrecognized.
      break;
      
    // Handle the other cases here  
  }
  print('Sentiance SDK initialization failed: ${e.reason}, ${e.message}');
} on PlatformException catch (e) {
  // An unexpected error occurred while talking to the host platform.
  print('Unexpected error: ${e.message}');
}
```

{% endcode %}
{% endstep %}

{% step %}

### Next: User Creation

After having successfully initialized the Sentiance SDK, you must now create a user. To do so, follow the instructions on [this page](/getting-started/sdk-integration/4.-user-creation).
{% endstep %}
{% endstepper %}


# 4. User Creation

Learn how to create and link a user to enable the Sentiance SDK's core functionality.

Please checkout  [SDK and User Management](/sdk/appendix/user-deletion) to understand what a Sentiance user is and how the SDK manages users and devices.

## Steps

{% stepper %}
{% step %}

### Prerequisites

As part of user creation, you will need the following:

1. **Sentiance API Key**\
   A valid Sentiance API key with the <mark style="color:$success;">**`user_link`**</mark> permission. Refer to the [API key creation guide](/getting-started/insights-control-tower/developer-dashboard/api-keys) to generate a key and give it the correct permission.
2. **User Creation Endpoint**\
   An API endpoint on your backend that accepts user creation requests from your app, and forwards them to Sentiance. It should use the Sentiance API Key for authentication.
   {% endstep %}

{% step %}

### Request an Authentication Code

1. From your app, send a user creation request to your own backend.
2. Forward this request to Sentiance by making an HTTP POST request to obtain an **authentication code**. The request must include a unique app-user identifier. Sentiance’s default (Europe) API endpoint is <mark style="color:$success;">**`https://api.sentiance.com`**</mark>. If you have specific regional requirements, contact Sentiance.

```http
POST /users/auth-code

Content-Type: application/json
Authorization: Bearer <sentiance-api-key>

{
    external_id: <unique-app-user-identifier>
}
```

3. The Sentiance API will respond with a json object containing an <mark style="color:$success;">**`authentication_code`**</mark>. This code is **valid for 10 minutes**. Forward it to your app, and use it to create a Sentiance user.

Here are code samples in Express (Nodejs) and Flask (Python) for handling user creation requests on your backend.

<details>

<summary>Complete Nodejs (Express) endpoint code sample</summary>

```javascript
const USER_LINK_API_KEY = "ABCDEF123456789...."; 
const authUrl = "https://api.sentiance.com/users/auth-code";

app.post("/sentiance-user-create", await (req,res) =>{
// validate the request and identify the user
  const externalID = "user_001";  // Identifier for this particular user: (email, phone number, userID)
  try {
      const sentianceRsponse = await fetch(authURL, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${USER_LINK_API_KEY}`,
          "Content-Type": "application/json",
          "Accept": "application/json"
        },
        body: JSON.stringify({
          external_id: externalID
        })
      });
    
      // validate the response to the sentiance platform
      const data = await sentianceRsponse.json();
      res.send(data["authentication_code");
  } catch (error) {
    console.error("Error getting auth code:", error);
      // respond with 4xx
  }
});


```

</details>

<details>

<summary>Complete Python (Flask) endpoint code sample</summary>

```python
import requests
from flask import Blueprint, jsonify, current_app
​
auth_bp = Blueprint("auth_bp", __name__)
​
@auth_bp.route("/get-auth-code", methods=["GET"])
def get_auth_code():
​
    ## DONT PUT YOUR API KEY ON YOUR APP -- SAMPLE ONLY
    USER_LINK_API_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    EXTERNAL_ID = "customer_id_001"
    sentiance_url = "https://api.sentiance.com/users/auth-code"
​
​
    ## Where api_key is the USER_LINK API Key you generated in ICT
    headers = {
        "Authorization": f"Bearer {USER_LINK_API_KEY}",
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
​
    ## Where EXTERNAL_ID is user identifier you choose to assign
    ## (e.g., email address, userID, etc.).
    payload = {
        "external_id": EXTERNAL_ID
    }
​
    ## Send the "POST" request to Sentiance URL
    response = requests.post(
        sentiance_url,
        headers=headers,
        json=payload
    )
    return response.json()

```

</details>

#### UserCreation Data Flow

<figure><img src="/files/2h7Q5wMjB02dMwVmDAIm" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create a User

Create a user by calling the SDK's <mark style="color:$success;">**`createUser()`**</mark> method, and passing it the authentication code that you received from Sentiance.

{% tabs %}
{% tab title="iOS" %}

```swift
import SENTSDK

let options = SENTUserCreationOptions(authenticationCode: authCode)
Sentiance.shared.createUser(options: options) { result, error in
    guard let result = result else {
        NSLog("User creation failed with reason \(error!.failureReason)")
        return
    }
    NSLog("Created a user with ID: \(result.userInfo.userId)")
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
import com.sentiance.sdk.Sentiance
import com.sentiance.sdk.usercreation.UserCreationOptions

val options = UserCreationOptions.Builder(authenticationCode).build()
Sentiance.getInstance(context).createUser(options).addOnCompleteListener { operation ->
    if (operation.isSuccessful) {
        val userInfo = operation.result.userInfo
        Log.d(TAG, "Created a user with ID: ${userInfo.userId}")
    } else {
        val error = operation.error
        Log.e(TAG, "User creation failed with reason ${error.reason.name}. Details: ${error.details}")
    }
}
```

{% endtab %}

{% tab title="React Native" %}

```typescript
import SentianceCore from '@sentiance-react-native/core';

async function createUser(authenticationCode: string) {
  try {
    const result = await SentianceCore.createUser({ authCode: authenticationCode })
    console.log(`Created a user with ID: ${result.userInfo.userId}`);
  } catch (e) {
    console.log(`User creation failed with error: ${e}`);
  }
}
```

{% endtab %}

{% tab title="Flutter " %}

```dart
import 'package:sentiance_core/sentiance_core.dart';

final sentiance = SentianceCore();

Future createUser(String authCode) {
  try{
    final result = await sentiance.createUser(CreateUserOptions(authCode: authCode));
    // check the returned CreateUserResult
    print("Created a user with ID: ${result.userInfo.userId}")
    
  } catch (e){ 
    // perform action on failed user creation 
    print("User creation failed with error: ${e}");
  }
}
```

{% endtab %}
{% endtabs %}

#### Specifying a Sentiance API Endpoint

If your Sentiance account is not located in the default Europe region, you must specify the corresponding Sentiance API endpoint when creating a user. Here's an example.

{% tabs %}
{% tab title="iOS" %}

```swift
let userCreationOptions = SENTUserCreationOptions(authenticationCode: authCode)
userCreationOptions.platformUrl = "https://api.p15.sentiance.com"
```

{% endtab %}

{% tab title="Android" %}

```kotlin
val createOptions = UserCreationOptions.Builder(authCode)
    .setPlatformUrl("https://api.p15.sentiance.com")
    .build()
```

{% endtab %}

{% tab title="React Native" %}

```typescript
await SentianceCore.createUser({ 
    platformUrl: "https://api.p15.sentiance.com", 
    authCode 
})
```

{% endtab %}

{% tab title="Flutter" %}

```dart
await sentiance.createUser(
    CreateUserOptions(
      authCode: authCode,
      platformUrl: "https://api.p15.sentiance.com",
    ),
);
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Next: Enabling Detections

After having successfully created a Sentiance user, you can proceed to enable SDK detections. To do so, follow the instructions on [this page](/getting-started/sdk-integration/5.-enabling-detections).
{% endstep %}
{% endstepper %}


# 5. Enabling Detections

Learn how to turn on detections to allow the Sentiance SDK to begin monitoring trips and events.

After creating a Sentiance user, you should enable SDK detections. This will allow the SDK to run in the background, and intelligently detect the user's movements patterns, driving habits, and other activities.

## Enable Detections

You can enable detections by calling <mark style="color:$success;">**`enableDetections()`**</mark>. Here are examples of how to do that on each platform:

{% tabs %}
{% tab title="iOS" %}
{% code expandable="true" %}

```swift
Sentiance.shared.enableDetections { result, error in
    guard let result = result else {
        NSLog("Failed to enable detections due to reason \(error!.failureReason)")
        return
    }
    
    NSLog("Successfully enabled detections")
}
```

{% endcode %}

After detections are successfully enabled, you can determine whether the SDK is able to detect, by checking the detection status:

```swift
switch result.detectionStatus { // or Sentiance.shared.detectionStatus
case .enabledAndDetecting:
    // Detections are enabled and running
case .enabledButBlocked:
    // Detections are enabled but blocked (e.g. missing permission issue)
default:
    // Other enum values don't apply for the success scenario
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
val sentiance = Sentiance.getInstance(context)
sentiance.enableDetections().addOnCompleteListener { operation ->
    if (operation.isSuccessful) {
        Log.d(TAG, "Successfully enabled detections")
    } else {
        val error = operation.error;
        Log.e(TAG, "Failed to enabled detections due to reason {${error.reason}")
    }
}
```

After detections are successfully enabled, you can determine whether the SDK is able to detect, by checking the detection status:

```kotlin
val detectionStatus = operation.result.detectionStatus // or Sentiance.getInstance(context).detectionStatus
when (detectionStatus) {
    DetectionStatus.ENABLED_AND_DETECTING -> 
        // Detections are enabled and running
    DetectionStatus.ENABLED_BUT_BLOCKED ->
        // Detections are enabled but blocked (e.g. missing permission issue)
    else ->
        // Other enum values don't apply for the success scenario
}
```

{% endtab %}

{% tab title="React Native" %}

```typescript
import { enableDetections } from '@sentiance-react-native/core';
try {
    const result = await enableDetections();
    console.log(`SDK detection status is now ${result.detectionStatus}`);
} catch(e) {
    console.log('Failed to start detections: ' + e);
}
```

After detections are successfully enabled, you can determine whether the SDK is able to detect, by checking the detection status:

```typescript
const detectionStatus = result.detectionStatus;

switch(detectionStatus) {
    case 'ENABLED_AND_DETECTING':
        // Detections are enabled and running
        break;
    case 'ENABLED_BUT_BLOCKED':
        // Detections are enabled but blocked (e.g. missing permission issue)
        break;
    default:
        // Other enum values don't apply for the success scenario
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
final sentiance = SentianceCore();

try {
    await sentiance.enableDetections();
    print("Detections are now enabled.");
} on EnableDetectionsError catch (e) {
    print('Failed to enable detections, reason: ${e.reason}');
} catch (e) {
    print(e);
}
```

After detections are successfully enabled, you can determine whether the SDK is able to detect, by checking the detection status:

```dart
final sdkStatus = await sentiance.getSdkStatus();
switch (sdkStatus.detectionStatus) {
    case DetectionStatus.enabledAndDetecting:
    // Detections are enabled and running
    case DetectionStatus.enabledButBlocked:
    // Detections are enabled but blocked (e.g. missing permission issue)
    default:
    // Other enum values don't apply for the success scenario
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Enabling Detections Is Persistent**

When you enable detections, the SDK will remember this and automatically enable them the next time the app restart. Therefore, you do not need to enable them every time. You only need to make sure that you are properly initializing the SDK during every app startup.
{% endhint %}

If the resulting detection status is "**enabled and detecting**", then detections are successfully running. However, if the status is "**enabled but blocked**", then an issue is preventing detections from starting. Once the issue is resolved, detection will start running automatically. Most likely causes for blocked detection are:

* Missing permission.
* Location service disabled on the device.
* OS restrictions, such as background execution restriction on Android.
* Device is in airplane mode.
* The user account is deactivated.

To find out the exact reason, head over to [the next section](/getting-started/sdk-integration/6.-sdk-status-updates) to learn about how you can check the SDK's status.


# 6. SDK Status Updates

Learn how to monitor the SDK's status and respond to changes, ensuring permissions and configurations remain properly set up over time.

You can stay up to date with changes to the SDK's detection status by setting an **SDK status update listener**. SDK status updates are usually triggered by changes to the device state and settings (e.g. airplane mode, location permission, etc). Handling these updates gives you the chance to instruct your user, when applicable, to properly adjust the device settings for optimal SDK detections.

## Checking the SDK Status

{% tabs %}
{% tab title="iOS" %}
To subscribe for SDK status updates:

```swift
Sentiance.shared.setDidReceiveSdkStatusUpdateHandler { status in
    // Handle SDK status updates
}
```

To retrieve the current SDK status:

```swift
let sdkStatus = Sentiance.shared.sdkStatus
```

{% endtab %}

{% tab title="Android" %}
To subscribe for SDK status updates:

```kotlin
Sentiance.getInstance(this).setSdkStatusUpdateListener { status -> 
    // Handle SDK status updates
}
```

To retrieve the current SDK status:

```kotlin
val sdkStatus = Sentiance.getInstance(this).sdkStatus
```

{% endtab %}

{% tab title="React Native" %}
To get SDK status updates **in the background**, add the code the below inside your app's `index.js/ts` file:

```typescript
import SentianceCore, { addSdkStatusUpdateListener } from "@sentiance-react-native/core";

await SentianceCore.ensureInitialized(); // Ensure SDK is initialized

addSdkStatusUpdateListener((sdkStatus) => {
    // Check sdkStatus.detectionStatus to determine whether detections are 
    // running, and if not, check the various status fields to see what 
    // might be blocking the detections.
});
```

If you're only interested in such updates when the app is in the foreground, then place this code inside your UI code.

To retrieve the current SDK status:

```typescript
import { getSdkStatus } from '@sentiance-react-native/core';

const sdkStatus = await getSdkStatus();
```

{% endtab %}

{% tab title="Flutter" %}
To retrieve the current SDK status:

```dart
import 'package:sentiance_core/sentiance_core.dart' 
    show SentianceCore, DetectionStatus;

final sentiance = SentianceCore();
final sdkStatus = await sentiance.getSdkStatus();
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
To find out more about the different SDK Status fields and why detections might be blocked, check out the SDK Status documentation for [iOS](broken://pages/POK9eZRe3y5qg5RZMQoc) and [Android](broken://pages/rgc1AKjbW9IZXD93CFC9).
{% endhint %}

{% hint style="success" %}
Congratulations! Your core Sentiance SDK integration is now complete.&#x20;
{% endhint %}

## Next: Feature Integration

To start utilizing the various Sentiance SDK features, head on over to the [feature implementation](/implementing-features/insights) section.


# Accessing Insights

To access the valuable insights provided by Sentiance, you have a range of flexible options at your disposal. These options cater to various integration preferences and can be tailored to suit your specific requirements. Let's explore the different means of accessing Insights:

{% stepper %}
{% step %}

### **SDK API's**

Native iOS, Android, React Native and Flutter APIs exposed by the Sentiance SDK can be seamlessly integrated into your application. This allows you to access Insights directly on the user's device, enabling real-time and personalized experiences.
{% endstep %}

{% step %}

### **Offloads**

Pre-signed URLs provide direct access to downloadable files containing insights gathered from your users. The data is batched at defined intervals. These URLs can be generated and made available via our Cloud GraphQL API or through ICT with Developer access.&#x20;

Checkout [this page](/getting-started/accessing-insights/offloads) for more details regarding offloads
{% endstep %}

{% step %}

### **Cloud GraphQL API**

Make use of the GraphQL server provided by Sentiance to query Insights. Access Insights from either your mobile application ( using SDK token) or backend server (using API Key).

Checkout [this page](/getting-started/accessing-insights/cloud-api) for more details regarding the Cloud GraphQL API
{% endstep %}
{% endstepper %}

{% hint style="info" %}
The specific features and access mechanisms available to you depend on the configuration settings for your app, which are based on the Statement of Work (SOW) you have with Sentiance. This ensures that your access options are tailored to your unique use case and business needs.
{% endhint %}

<br>

<br>


# SDK APIs

Sentiance Insights is available through the APIs exposed by the Sentiance SDK for:

* iOS
* Android
* React Native
* Flutter

### Fetching Insights

Depending on your use-case and the technical design of your application, you have two options to retrieve insights:

1. Querying the SDK on Demand
2. Listening to Callbacks for Real-time Updates

#### Querying the SDK on Demand

You can query the SDK for insights at any point in your application logic. The following example demonstrates how to fetch the recent timeline events:

{% tabs %}
{% tab title="iOS" %}

```swift
let currentDate = Date()
let calendar = Calendar.current
let fiveDaysAgo = calendar.date(byAdding: .day, value: -5, to: currentDate)

let events = Sentiance.shared.getTimelineEvents(from: fiveDaysAgo!, to: currentDate)
events.forEach { event in
    print(event.eventId)
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
val from = Date(0)
val to = Date(Long.MAX_VALUE)

EventTimelineApi.getInstance(context).getTimelineEvents(from, to).forEach { event ->
    print("Event ID: ${event.id}")
}
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import EventTimelineApi from '@sentiance-react-native/event-timeline';

const from = new Date(0); // 01/01/1970
const to = new Date(2 ** 31 * 1000);

const events = await EventTimelineApi.getTimelineEvents(from.getTime(), to.getTime());
for (const event of events) {
    console.log(`Event ID: ${event.id}`);
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';

final sentianceEventTimeline = SentianceEventTimeline();
const int maxLongValue = 9223372036854775807;
final events = await sentianceEventTimeline.getTimelineEvents(0, maxLongValue);
for (var event in events) {
    print("Event ID: ${event.id}");
}
```

{% endtab %}
{% endtabs %}

Similarly, you can request various insights. Read through the [full example](/implementing-features/insights) and explore [SDK API Reference](/sdk/api-reference) for a comprehensive list of available APIs

#### Listening to Callbacks for Real-time Updates

When setting up callbacks for real-time updates, it's crucial to ensure that you attach the listener at the appropriate location in your code.

The listeners should be included in the app boot-up scripts, right after executing the SDK initialization commands.

Checkout [Real-Time Listeners](/sdk/appendix/real-time-listeners) for more details

#### Synchronising Insights with Your Backend

A common practice involves ensuring the insights are effectively synchronised with your backend storage systems. This step is essential for integrating real-time data insights directly into your data management workflows.

Checkout [Data Synchronization](/sdk/appendix/data-sychronization) for more details


# Offloads

Sentiance provides data offloads with Sentiance Insights to the clients on a daily basis.

## What are Offloads

The Sentiance platform offers various methods to access and analyze your end-users' data, one of which is through offloads our implementation of a file-based system, typically in json format (or CSV format for older product offerings). Offloads generally contain data for all your users over a specific timeframe, by default a 24-hour tumbling window. Detailed field descriptions can be found below.

{% hint style="warning" %}
While offloads contain much of the core data, they do not include certain elements such as predictions, coaching data, or some fine-grained details.

*Please consult with your Sentiance contact person to discuss the offloads before implementing.*
{% endhint %}

## Accessing Offloads

Access to offloads is managed by the [Sentiance Cloud API](/getting-started/accessing-insights/cloud-api), with permissions authorized by\
[API Key Scopes](/sdk/appendix/request-authentication#api-keys):

* OFFLOADS\_READ
* OFFLOADS\_GENERATE\_URL

To check available offloads, use an API Key with the `OFFLOADS_READ` scope. The query example is:

```graphql
query {
  offloads(from: "2026-03-23", to: "2026-03-23") {
    day
    type
    files {
      format
      link {
        expires_at
        url
      }
      name
      size
    }
  }
}
```

`from` and `to` are always days in the format `YYYY-MM-DD`. The response looks like the following.

```json
{
  "data": {
    "offloads": [
      {
        "day": "2026-03-23",
        "files": [
          { "format": "json.gz", "link": null, "name": "driving_insights", "size": 5523310 }
        ],
        "type": "OFFLOAD_TYPE_DRIVING_INSIGHTS"
      },
      {
        "day": "2026-03-23",
        "files": [
          { "format": "json.gz", "link": null, "name": "crash_insights", "size": 8943 }
        ],
        "type": "OFFLOAD_TYPE_CRASH_INSIGHTS"
      },
      {
        "day": "2026-03-23",
        "files": [
          { "format": "json.gz", "link": null, "name": "badges", "size": 5284 }
        ],
        "type": "OFFLOAD_TYPE_BADGES"
      },
      {
        "day": "2026-03-23",
        "files": [
          { "format": "json.gz", "link": null, "name": "challenges", "size": 8534 }
        ],
        "type": "OFFLOAD_TYPE_CHALLENGES"
      },
      {
        "day": "2026-03-23",
        "files": [
          { "format": "json.gz", "link": null, "name": "streaks", "size": 3862 }
        ],
        "type": "OFFLOAD_TYPE_STREAKS"
      }
    ]
  }
}

```

Offloads of several types are available (based on which Sentiance product offering you are using). Each day will contain one offload of each type and each offload item may have one or more associated files to download.

The files list their format, name, size and a link to download that file.

In the response above the `link` field is null since no links have been generated for these files yet. Link generation is done with an API Key with the scope `OFFLOADS_GENERATE_URL`.

```graphql
mutation {
  generate_offload_url(day: "2026-03-23", offload_type: OFFLOAD_TYPE_DRIVING_INSIGHTS) {
    offload {
      day
      type
      files {
        format
        link {
          expires_at
          url
        }
        name
        size
      }
    }
  }
}
```

To generate a link to the offload files, you must use the [generate\_offload\_url](https://graphqldocs.sentiance.com/#mutation-generate_offload_url) mutation, giving the day of the offload and the type as input. This will respond with

```json
{
  "data": {
    "generate_offload_url": {
      "offload": {
        "day": "2026-03-23",
        "files": [
          {
            "format": "json.gz",
            "link": {
              "expires_at": "2026-04-07T11:32:29Z",
              "url": "https://sentiance-your-offloads.s3.eu-west-1.amazonaws.com/00000000000000000000000a/2026-03-23_0/driving_insights.json.gz?..."
            },
            "name": "driving_insights",
            "size": 5523310
          }
        ],
        "type": "OFFLOAD_TYPE_DRIVING_INSIGHTS"
      }
    }
  }
}
```

Links expire after a set time and must be regenerated if needed. Offloads for the previous day are fully available, while current-day offloads may be incomplete.

## Types of Offloads

The following offloads are available based on your product selection on the Sentiance platform. The availability of some granular data fields also depends on your product selection.

### **Driving Insights**

The Driving Insights offload type is a bundle of all the insights related to transports that the Sentiance SDK has detected and processed on the users devices. Each json line contains results for one detected transport for a user, e.g. start time, end time, transport mode, waypoints, driving events, driving scores, ... For a detailed explanation of the attributes, see the [Driving Insights](/getting-started/features-catalog/driving-insights).

{% file src="/files/mfB1G49gLPTdeUllKInn" %}

### **Mobility**

The Mobility offload type is a subset of the Driving Insights offloads, without the driving events and driving scores data.

{% file src="/files/Uj3wXjrBiHFiU9pG2GYZ" %}

### **Crash Insights**

This offload file contains a list of transports during which a crash event was detected. The following context around the crash is available for each such transport:

* user identifier
* transport identifier
* timestamp
* latitude, longitude
* speed limit
* gps speed (from the latest gps signal before the crash)
* speed at impact (from all sensor data combined by the crash model)
* delta v
* max magnitude
* crash severity
* confidence

For more information about the crash insights feature, see [Crash Insights](/getting-started/features-catalog/crash-insights).

{% file src="/files/NMufHNvKNVnieGxzKLw0" %}

### Smart Geofences

These offloads contain all the Smart Geofence events that have been detected.

* user identifier
* event time
* latitude, longitude (of the triggering location)
* geofence event type (`ENTRY` or `EXIT` )
* geofence identifier
* geofence group identifier

{% file src="/files/V64nOipbQr1kZv1FhiiN" %}

For more information about the Smart Geofence feature, see [Smart Geofences](/getting-started/features-catalog/smart-geofences).

### Engagement

#### Custom Scores

These offloads contain the trip-by-trip scores and their corresponding user scores based on your integration with the Engagement platform. Can be similar to Driving Insights scores or different if you have a custom integration.

#### Badges

These offloads contain events related to your user’s progress towards achieving Badges. A badge state can change to be `ACTIVE`, `IN_PROGRESS` or `COMPLETED`.

#### Challenges

These offloads contain events related to your user’s progress towards completing a challenge. A challenge state can change to: `ACCEPTED`, `COMPLETED`, `IN_PROGRESS`, `FAILED`, `ABANDONED`.

For more information about the Challenges feature, see [Challenges](/implementing-features/engagement/challenges).

#### Streaks

These offloads contain events related to your user’s progress towards continuing and maintaining streaks. A streak state can change to: `NEW`, `CONTINUE`, `BREAK`.

For more information about the Streaks feature, see [Streaks](/implementing-features/engagement/streaks).

#### Groups

These offloads contain information about all social groups your users create.

For more information about the Social Groups feature, see [Social Groups](/implementing-features/engagement/social-groups).

#### Group Members

These offloads contain information about users who are part of groups and their role in the groups. A user can have one of these roles: `ADMIN`, `MEMBER`, `PENDING`.

For more information about the Social Groups feature, see [Social Groups](/implementing-features/engagement/social-groups).

#### Communications

These offloads contain information about each communication message that was generated for your users and whether or not a user interacted with it. A communication message can have one of these states: `NEW`, `MANUALLY_CLOSED`, `AUTOMATICALLY_CLOSED`.

For more information about the Communications feature, see [Communication Campaign](/implementing-features/engagement/communication-campaign).

#### Daily Rewards

Contains daily rewarded points for you users for the last 7 days.

For more information about the Rewards feature, see [Reward System](/implementing-features/engagement/reward-system).

### Appkit

{% hint style="info" %}
This offload types in this section are only available if you are using Sentiance Insights App or any custom Appkit Apps built by Sentiance.
{% endhint %}

#### Appkit All Users

Contains all the users who got signup with Appkit related apps.

#### Appkit Updated Users

Contains all the users who updated their user profiles. &#x20;

#### Appkit Deleted Users

Contains all the users who deleted their Appkit based app accounts.

### Other

#### Feedback

These offloads contain information about feedback provided by your users about our transport mode and occupant role predictions. This requires an integration of the mobile app with the Sentiance Feedback API (for Android: [Broken mention](broken://pages/Zh1NJbAPOYRH6P30TWoT), for iOS: [Broken mention](broken://pages/YCaLVvhc1vlDJPDA0NH5)).

#### App Events

These offloads contain events generated by your user by using the mobile app. This requires an integration of the mobile app with the [Sentiance backend API to submit logevents](https://graphqldocs.sentiance.com/#mutation-submit_log_event).

#### **Off-the-grids**

Off-the-grids correspond to segments of time when the user was offline and not sending data from their mobile device to our platform.\
For an explanation of the possible of the grid reasons see [here](broken://pages/-LXLM4YlU02S2LqqIDH5#off-the-grid-reasons).

## Deprecated types of Offloads

{% hint style="warning" icon="clock" %}
The following offload types are generated from results that are computed based on Cloud processing and are now deprecated. They are being phased out in a transition to full on-device processing for Driving Insights features.

The file format of these offloads is columnar CSV, and do not support nested data fields to the same extend as the json file format, and thus require additional post-processing to join all the attributes for one individual transport.
{% endhint %}

### **Driving Insights**

#### **Transports**

Transports represent the period of time a user moved from one venue to another. The transports offloads contain a list of transports and their corresponding predicted transport mode (e.g. car, train, bus, etc.), duration and top speed.

{% file src="/files/n2NwvVtNm8DstDiDXdtf" %}

#### **Driver Passenger**

The driver passenger offload contains a list of transports, together with a corresponding prediction of driver or passenger (of the mobile user).

{% file src="/files/zKfCRQsG0xiBI1Ruvb2b" %}

#### **Driving Events (all)**

This offload file contains a list of transports, together with their corresponding lists of *all* driving events. The following driving events are available:

* accelerating events
* phone handling events
* turning events
* mounted events
* braking events
* speeding events
* call events
* screen events

*All* driving events for transports are included in this offload.

{% file src="/files/KHY0OarwCPEoJA0jH4E2" %}

#### **Driving Events (significant)**

This offload file contains a list of transports, together with their corresponding lists of significant driving events. The following driving events are available:

* accelerating events
* phone handling events
* turning events
* mounted events
* braking events
* speeding events
* call events
* screen events

*Significant* events are a subset of all events, and only driving events that fall under certain thresholds are included (e.g. accelerations that have a high magnitude or calls that have a long duration).

{% file src="/files/vBVtAjZpzHEpCmojueCt" %}

#### **Primary Safety Scores (transports)**

These offloads contain a list of transports and their corresponding primary safety scores.

The primary safety scores available are:

* legal
* smooth
* attention
* overall

{% file src="/files/l8XsnSAJ2Pz0409yUFff" %}

#### **Secondary Safety Scores (transports)**

These offloads contain a list of transports and their corresponding secondary safety scores.

The secondary safety scores available are:

* focus
* harsh acceleration
* harsh braking
* harsh turning
* anticipation
* mounted

{% file src="/files/mIRt6WcPkYbYfrus0riK" %}

#### **Primary Safety Scores (users)**

These offloads contain a list of users and their corresponding primary safety scores, based on their overall driving.

The primary safety scores available are:

* legal
* smooth
* attention
* overall

{% file src="/files/RgE1azVMUAG9yiBX3mBQ" %}

#### **Secondary Safety Scores (users)**

These offloads contain a list of users and their corresponding secondary safety scores, based on their overall driving.

The secondary safety scores available are:

* focus
* harsh acceleration
* harsh braking
* harsh turning
* anticipation
* mounted

{% file src="/files/ODi7nUjABjC1YMXfSDTv" %}

#### **Trajectories**

Trajectories are information specific about the route that a transports followed. These offloads contain information such as the total distance traveled, the mapped waypoints of the transport, and the start and end addresses.&#x20;

{% file src="/files/IuFUUt8K23tq4rpf25rw" %}


# Cloud API

We provide a GraphQL-based Cloud API exclusively for interactions with the Engagement Platform and Engagement-related modules and features.

As the Engagement Platform processes data across your user base, the Cloud API enables access to user Engagement through a secure GraphQL server accessible through the following endpoints:

* **Default Endpoint**\
  `https://api.sentiance.com/v4/gql`
* **USA Endpoint**\
  `https://api.p15.sentiance.com/v4/gql`

Authentication to the GraphQL endpoint is available for both API Keys and SDK Tokens.&#x20;

* [**SDK Tokens**](/sdk/appendix/request-authentication#sdk-user-token) can be obtained from the SDK.
* [**API Keys**](/sdk/appendix/request-authentication#api-keys) can be created and obtained from the Insights Control Tower.

#### Resources

Visit our the Pages below for GraphQL model documentation and example uses

* **Engagement Queries**\
  <https://graphqldocs.sentiance.com/#query-engagement>
* **User Engagement Features**\
  <https://graphqldocs.sentiance.com/#definition-UserEngagement>

{% hint style="info" %}
GraphQL is a query language for APIs that allows clients to request exactly the data they need in a flexible and efficient way. To learn more about GraphQL and how to work with GraphQL APIs, visit: <https://graphql.org/>
{% endhint %}


# Firehose Webhooks

Real-time delivery of events to your backend via Webhooks.

## Overview

A common need among Sentiance customers is to react to user activity in real time whether that's enhancing a travel app based on mode of transport, or surfacing relevant offers when a user plans to visit a store.

Enter the **Firehose**: Firehose delivers a continuous stream of user activity events to your backend as they are processed on the users device. Delivery is handled through standard Webhooks.

## Webhooks

Firehose uses Webhooks to push event data in to your backend. When the Sentiance SDK detects a Transport, Driving Insights, or Crash Insights. it sends an HTTP POST request to your configured endpoint that has the relevant insights for you to immediatly interract with.

Webhook creation and management is done in the [**Insights Control Tower (ICT)**](/getting-started/insights-control-tower) under \
***Developer** → **Webhooks***, and requires the **Developer** role.

## Setup

Follow the steps below to configure and activate your Firehose Webhook.

1. Create a **POST endpoint** on your backend secured with Basic Auth or OAuth2. \
   It must return `200 OK` on success.
2. Create a **GET endpoint** on the same path with the same auth credentials. \
   It must return `200 OK` with the JSON body: `{"app_id": "<your Sentiance appID>"}`
3. Ensure both endpoints are reachable over **HTTPS** with an SSL certificate rated **grade B or higher**. Use [ssllabs.com](https://www.ssllabs.com/) to verify.
4. In ICT, navigate to Developer → Webhooks and click Create Webhook.
   1. Enter your endpoint URL, select your authorization method, and provide your credentials.
   2. Configure your max payload size, max wait time, and TTL
   3. Click Submit Request. This will start the verification process.

{% hint style="warning" %}
Firehose Webhook requests and changes must be confirmed and approved by Sentiance. \
Always consult with the Sentiance team when creating or updating.
{% endhint %}

Once all verifications pass and Sentiance approves the webhook, its status in ICT will update to **Verification successful** and delivery will begin.

You will be notified of any errors during the verification. Any other status code outside of `200 OK` by the **POST** and **GET** endpoints will result in an error during the verification process.

## Message Delivery

Once active, Sentiance delivers events to your endpoint in a consistent JSON(Envelope) format.\
The delivery system handles batching, error handling, retention, and security automatically.

Please see below for more details regarding these topics

### Batching

To reduce network overhead, messages are batched before delivery both by size and by time. \
A batch is sent when either threshold is reached first.

| Parameter        | Default   | Configurable Range |
| ---------------- | --------- | ------------------ |
| Max wait time    | 5 seconds | 1 – 300 seconds    |
| Max payload size | 1 MB      | 23 KB – 4 MB       |

That is to say that once *we have 1 MB worth of data* or *5 seconds have passed*, we will create a batch of data and send a request. These values can be configured during the Webhook setup

**Note:** Batching by size is done before gzip compression.

### Error handling and Retention

**Error Handling**

Firehose expects a `200 OK` response for every delivered message. Any other response or no response at all is treated as a failure and triggers automatic retries with exponential backoff starting from 100 ms up to 5 minutes.

{% hint style="info" %}
Sentiance guarantees at-least-once delivery. In rare cases where our server fails to register a successful response, the same message may be delivered more than once. Design your endpoint to handle duplicate events gracefully.
{% endhint %}

**Retention**&#x20;

Each webhook has a configurable **Message TTL** the maximum time Sentiance will retain and retry undelivered messages. Once a message exceeds its TTL, it is discarded. This ensures that we avoid sending stale information.

For example, if your **Message TTL** has been set to 30 minutes and your endpoint has been down for 40 minutes, on resuming you will only receive messages that are up to 30 minutes old. Messages from the first 10 minutes of downtime will have been dropped.

Set the TTL when creating your Webhook in ICT based on how much data staleness is acceptable for your use case.

### Security

**Authentication & Authorization**

Sentiance applies multiple layers of security to ensure message integrity and endpoint authenticity. To ensure that your messages originate from Sentiance and not from a malicious third party, we will set a **Basic Auth HTTP Authorization header** on every request as configured in the **Webhook Request**&#x20;

**TLS & SSL**

All connections must use HTTPS. Sentiance requires a **grade B or above** SSL certificate. Verify your endpoint at [ssllabs.com](https://www.ssllabs.com/).

All Firehose requests originate from the following dedicated IPs, Add these to your allowlist if your endpoint enforces IP filtering:

* 52.213.134.71
* 34.252.131.81

We perform a list of automated verifications before activating Firehose Webhooks.

<table><thead><tr><th width="216.3515625">Check</th><th>Description</th></tr></thead><tbody><tr><td>SSL Certificate</td><td>Valid certificate present on the endpoint</td></tr><tr><td>Auth Credentials</td><td>Correct Basic Auth credentials accepted</td></tr><tr><td>Auth Rejection</td><td>Incorrect credentials are rejected (tested explicitly)</td></tr><tr><td>App ID</td><td>GET endpoint returns the correct Sentiance Application ID<br>To ensure the data gets sent to the correct Application ID</td></tr><tr><td>Payload Handling</td><td>POST endpoint correctly processes test payloads.<br>These test payloads can be identified by looking for the HTTP header <code>sentiance-payload-type: test</code></td></tr></tbody></table>

Once automated checks pass, a member of the **Sentiance Client team** performs a final manual review. You will be notified by email when your Webhook is activated or contacted directly if any issues are found.

## Message Reference

All Webhook payloads are delivered in a standardised Envelope format, structured as follows:

* **Envelope** A parent object containing a single `data` child.
* **Data Array** An array of messages  either a single message or multiple if batched.
* **Each message contains:**
  * **`data`**  the detected insights details
    * varies depending on the `message_type` of the sibling `meta` object.
  * **`meta`**  message details, including timestamp, message type and app ID\
    which contains a `message_type` field indicating the kind of message being sent. Possible values for message\_type:
    * **on\_device\_transport\_processed**\
      Indicates a completed and processed transport.
    * **on\_device\_crash\_processed**\
      Indicates a detected and processed crash.
    * **on\_device\_geofence\_processed**\
      Indicates a geofence entry or exit.
    * **engagement**\
      Indicates an event from the Engagement platform. This message can have a sub-type.

<details>

<summary>Example Payload in Envelope Format</summary>

```json
{
  "data": [
    { // Individual messages, this can be one or multiple when batched
      "meta": {
        "message_type": <typeOfEvent>,
        "message_timestamp": <timestampOfEvent>,
        "app_id": <appID>
      },
      "data": { }
    }
  ]
}
```

</details>

#### Transports <a href="#transport-complete" id="transport-complete"></a>

Sent when a transport has been completed and processed by the SDK. This message includes the transport ID, transport mode, user ID, start and end times, app ID, detected waypoints, and more.

If you need Driving Insights for motorcycle and car transports, Transports messages can be enriched with Driving Events and Driving Scores.

<details>

<summary>Transport</summary>

```json
{
    "data": {
        "details": {
            "Waypoints": [
                {
                    "AccuracyInMeters": 3,
                    "SpeedLimitInMps": 13.88,
                    "Latitude": 51.21959,
                    "SpeedInMps": 1.53,
                    "Longitude": 4.45458,
                    "Timestamp": 1781534375002
                },
                {
                    "AccuracyInMeters": 6,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22034,
                    "SpeedInMps": 1.45,
                    "Longitude": 4.45601,
                    "Timestamp": 1781534543000
                },
                {
                    "AccuracyInMeters": 5,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22036,
                    "SpeedInMps": 1.33,
                    "Longitude": 4.45614,
                    "Timestamp": 1781534550000
                },
                {
                    "AccuracyInMeters": 6,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22029,
                    "SpeedInMps": 1.78,
                    "Longitude": 4.45628,
                    "Timestamp": 1781534554000
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22037,
                    "SpeedInMps": 1.4,
                    "Longitude": 4.45658,
                    "Timestamp": 1781534573000
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22043,
                    "SpeedInMps": 1.36,
                    "Longitude": 4.45664,
                    "Timestamp": 1781534577000
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22039,
                    "SpeedInMps": 1.07,
                    "Longitude": 4.45677,
                    "Timestamp": 1781534584000
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22039,
                    "SpeedInMps": 1.55,
                    "Longitude": 4.45705,
                    "Timestamp": 1781534597001
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22036,
                    "SpeedInMps": 1.46,
                    "Longitude": 4.45721,
                    "Timestamp": 1781534604001
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22034,
                    "SpeedInMps": 1.58,
                    "Longitude": 4.45758,
                    "Timestamp": 1781534621001
                },
                {
                    "AccuracyInMeters": 7,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22035,
                    "SpeedInMps": 1.4,
                    "Longitude": 4.45775,
                    "Timestamp": 1781534631001
                },
                {
                    "AccuracyInMeters": 9,
                    "SpeedLimitInMps": 25,
                    "Latitude": 51.22042,
                    "Longitude": 4.45862,
                    "Timestamp": 1781534641001
                }
            ],
            "DistanceInMeters": 404,
            "EndTime": "2026-06-15T16:44:01.001+02:00",
            "Version": "0.0.1",
            "UserId": "69a6fef0dfb3970800000433",
            "StartTime": "2026-06-15T16:39:27.234+02:00",
            "TransportMode": "WALKING",
            "Id": "D6A6BAA2-EF30-4B0F-8646-9166B9F449AD",
            "OccupantRole": "UNAVAILABLE",
            "Tags": []
        }
    },
    "meta": {
        "updated_attributes": [],
        "message_timestamp": "2026-06-15T14:47:20.715+00:00",
        "message_type": "on_device_transport_processed",
        "app_id": "68efa11490ae52080000bbbc"
    }
}
```

</details>

<details>

<summary>Transport with Driving Insights</summary>

```json
{
    "data": {
        "details": {
          "Waypoints": [
            {
              "AccuracyInMeters": 10,
              "SpeedLimitInMps": 25,
              "Latitude": 51.220903,
              "SpeedInMps": 6.79,
              "Longitude": 4.45717,
              "Timestamp": 1783319986463
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 19.44,
              "Latitude": 51.217074,
              "SpeedInMps": 12.08,
              "Longitude": 4.446429,
              "Timestamp": 1783320078467
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 19.44,
              "Latitude": 51.214839,
              "SpeedInMps": 0,
              "Longitude": 4.446745,
              "Timestamp": 1783320167460
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 19.44,
              "Latitude": 51.208944,
              "SpeedInMps": 0,
              "Longitude": 4.441044,
              "Timestamp": 1783320267464
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 27.77,
              "Latitude": 51.198577,
              "SpeedInMps": 21.09,
              "Longitude": 4.436211,
              "Timestamp": 1783320359509
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 27.77,
              "Latitude": 51.19215,
              "SpeedInMps": 28.75,
              "Longitude": 4.401856,
              "Timestamp": 1783320457470
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 13.88,
              "Latitude": 51.202311,
              "SpeedInMps": 4.06,
              "Longitude": 4.3891,
              "Timestamp": 1783320547464
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 13.88,
              "Latitude": 51.203092,
              "SpeedInMps": 0,
              "Longitude": 4.388736,
              "Timestamp": 1783320647470
            },
            {
              "AccuracyInMeters": 5,
              "SpeedLimitInMps": 25,
              "Latitude": 51.205674,
              "SpeedInMps": 7.15,
              "Longitude": 4.387438,
              "Timestamp": 1783320740564
            },
            {
              "AccuracyInMeters": 49,
              "Latitude": 51.204822,
              "Longitude": 4.381782,
              "Timestamp": 1783320844568
            }
          ],
          "DistanceInMeters": 9091,
          "EndTime": "2026-07-06T08:54:04.568+02:00",
          "Version": "0.0.1",
          "UserId": "69a6fef0dfb3970800000433",
          "StartTime": "2026-07-06T08:39:45.760+02:00",
          "TransportMode": "CAR",
          "Id": "6EF6F639-05B2-4AEA-9361-D97BD6139E1F",
          "DrivingInsights": {
            "SafetyScores": {
              "SmoothScore": 0.66,
              "CallWhileMovingScore": 1,
              "FocusScore": 0.76,
              "LegalScore": 0.62,
              "WrongWayDrivingScore": 1,
              "AttentionScore": 0.91,
              "OverallScore": 0.68
            },
            "Events": {
              "PhoneUsageEvents": [
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.207649,
                      "Longitude": 4.38441,
                      "Timestamp": 1783320773760
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 25,
                      "Latitude": 51.207629,
                      "SpeedInMps": 0.87,
                      "Longitude": 4.384785,
                      "Timestamp": 1783320777572
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 25,
                      "Latitude": 51.207954,
                      "SpeedInMps": 5.4,
                      "Longitude": 4.384657,
                      "Timestamp": 1783320787568
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 13.88,
                      "Latitude": 51.208034,
                      "SpeedInMps": 11.67,
                      "Longitude": 4.384155,
                      "Timestamp": 1783320797573
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.207368,
                      "Longitude": 4.383764,
                      "Timestamp": 1783320797760
                    }
                  ],
                  "EndTime": "2026-07-06T08:52:53.760+02:00",
                  "StartTime": "2026-07-06T08:52:53.760+02:00",
                  "CallState": "NO_CALL"
                }
              ],
              "CallWhileMovingEvents": [],
              "CallEvents": [],
              "HarshDrivingEvents": [
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 13.88,
                      "Latitude": 51.21763,
                      "SpeedInMps": 14.35,
                      "Longitude": 4.447286,
                      "Timestamp": 1783320067494
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.218018,
                      "Longitude": 4.446916,
                      "Timestamp": 1783320071212
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 19.44,
                      "Latitude": 51.216356,
                      "SpeedInMps": 12.07,
                      "Longitude": 4.447238,
                      "Timestamp": 1783320078467
                    }
                  ],
                  "Type": "TURN",
                  "EndTime": "2026-07-06T08:41:11.212+02:00",
                  "Confidence": 55,
                  "StartTime": "2026-07-06T08:41:11.212+02:00",
                  "Magnitude": 5.05
                },
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 33.33,
                      "Latitude": 51.204497,
                      "SpeedInMps": 18.4,
                      "Longitude": 4.439049,
                      "Timestamp": 1783320317466
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.204059,
                      "Longitude": 4.439386,
                      "Timestamp": 1783320322248
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.202926,
                      "SpeedInMps": 14.81,
                      "Longitude": 4.439016,
                      "Timestamp": 1783320327465
                    }
                  ],
                  "Type": "ACCELERATING",
                  "EndTime": "2026-07-06T08:45:22.248+02:00",
                  "Confidence": 64,
                  "StartTime": "2026-07-06T08:45:22.248+02:00",
                  "Magnitude": 2.48
                },
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.19672,
                      "SpeedInMps": 26.07,
                      "Longitude": 4.390682,
                      "Timestamp": 1783320487472
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.196757,
                      "Longitude": 4.389907,
                      "Timestamp": 1783320493402
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 25,
                      "Latitude": 51.197982,
                      "SpeedInMps": 23.8,
                      "Longitude": 4.38802,
                      "Timestamp": 1783320497469
                    }
                  ],
                  "Type": "ACCELERATING",
                  "EndTime": "2026-07-06T08:48:13.402+02:00",
                  "Confidence": 51,
                  "StartTime": "2026-07-06T08:48:13.402+02:00",
                  "Magnitude": 3.03
                },
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 13.88,
                      "Latitude": 51.203538,
                      "SpeedInMps": 3.13,
                      "Longitude": 4.389058,
                      "Timestamp": 1783320597466
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.20271,
                      "Longitude": 4.388958,
                      "Timestamp": 1783320604235
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 13.88,
                      "Latitude": 51.203947,
                      "SpeedInMps": 0,
                      "Longitude": 4.388861,
                      "Timestamp": 1783320607463
                    }
                  ],
                  "Type": "ACCELERATING",
                  "EndTime": "2026-07-06T08:50:04.235+02:00",
                  "Confidence": 55,
                  "StartTime": "2026-07-06T08:50:04.235+02:00",
                  "Magnitude": 2.77
                }
              ],
              "SpeedingEvents": [
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.192618,
                      "Longitude": 4.427015,
                      "Timestamp": 1783320395541
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.192039,
                      "SpeedInMps": 29.21,
                      "Longitude": 4.4268,
                      "Timestamp": 1783320397541
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.190685,
                      "SpeedInMps": 32.34,
                      "Longitude": 4.422175,
                      "Timestamp": 1783320410466
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.190148,
                      "SpeedInMps": 31.47,
                      "Longitude": 4.417648,
                      "Timestamp": 1783320417471
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.190253,
                      "SpeedInMps": 31.88,
                      "Longitude": 4.414549,
                      "Timestamp": 1783320427465
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.190594,
                      "SpeedInMps": 30.54,
                      "Longitude": 4.408926,
                      "Timestamp": 1783320437474
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 27.77,
                      "Latitude": 51.19112,
                      "SpeedInMps": 31.02,
                      "Longitude": 4.404751,
                      "Timestamp": 1783320448468
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.192273,
                      "Longitude": 4.40342,
                      "Timestamp": 1783320450468
                    }
                  ],
                  "EndTime": "2026-07-06T08:46:37.541+02:00",
                  "StartTime": "2026-07-06T08:46:37.541+02:00"
                },
                {
                  "Waypoints": [
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.203333,
                      "Longitude": 4.387833,
                      "Timestamp": 1783320665470
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 5.55,
                      "Latitude": 51.204262,
                      "SpeedInMps": 7.61,
                      "Longitude": 4.387792,
                      "Timestamp": 1783320667470
                    },
                    {
                      "AccuracyInMeters": 5,
                      "SpeedLimitInMps": 5.55,
                      "Latitude": 51.204441,
                      "SpeedInMps": 7.19,
                      "Longitude": 4.387872,
                      "Timestamp": 1783320678468
                    },
                    {
                      "AccuracyInMeters": 0,
                      "Latitude": 51.204076,
                      "Longitude": 4.388288,
                      "Timestamp": 1783320680468
                    }
                  ],
                  "EndTime": "2026-07-06T08:51:07.470+02:00",
                  "StartTime": "2026-07-06T08:51:07.470+02:00"
                }
              ],
              "WrongWayDrivingEvents": []
            }
          },
          "OccupantRole": "DRIVER",
          "Tags": []
        }
      },
      "meta": {
        "updated_attributes": [],
        "message_timestamp": "2026-07-06T06:57:41.529+00:00",
        "message_type": "on_device_transport_processed",
        "app_id": "68efa11490ae52080000bbbc"
      }
    }
```

</details>

#### Crash Insights

Sent when a crash has been detected and processed by the SDK.

<details>

<summary>Crash Insights</summary>

{% code expandable="true" %}

```json
{
    "data": {
        "details": {
            "SpeedAtImpact": 6.68,
            "TimestampUtcMillisSeconds": 1783622510008,
            "WaypointLatitude": 51.21881,
            "TimestampOffsetMinutes": 120,
            "MaxMagnitude": 136.49,
            "AppId": "68efa11490ae52080000bbbc",
            "CrashSeverity": "MEDIUM",
            "Version": "0.0.1",
            "DeltaV": 6.14,
            "UserId": "69a6fef0dfb3970800000433",
            "WaypointSpeed": 6.68,
            "WaypointLongitude": 4.46039,
            "ConfidencePercentage": 99
        }
    },
    "meta": {
        "updated_attributes": [],
        "message_timestamp": "2026-07-09T18:43:48.686+00:00",
        "message_type": "on_device_crash_processed",
        "app_id": "68efa11490ae52080000bbbc"
    }
}
```

{% endcode %}

</details>

#### Smart Geofences

The Smart Geofence message is sent whenever a user enters or exits a preconfigured geofence. One message can contain multiple geofences, if the triggering location touches multiple overlapping geofences.

<details>

<summary>Smart Geofences Entry</summary>

{% code expandable="true" %}

```json
{
    "data": {
        "details": {
            "Version": "0.0.1",
            "Geofences": [
                {
                    "GeofenceGroupId": "4b167019-1f0a-458c-b663-53f757bfe442",
                    "GeofenceId": "9f90a779-8d5e-4fda-875d-429b4a989313"
                }
            ],
            "UserId": "69a6fef0dfb3970800000433",
            "EventTime": "2026-07-08T16:32:40.602+02:00",
            "GeofenceEventType": "ENTRY"
        }
    },
    "meta": {
        "updated_attributes": [],
        "message_timestamp": "2026-07-08T14:32:40.889+00:00",
        "message_type": "on_device_geofence_processed",
        "app_id": "68efa11490ae52080000bbbc"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Smart Geofences Exit</summary>

{% code expandable="true" %}

```json
{
    "data": {
        "details": {
            "Version": "0.0.1",
            "Geofences": [
                {
                    "GeofenceGroupId": "4b167019-1f0a-458c-b663-53f757bfe442",
                    "GeofenceId": "9f90a779-8d5e-4fda-875d-429b4a989313"
                }
            ],
            "UserId": "69a6fef0dfb3970800000433",
            "EventTime": "2026-07-08T16:32:40.602+02:00",
            "GeofenceEventType": "EXIT"
        }
    },
    "meta": {
        "updated_attributes": [],
        "message_timestamp": "2026-07-08T14:32:40.889+00:00",
        "message_type": "on_device_geofence_processed",
        "app_id": "68efa11490ae52080000bbbc"
    }
}
```

{% endcode %}

</details>

#### Engagement

These messages are triggered by engagement actions, such as challenges, badges, and similar features. They can be used to deliver real-time push notifications to users, or as general updates about user activity.

Although all Firehose messages have a specific message type, all Engagement messages share the same value `"engagement"`  in the `message_type` field within the message's `meta` object. Each Engagement message also has a specific **sub-type**, found in the `event_type` field within the message's `data` object.

These messages can only be sent if your application is enabled on the [Engagement Platform](/getting-started/features-catalog/engagement)

<details>

<summary>Challenge</summary>

Trigges when a user completes or fails a challenge.

Possible values for challenge state: **COMPLETED** and **FAILED.**

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "challenge_id":"challenge.driving.focused.easy-001",
            "challenge_state":"COMPLETED",
            "progress":"100.0",
            "evaluation_id":"695bb322-c95a-4a9b-963e-bb28c58a58f2",
            "notification_id":"notification-challenge-complete-00",
            "title":"Challenge completed",
            "message":"Challenge completed! Congrats.",
            "url":"",
            "challenge_description":"The next trip with your car will be without using your phone. Deal?",
            "challenge_category":"driving",
            "challenge_subcategory":"focused",
            "challenge_difficulty":"easy",
            "challenge_image_url":""
        },
        "event_type":"CHALLENGE"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Badge</summary>

Triggered when a user achieves a badge.

Possible values for badge state: **COMPLETED.**

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "badge_id":"badge.no-speeding.level-1",
            "badge_state":"COMPLETED",
            "progress":"100.0",
            "evaluation_id":"70cd50c6-5eb2-494a-a0f6-463c1aba45f2",
            "notification_id":"notification-badge-complete-00",
            "title":"Badge earned!",
            "message":"Awesome! You earned a badge: check it out in the app.",
            "url":"",
            "badge_name":"Not making tracks",
            "badge_description":"Comply with the speed limit for 4.45 km",
            "badge_category":"No Speeding",
            "badge_reward_text":"4.45 km is exactly the length of the Phillip Island Formula 1 circuit.",
            "badge_image_url":""
        },
        "event_type":"BADGE"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Trip</summary>

Triggered when a new trip is recorded for a user.

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "evaluation_id":"70cd50c6-5eb2-494a-a0f6-463c1aba45f2",
            "notification_id":"notification-trip-complete-00",
            "title":"Trip completed!",
            "message":"We recorded a new trip for you, CHeck it out in the app!",
            "url":"",
            "event_id": "",
            "total_distance_m": "10000",
            "duration_minutes": "60"
        },
        "event_type":"TRIP"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Streak</summary>

Triggered when a user breaks a streak.

Possible values for streaks state: **BREAK.**

Possible values for streak type: STRICT and SELF\_COMPETING

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "evaluation_id":"70cd50c6-5eb2-494a-a0f6-463c1aba45f2",
            "notification_id":"notification-streak-broken-00",
            "title":"Sreak broken!",
            "message":"Oh no! Your last trip broke your streak of perfect driving!",
            "url":"",
            "streak_type": "STRICT",
            "score_type": "bae-mffs-score",
            "streak_state": "BREAK",
            "streak_count": "21"
        },
        "event_type":"STREAK"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Report</summary>

Used as trigger for delivering reports to users via email or other channels.

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "language":"en",
            "evaluation_id":"13674fa0-73d8-4513-877f-bc08810d9e9a",
            "notification_id":"notification-email-sign-up-00",
            "title":"",
            "message":"",
            "url":"",
        },
        "event_type":"REPORT"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Message</summary>

This is a generic category for events that do not fit any of the categories above. For example, an event with type MESSAGE can be triggered for reminders.

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "language":"en",
            "evaluation_id":"13674fa0-73d8-4513-877f-bc08810d9e9a",
            "notification_id":"notification-email-sign-up-00",
            "title":"",
            "message":"",
            "url":"",
        },
        "event_type":"REPORT"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Scheduled</summary>

Triggered for messages that are scheduled to be delivered at a specific time and date.

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "evaluation_id":"4bf4fefa-90dc-4aa5-ad98-1372e9b0306a",
            "notification_id":"2c260af0-22d4-4731-a9cc-4f3d02f23861",
            "title":"You are the best driver!",
            "message":"Keep it going!",
            "url":""
        },
        "event_type":"SCHEDULED"
    }
}
```

{% endcode %}

</details>

<details>

<summary>Crash Event</summary>

Triggered whenever a crash event is detected by our sdk.

{% code expandable="true" %}

```json
{
    "meta":{
        "app_id":"00000000000000000000000a",
        "message_timestamp":"2025-11-19T11:10:08.382+00:00",
        "message_type":"engagement",
        "updated_attributes":[]
    },
    "data":{
        "user_id":"5984483fa3b15f0700000288",
        "context":{
            "severity":"UNAVAILABLE",
            "time_iso":"2021-09-13T21:17:52.000Z",
            "detector_mode":"UNKNOWN",
            "speed_at_impact":"50",
            "confidence":"0.95",
            "location":"[37.7749,-122.4194]",
            "magnitude":"4.5",
            "time":"1763557910000",
            "preceding_locations":"[[37.775,-122.4195],[37.7752,-122.4196]]",
            "delta_v":"15"
        },
        "event_type":"CRASH_EVENT"
    }
}
```

{% endcode %}

</details>


# Insights


# Driving Insights

{% hint style="info" %}
To learn about the Driving Insights feature, check out [this page](/getting-started/features-catalog/driving-insights).
{% endhint %}

## Prerequisites

The Driving Insights feature relies on additional library dependencies alongside the core SDK dependency. To start using this feature, you must first add these dependencies to you project.

<details>

<summary>iOS</summary>

This feature is included in the main SDK framework. No additional dependencies are needed.

</details>

<details>

<summary>Android</summary>

Open your app <mark style="color:$success;">**`build.gradle`**</mark> file and add the driving insights dependency.

{% code title="app/build.gradle" %}

```kotlin
dependencies {
    implementation(platform('com.sentiance:sdk-bom:<sentiance-version>'))
    implementation('com.sentiance:sdk-driving-insights')
}
```

{% endcode %}

</details>

<details>

<summary>React Native</summary>

This feature is included after installing the following modules:

* [@sentiance-react-native/core](https://www.npmjs.com/package/@sentiance-react-native/core)
* [@sentiance-react-native/event-timeline](https://www.npmjs.com/package/@sentiance-react-native/event-timeline)
* [@sentiance-react-native/driving-insights](https://www.npmjs.com/package/@sentiance-react-native/driving-insights)

</details>

<details>

<summary>Flutter</summary>

This feature can be added by installing the following packages:

* [sentiance\_core](https://pub.dev/packages/sentiance_core)
* [sentiance\_event\_timeline](https://pub.dev/packages/sentiance_event_timeline)
* [sentiance\_driving\_insights](https://pub.dev/packages/sentiance_driving_insights)

</details>

## Utilize the Driving Insights APIs

In this section, you can find examples of how to query the Sentiance SDK for driving insights, safe driving scores, detailed driving events. You will also find how to register to be notified of driving insights, once it becomes available.

### Query for Driving Insights

{% hint style="info" %}
Driving Insights become available a few minutes after a transport ends. Once the user stops driving, the SDK takes a few minutes to make sure that the drive is over, and then proceeds to prepare the insights.

To be notified of when the insights are ready, you can [subscribe for Driving Insights updates](#subscribe-for-driving-insights-updates).
{% endhint %}

To retrieve driving insights, use the <mark style="color:$success;">**`getDrivingInsights`**</mark> method.&#x20;

{% tabs %}
{% tab title="iOS" %}

```swift
let sentiance = Sentiance.shared
if let drivingInsights = sentiance.getDrivingInsights(forTransportId: transportId) {
    let event = drivingInsights.transportEvent
    let safetyScores = drivingInsights.safetyScores
    
    print("Focus score: \(safetyScores.focusScore ?? -1)")
    print("Legal score: \(safetyScores.legalScore ?? -1)")
    print("Smooth score: \(safetyScores.smoothScore ?? -1)")
    print("Call-while-moving score: \(safetyScores.callWhileMovingScore ?? -1)")
    print("Overall score: \(safetyScores.overallScore ?? -1)")

    print("Event ID: \(event.eventId)")
    print("Started on: \(event.startDate)")
    print("Ended on: \(String(describing: event.endDate))")
    print("Mode: \(event.transportMode)")

    if let distanceInMeters = event.distanceInMeters {
        print("Distance: \(distanceInMeters)")
    }

    print("Waypoints: \(event.waypoints)")
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
val drivingInsightsApi = DrivingInsightsApi.getInstance(context)
val drivingInsights = drivingInsightsApi.getDrivingInsights(transportId)
if (drivingInsights != null) {
    val event = drivingInsights.transportEvent
    val safetyScores = drivingInsights.safetyScores

    print("Focus score: ${safetyScores.focusScore}")
    print("Legal score: ${safetyScores.legalScore}")
    print("Smooth score: ${safetyScores.smoothScore}")
    print("Call-while-moving score: ${safetyScores.callWhileMovingScore}")
    print("Overall score: ${safetyScores.overallScore}")

    print("Event ID: ${event.id}")
    print("Started on: ${event.startTime}")
    print("Ended on: ${event.endTime}")
    print("Mode: ${event.transportMode}")

    if (event.distanceInMeters != null) {
        print("Distance: ${event.distanceInMeters}")
    }

    print("Waypoints: ${event.waypoints}")
}
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import SentianceDrivingInsights from "@sentiance-react-native/driving-insights";

const drivingInsights = await SentianceDrivingInsights.getDrivingInsights(transportId);

if (drivingInsights !== null) {
    const event = drivingInsights.transportEvent; 
    const safetyScores = drivingInsights.safetyScores;
    
    if (safetyScores.focusScore) {
        console.log(`Focus score: ${safetyScores.focusScore}`);
    }
    if (safetyScores.legalScore) {
        console.log(`Legal score: ${safetyScores.legalScore}`);
    }
    if (safetyScores.smoothScore) {
        console.log(`Smooth score: ${safetyScores.smoothScore}`);
    }
    if (safetyScores.callWhileMovingScore) {
        console.log(`Call-while-moving score: ${safetyScores.callWhileMovingScore}`);
    }
    if (safetyScores.overallScore) {
        console.log(`Overall score: ${safetyScores.overallScore}`);
    }
    
    console.log(`Event ID: ${event.id}`);
    console.log(`Started on: ${event.startTime}`);
    console.log(`Ended on: ${event.endTime}`);
    console.log(`Mode: ${transport.transportMode}`);
    
    if (event.distance) {
        console.log(`Distance: ${event.distance}`);
    }
    console.log(`Waypoints: ${JSON.stringify(event.waypoints)}`);
}
```

{% endtab %}

{% tab title="Flutter" %}

<pre class="language-dart"><code class="lang-dart">import 'package:sentiance_driving_insights/sentiance_driving_insights.dart';

final sentianceDrivingInsights = SentianceDrivingInsights();

<strong>void getDrivingInsightsForTransport() async {
</strong>  String transportId = "your_transport_id";
  final drivingInsights = await sentianceDrivingInsights.getDrivingInsights(transportId);
  if (drivingInsights != null) {
    final transportEvent = drivingInsights.transportEvent;
    final safetyScores = drivingInsights.safetyScores;
    
    print('Focus score: ${safetyScores.focusScore}');
    print('Legal score: ${safetyScores.legalScore}');
    print('Smooth score: ${safetyScores.smoothScore}');
    print('Call-while-moving score: ${safetyScores.callWhileMovingScore}');
    print('Overall score: ${safetyScores.overallScore}');

    print('Event ID: ${transportEvent.id}');
    print('Started on: ${transportEvent.startTimeMs}');
    print('Ended on: ${transportEvent.endTimeMs}');
    print('Last updated on: ${transportEvent.lastUpdateTimeMs}');
    print('Mode: ${transportEvent.transportMode}');
    print('Duration in seconds: ${transportEvent.durationInSeconds}');
    print('Distance: ${transportEvent.distance}');
    print('Waypoints: [${transportEvent.waypoints.map((wp) => wp.toString()).join(", ")}]');
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

### Query For Driving Events

The Sentiance SDK offers more granular driving insights. You can retrieve detailed events, such as speeding and harsh driving, as shown below.

{% tabs %}
{% tab title="iOS" %}
{% code lineNumbers="true" %}

```swift
// Harsh driving events, used for computing the smooth driving score
Sentiance.shared.getHarshDrivingEvents(forTransportId: transportId).forEach { harshDrivingEvent in
    print("Start date: \(harshDrivingEvent.startDate)")
    print("End date: \(harshDrivingEvent.endDate)")
    print("Magnitude: \(harshDrivingEvent.magnitude)")
}

// Phone usage events, used for computing the focused driving score
Sentiance.shared.getPhoneUsageEvents(forTransportId: transportId).forEach { phoneUsageEvent in
    print("Start date: \(phoneUsageEvent.startDate)")
    print("End date: \(phoneUsageEvent.endDate)")
}

// Call-while-moving events, used for computing the call-while-moving score
Sentiance.shared.getCallsWhileMovingEvents(forTransportId: transportId).forEach { callWhileMovingEvent in
    print("Start date: \(callWhileMovingEvent.startDate)")
    print("End date: \(callWhileMovingEvent.endDate)")
    if let minSpeed = callWhileMovingEvent.minTraveledSpeedInMps?.floatValue {
        print("Min traveled speed: \(minSpeed)")
    }
    if let maxSpeed = callWhileMovingEvent.maxTraveledSpeedInMps?.floatValue {
        print("Max traveled speed: \(maxSpeed)")
    }
}

// Speeding events, used for computing the legal driving score
Sentiance.shared.getSpeedingEvents(forTransportId: transportId).forEach { speedingEvent in
    print("Start Date: \(speedingEvent.startDate)")
    print("End Date: \(speedingEvent.endDate)")
    print("Waypoints: \(speedingEvent.waypoints)")
}
```

{% endcode %}
{% endtab %}

{% tab title="Android" %}

<pre class="language-kotlin"><code class="lang-kotlin"><strong>// Harsh driving events, used for computing the smooth driving score
</strong>DrivingInsightsApi.getInstance(context).getHarshDrivingEvents(transportId)
  .forEach { harshDrivingEvent -> 
    print("Start time: ${harshDrivingEvent.startTime}")
    print("End time: ${harshDrivingEvent.endTime}")
    print("Magnitude: ${harshDrivingEvent.magnitude}")
}

// Phone usage events, used for computing the focused driving score
DrivingInsightsApi.getInstance(context).getPhoneUsageEvents(transportId)
  .forEach { phoneUsageEvent ->
    print("Start time: ${phoneUsageEvent.startTime}")
    print("End time: ${phoneUsageEvent.endTime}")
}

// Call-while-moving events, used for computing the call-while-moving score
DrivingInsightsApi.getInstance(context).getCallWhileMovingEvents(transportId)
  .forEach { callWhileMovingEvent ->
    print("Start time: ${callWhileMovingEvent.startTime}")
    print("End time: ${callWhileMovingEvent.endTime}")
    print("Min traveled speed: ${callWhileMovingEvent.minTraveledSpeedInMps}")        
    print("Max traveled speed: ${callWhileMovingEvent.maxTraveledSpeedInMps}")        
}

// Speeding events, used for computing the legal driving score
DrivingInsightsApi.getInstance(context).getSpeedingEvents(transportId)
  .forEach { speedingEvent ->
    print("Start time: ${speedingEvent.startTime}")
    print("End time: ${speedingEvent.endTime}")
    print("Waypoints: ${speedingEvent.waypoints}")
}
</code></pre>

{% endtab %}

{% tab title="React Native" %}

```javascript
import SentianceDrivingInsights from "@sentiance-react-native/driving-insights";

// Harsh driving events, used for computing the smooth driving score
const harshDrivingEvents = await SentianceDrivingInsights.getHarshDrivingEvents(transportId);
for (const event of harshDrivingEvents) {
    console.log(`Start time: ${event.startTime}`);
    console.log(`End time: ${event.endTime}`);
    console.log(`Magnitude: ${event.magnitude}`);
}

// Phone usage events, used for computing the focused driving score
const phoneUsageEvents = await SentianceDrivingInsights.getPhoneUsageEvents(transportId);
for (const event of phoneUsageEvents) {
    console.log(`Start time: ${event.startTime}`);
    console.log(`End time: ${event.endTime}`);
}

// Call-while-moving events, used for computing the call-while-moving score
const callWhileMovingEvents = await SentianceDrivingInsights.getCallWhileMovingEvents(transportId);
for (const event of callWhileMovingEvents) {
    console.log(`Start time: ${event.startTime}`);
    console.log(`End time: ${event.endTime}`);
    console.log(`Min traveled speed: ${event.minTravelledSpeedInMps}`);        
    console.log(`Max traveled speed: ${event.maxTravelledSpeedInMps}`);
}

// Speeding events, used for computing the legal driving score
const speedingEvents = await SentianceDrivingInsights.getSpeedingEvents(transportId);
for (const event of speedingEvents) {
    console.log(`Start time: ${event.startTime}`);
    console.log(`End time: ${event.endTime}`);
    console.log(`Waypoints: ${JSON.stringify(event.waypoints)}`);
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_driving_insights/sentiance_driving_insights.dart';

final sentianceDrivingInsights = SentianceDrivingInsights();

void fetchDrivingEvents(String transportId) async {
  // Harsh driving events, used for computing the smooth driving score
  final harshDrivingEvents = await sentianceDrivingInsights.getHarshDrivingEvents(transportId);
  for (final event in harshDrivingEvents) {
    print('Start time: ${event.startTimeMs}');
    print('End time: ${event.endTimeMs}');
    print('Magnitude: ${event.magnitude}');
  }

  // Phone usage events, used for computing the focused driving score
  final phoneUsageEvents = await sentianceDrivingInsights.getPhoneUsageEvents(transportId);
  for (final event in phoneUsageEvents) {
    print('Start time: ${event.startTimeMs}');
    print('End time: ${event.endTimeMs}');
  }

  // Call-while-moving events, used for computing the call-while-moving score
  final callWhileMovingEvents = await sentianceDrivingInsights.getCallWhileMovingEvents(transportId);
  for (final event in callWhileMovingEvents) {
    print('Start time: ${event.startTimeMs}');
    print('End time: ${event.endTimeMs}');
    print('Min traveled speed: ${event.minTraveledSpeedInMps}');
    print('Max traveled speed: ${event.maxTraveledSpeedInMps}');
  }

  // Speeding events, used for computing the legal driving score
  final speedingEvents = await sentianceDrivingInsights.getSpeedingEvents(transportId);
  for (final event in speedingEvents) {
    print('Start time: ${event.startTimeMs}');
    print('End time: ${event.endTimeMs}');
    print('Waypoints: ${event.waypoints}');
  }
}
```

{% endtab %}
{% endtabs %}

### Subscribe for Driving Insights Updates

{% hint style="info" %}
Checkout [Real-time Listeners](/sdk/appendix/real-time-listeners) to read more about implementing the SDK's Listeners
{% endhint %}

{% tabs %}
{% tab title="iOS" %}

```swift
class MyDelegate: DrivingInsightsReadyDelegate {
    func onDrivingInsightsReady(insights: SENTSDK.DrivingInsights) {
        let transportEvent = insights.transportEvent
        let safetyScores = insights.safetyScores
    }
}
        
Sentiance.shared.drivingInsightsReadyDelegate = MyDelegate()
```

{% endtab %}

{% tab title="Android" %}

```kotlin
val drivingInsightsApi = DrivingInsightsApi.getInstance(context)

drivingInsightsApi.setDrivingInsightsReadyListener { insights ->
    val transportEvent = insights.transportEvent
    val safetyScores = insights.safetyScores
}
```

{% endtab %}

{% tab title="React Native" %}
To get driving insights updates even when your app is in the background, place the following code inside your app's entrypoint **index.js** file. If you're only interested in these updates when your app is foregrounded, place this code inside the appropriate UI code instead.

```javascript
import SentianceDrivingInsights from "@sentiance-react-native/driving-insights";
import SentianceCore from "@sentiance-react-native/core";

await SentianceCore.ensureInitialized(); // Ensure SDK is initialized first

// If you're subscribing to updates only in the foreground, make sure
// to call subscription.remove() inside your component's componentWillUnmount() function
const subscription = await SentianceDrivingInsights.addDrivingInsightsReadyListener(drivingInsights => {
    // Handle the insights here (see the Query for Driving Insights
    // example above).
});
```

{% endtab %}

{% tab title="Flutter" %}
Create a **background.dart** file under your project's **lib** folder with the following code:

{% code title="background.dart" %}

```dart
import 'package:sentiance_driving_insights/sentiance_driving_insights.dart';
import 'package:sentiance_core/sentiance_core.dart';

@pragma('vm:entry-point')
void registerDrivingInsightsListener() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SentianceCore.ensureInitialized();
  
  SentianceDrivingInsights.registerDrivingInsightsListener((drivingInsights) {
    // Handle the insights here (see the Query for Driving Insights
    // example above).
  });
}
```

{% endcode %}

Add the following code, depending on your target platform.

For **iOS**, add the following to your app delegate class:

{% code title="AppDelegate.swift" %}

```swift
import Flutter
import sentiance_driving_insights

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
    
        // Other code
        
        // Make sure the SDK is initialized before calling this
        SentianceDrivingInsightsPlugin.initializeListener(
            withEntryPoint: "registerDrivingInsightsListener",
            libraryURI: "package:your_app_package_name/background.dart"
        )
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

{% endcode %}

For **Android**, add this code to your custom application class:

{% code title="MainApplication.kt" %}

```kotlin
import android.app.Application
import com.sentiance.driving_insights_plugin.DrivingInsightsPlugin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Other code
        
        val dartLibrary = "package:your_app_package_name/background.dart"
        // Make sure the SDK is initialized before calling this
        DrivingInsightsPlugin.initializeListener(this, dartLibrary, "registerDrivingInsightsListener")
    }
}
```

{% endcode %}

{% hint style="info" %}
If you're calling other 3rd party plugin APIs inside your `registerEventTimelineListener` Dart function, then you need to register these plugins with the Sentiance SDK. See [this](/sdk/appendix/flutter/declaring-3rd-party-plugins) for more details.
{% endhint %}
{% endtab %}
{% endtabs %}


# Mobility Insights

{% hint style="info" %}
To learn about the Mobility Insights feature, check out [this page](/getting-started/features-catalog/mobility-insights).
{% endhint %}

## Prerequisites

The Mobility Insights feature relies on additional library dependencies alongside the core SDK dependency. To start using this feature, you must first add these dependencies to you project.

<details>

<summary>iOS</summary>

This feature is included in the main SDK framework. No additional dependencies are needed.

</details>

<details>

<summary>Android</summary>

Open your app <mark style="color:$success;">**`build.gradle`**</mark> file and add the event timeline dependency.

{% code title="app/build.gradle" %}

```kotlin
dependencies {
    implementation(platform('com.sentiance:sdk-bom:<sentiance-version>'))
    implementation('com.sentiance:sdk-event-timeline')
}
```

{% endcode %}

</details>

<details>

<summary>React Native</summary>

This feature is included after installing the following modules:

* [@sentiance-react-native/core](https://www.npmjs.com/package/@sentiance-react-native/core)
* [@sentiance-react-native/event-timeline](https://www.npmjs.com/package/@sentiance-react-native/event-timeline)

</details>

<details>

<summary>Flutter</summary>

This feature can be added by installing the following packages:

* [sentiance\_core](https://pub.dev/packages/sentiance_core)
* [sentiance\_event\_timeline](https://pub.dev/packages/sentiance_event_timeline)

</details>

## Utilize the Event Timeline APIs

In this section, you can find examples of how to query the Sentiance SDK for historic timeline events (e.g. transports and stationaries), and how to register to receive timeline updates in your app, as new events are detected or existing ones are updated.

### Query for Historic Events

You can query for past events in the user's timeline by utilizing the <mark style="color:$success;">**`getTimelineEvents`**</mark> method.

{% hint style="info" %}
The Sentiance SDK has the capacity to remember 9 weeks of events. It is recommended to store a copy of these events on your end, that way, you can store a much longer history. To properly collect events and store them in your own event store, check out [the next section](#subscribe-for-and-retrieve-event-timeline-updates).
{% endhint %}

{% tabs %}
{% tab title="iOS" %}

```swift
let from = Date.distantPast
let to = Date.distantFuture

Sentiance.shared.getTimelineEvents(from: from, to: to).forEach { event in
    print("Event ID: \(event.eventId)")
    print("Started on: \(event.startDate)")
    print("Ended on: \(event.endDate)")
    
    if event.type == .inTransport {
        let transport =  event as! SENTTransportEvent
        
        print("Type: transport")
        print("Mode: \(transport.transportMode)")
        
        if let distance = transport.distanceInMeters {
            print("Distance: \(distance)")
        }
        
        print("Waypoints: \(transport.waypoints)")
        
    }
    else if event.type == .stationary {
        let stationary =  event as! SENTStationaryEvent
        
        print("Type: stationary")
        print("Location: \(stationary.location)")
        print("Venue: \(stationary.venue)")
    }
    else if event.type == .offTheGrid {
        print("Type: off-the-grid")
    }
    else {
        print("Type: unknown")
    }
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
val from = Date(0)
val to = Date(Long.MAX_VALUE)

EventTimelineApi.getInstance(context).getTimelineEvents(from, to).forEach { event ->
    print("Event ID: ${event.id}")
    print("Started on: ${event.startTime}")
    print("Ended on: ${event.endTime}")

    when (event.eventType) {
        EventType.IN_TRANSPORT -> {
            val transport = event as TransportEvent!

            print("Type: transport")
            print("Mode: ${transport.transportMode}")

            if (transport.distanceInMeters != null) {
                print("Distance: ${transport.distanceInMeters}")
            }

            print("Waypoints: ${transport.waypoints}")
        }
        EventType.STATIONARY -> {
            val stationary = event as StationaryEvent!

            print("Type: stationary")
            print("Location: ${stationary.location}")
            print("Venue: ${stationary.venue}")
        }
        EventType.OFF_THE_GRID -> {
            print("Type: off-the-grid")
        }
        else -> {
            print("Type: unknown")
        }
    }
}
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import EventTimelineApi from '@sentiance-react-native/event-timeline';

const from = new Date(0); // 01/01/1970
const to = new Date(2 ** 31 * 1000);

const events = await EventTimelineApi.getTimelineEvents(from.getTime(), to.getTime());
for (const event of events) {
    console.log(`Event ID: ${event.id}`);
    console.log(`Started on: ${event.startTime}`);
    console.log(`Ended on: ${event.endTime}`);

    switch (event.type) {
        case 'IN_TRANSPORT':
            const transport = event;
            console.log('Type: transport');
            console.log(`Mode: ${transport.transportMode}`);

            if (transport.distance) {
                console.log(`Distance: ${transport.distance}`);
            }
            console.log(`Waypoints: ${JSON.stringify(transport.waypoints)}`);
            break;
        case 'STATIONARY':
            const stationary = event;
            console.log('Type: stationary');
            console.log(`Location: ${JSON.stringify(stationary.location)}`);
            console.log(`Venue: ${JSON.stringify(stationary.venue)}`);
            break;
        case 'OFF_THE_GRID':
            console.log('Type: off-the-grid');
            break;
        default:
            console.log('Type: unknown');
    }
}
```

{% endtab %}

{% tab title="Flutter" %}
{% code title="app.dart" %}

```dart
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';

final eventTimeline = SentianceEventTimeline();

/// A function that processes all events that occurred since the dawn of times.
void processTimelineEvents() async {
  const startEpochTimeMs = 0;
  final endEpochTimeMs = DateTime.now().millisecondsSinceEpoch;
  final timelineEvents = await eventTimeline.getTimelineEvents(startEpochTimeMs, endEpochTimeMs);

  for (final event in timelineEvents) {
    if (event is StationaryEvent) {
      print("Type: stationary");
      print("Location: ${event.location}");
      print("Venue: ${event.venue}");
    } else if (event is TransportEvent) {
      print("Type: transport");
      print("Mode: ${event.transportMode}");
      print("Distance: ${event.distance}");
      for (final waypoint in event.waypoints) {
        print(waypoint.toString());
      }
    } else if (event is OffTheGridEvent) {
      print("Type: off-the-grid");
    } else if (event is UnknownEvent) {
      print("Type: unknown");
    }
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Subscribe for and Retrieve Event Timeline Updates

{% hint style="info" %}
Checkout [Real-time Listeners](/sdk/appendix/real-time-listeners) to read more about implementing the SDK's Listeners
{% endhint %}

To receive event updates, such as when a transport starts or ends, you can subscribe for event timeline updates.&#x20;

The example below demonstrates a robust approach to collecting timeline events by combining an event update subscription with a query for updates since the last processed event. This ensures the logic remains resilient to interruptions or unexpected app terminations.

{% tabs %}
{% tab title="iOS" %}
{% code lineNumbers="true" %}

```swift
public class EventTimelineUpdateReceiver: EventTimelineDelegate {
    private let LAST_UPDATE_KEY = "SentianceTimelineEventLastUpdateTime"
    private let timelineStore: TimelineStore
    private let queue: OperationQueue
    
    init() {
        timelineStore = TimelineStore() // your own store implementation
        queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
    }    
        
    public func listenForUpdates() {
        // Saving a local copy of the most recent event update time, before 
        // setting the delegate below. This way, we make sure that this 
        // value does not change by the delegate method by the time we query
        // for updates.
        let mostRecentEventUpdateTime = self.mostRecentEventUpdateTime
                
        // Set the delegate to start listening for timeline updates.
        Sentiance.shared.eventTimelineDelegate = self
        
        // Finally, let's make sure we haven't missed any events since the 
        // last time our app ran (e.g. due to unexpected app termination).
        let afterDate = Date(timeIntervalSince1970: mostRecentEventUpdateTime)
        Sentiance.shared.getTimelineUpdates(after: afterDate)
          .forEach { [weak self] timelineEvent in
            self?.updateOrInsert(event: timelineEvent, 
                                 updateMostRecentEventUpdateTime: false)
        }
    }
    
    public func onEventTimelineUpdate(event: SENTTimelineEvent) {
        // Note: this delegate method invocation happens on the 
        // main/UI thread.
        updateOrInsert(event: event, updateMostRecentEventUpdateTime: true)
    }
    
    private func updateOrInsert(event: SENTTimelineEvent,
                                updateMostRecentEventUpdateTime: Bool) {
        // The non-concurrent queue makes sure we don't process incoming 
        // updates via the delegate method and the result of calling
        // getTimelineUpdates() at the same time. Plus, it makes sure
        // we do the processing off of the main thread.
        queue.addOperation { [weak self] in
            guard let self = self else { return }
            
            if let existingEvent = timelineStore.get(id: event.eventId) {
                // Make sure the update that we're about to apply isn't for 
                // an older version of the event than the one we already have.
                if (existingEvent.lastUpdateDate.timeIntervalSince1970 < 
                     event.lastUpdateDate.timeIntervalSince1970) {
                    timelineStore.update(event: event)
                }
            } else {
                timelineStore.insert(event: event)
            }
            
            if updateMostRecentEventUpdateTime {
                // Note: events arriving via the onEventTimelineUpdate 
                // delegate method have a monotonically increasing 
                // lastUpdateDate.
                self.mostRecentEventUpdateTime 
                   = event.lastUpdateDate.timeIntervalSince1970
            }
        }
    }
    
    // We keep track of the most recent lastUpdateDate of the received
    // events, so that on the next run, we can use it to process missed
    // events (see listenForUpdates()).
    private var mostRecentEventUpdateTime : TimeInterval {
        get {
            // We use UserDefaults. But if your app uses the iOS
            // DataProtection capability, this won't work for you
            // when the device is locked. Use something that will
            // be accessible in this case, such as the keychain or
            // a file with a lenient protection option.
            let defaults = UserDefaults.standard
            var updateTime = defaults.double(forKey: LAST_UPDATE_KEY)
            if updateTime == 0 {
                updateTime = Date().timeIntervalSince1970
                self.mostRecentEventUpdateTime = updateTime
            }
            return updateTime
        }
        set {
            let defaults = UserDefaults.standard
            defaults.set(newValue, forKey: LAST_UPDATE_KEY)
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Android" %}

```kotlin
public class EventTimelineUpdateReceiver(val context: Context) {
    private val timelineStore: TimelineStore
    private val executor: ExecutorService

    init {
        timelineStore = TimelineStore() // your own store implementation
        executor = Executors.newSingleThreadExecutor()
    }

    fun listenForUpdates() {
        val api = EventTimelineApi.getInstance(context)

        // Saving a local copy of the most recent event time, before setting 
        // the listener below. This way, we make sure that this value does not
        // change by the listener callback method by the time we query for
        // updates.
        val mostRecentEventUpdateTime = getMostRecentEventUpdateTime()

        // Set the listener to start listening for timeline updates.
        api.setTimelineUpdateListener { event ->
            // Note: this invocation happens on the main/UI thread.
            updateOrInsert(event, updateMostRecentEventUpdateTime = true)
        }

        // Finally, let's make sure we haven't missed any events since the
        // last time our app ran (e.g. due to unexpected app termination).
        api.getTimelineUpdates(Date(mostRecentEventUpdateTime)).forEach { event ->
            updateOrInsert(event, updateMostRecentEventUpdateTime = false)
        }
    }

    private fun updateOrInsert(event: Event, updateMostRecentEventUpdateTime: Boolean) {
        // The single thread executor makes sure we don't process incoming
        // updates via the listener callback method and the result of calling
        // getTimelineUpdates() at the same time. Plus, it makes sure we do
        // the processing off of the main thread.
        executor.submit {
            val existingEvent = timelineStore.get(event.id)
            if (existingEvent != null) {
                // Make sure the update that we're about to apply isn't for 
                // an older version of the event than the one we already have.
                if (existingEvent.lastUpdateTime < event.lastUpdateTime) {
                    timelineStore.update(event)
                }
            } else {
                timelineStore.insert(event)
            }

            if (updateMostRecentEventUpdateTime) {
                // Note: events arriving listener callback method have
                // monotonically increasing lastUpdateTime.
                setMostRecentEventUpdateTime(event.lastUpdateTime.epochTime)
            }
        }
    }


    // We keep track of the most recent lastUpdateTime of the received
    // events, so that on the next run, we can use it to process missed
    // events (see listenForUpdates()).
    private fun getMostRecentEventUpdateTime(): Long {
        val prefs = context.getSharedPreferences(null, Context.MODE_PRIVATE)
        var updateTime = prefs.getLong(EventTimelineUpdateReceiver.PREFS_KEY_LAST_UPDATE_TIME, -1)
        if (updateTime < 0) {
            // Store the current time, so that in the future, we start from here.
            updateTime = System.currentTimeMillis()
            setMostRecentEventUpdateTime(updateTime)
        }
        return updateTime
    }
    
    private fun setMostRecentEventUpdateTime(timestamp: Long) {
        val prefs = context.getSharedPreferences(null, Context.MODE_PRIVATE)
        prefs.edit().putLong(EventTimelineUpdateReceiver.PREFS_KEY_LAST_UPDATE_TIME, timestamp).apply()
    }

    companion object {
        const val PREFS_KEY_LAST_UPDATE_TIME = "SentianceTimelineEventLastUpdateTime"
    }
}
```

{% endtab %}

{% tab title="React Native" %}
To get event timeline updates even when your app is in the background, place the following code inside your app's entrypoint **index.js** file. If you're only interested in these updates when your app is foregrounded, place this code inside the appropriate UI code instead.

```javascript
// We're using the DefaultPreference library to store key/value pairs (using 
// SharedPreferences on Android, and UserDefaults on iOS). Feel free to use 
// a library of your own choosing.
// Note however that if your app uses the iOS DataProtection capability, 
// accessing UserDefaults won't work when the device is locked. In this 
// case, use something that will be accessible (such as the keychain or
// a file with a lenient protection option).

import DefaultPreference from 'react-native-default-preference';
import EventTimelineApi from '@sentiance-react-native/event-timeline';
import SentianceCore from "@sentiance-react-native/core";

const KEY_LAST_UPDATE_TIME = 'SentianceTimelineEventLastUpdateTime';
const timelineStore = getTimelineStore(); // your own store implementation

await SentianceCore.ensureInitialized(); // Ensure SDK is initialized
listenForUpdates(); // Start listening to event updates

async function listenForUpdates() {
    // Saving a local copy of the most recent event update time, before setting
    // the listener below. This way, we make sure that this value does not
    // change by the listener callback method by the time we query for updates.
    const mostRecentEventUpdateTime = await getMostRecentEventUpdateTime();

    // Set the listener to start listening for timeline updates.
    //
    // If you're subscribing to event updates only in the foreground, make sure
    // to call subscription.remove() inside your component's componentWillUnmount() function
    const subscription = await EventTimelineApi.addTimelineUpdateListener(
        async event => await updateOrInsert(event, true));

    // Finally, let's make sure we haven't missed any events since the
    // last time our app ran (e.g. due to unexpected app termination).
    const events = await EventTimelineApi.getTimelineUpdates(mostRecentEventUpdateTime);
    for (let event in events) {
        await updateOrInsert(event, false);
    }
}

async function updateOrInsert(event, updateMostRecentEventUpdateTime) {
    // Make sure to handle concurrent access appropriately here
    
    const existingEvent = await timelineStore.get(event.id);
    if (existingEvent) {
        // Make sure the update that we're about to apply isn't for 
        // an older version of the event than the one we already have.
        if (existingEvent.lastUpdateTimeEpoch < event.lastUpdateTimeEpoch) {
            await timelineStore.update(event);
        }
    } else {
        await timelineStore.insert(event);
    }

    if (updateMostRecentEventUpdateTime) {
        // Note: events arriving through the listener callback method have
        // a monotonically increasing lastUpdateTimeEpoch.
        await setMostRecentEventUpdateTime(event.lastUpdateTimeEpoch)
    }
}

async function getMostRecentEventUpdateTime() {
    let updateTimeEpoch = await DefaultPreference.get(KEY_LAST_UPDATE_TIME);
    if (updateTimeEpoch < 0) {
        // Store the current time, so that in the future, we start from here.
        updateTimeEpoch = Date.now();
        await setMostRecentEventUpdateTime(updateTimeEpoch);
    }
    return updateTimeEpoch;
}

async function setMostRecentEventUpdateTime(timestamp) {
    await DefaultPreference.set(KEY_LAST_UPDATE_TIME, timestamp);
}
```

{% endtab %}

{% tab title="Flutter" %}
Create a **background.dart** file under your project's **lib** folder with the following code:

{% code title="background.dart" %}

```dart
// We're using the shared_preferences Flutter package to store key/value pairs 
// (using SharedPreferences on Android, and UserDefaults on iOS). 
// Feel free to use a library of your own choosing.
// Note however that if your app uses the iOS DataProtection capability, 
// accessing UserDefaults won't work when the device is locked. In this 
// case, use something that will be accessible (such as the keychain or
// a file with a lenient protection option).
import 'package:flutter/material.dart';
import 'package:sentiance_core/sentiance_core.dart';
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';
import 'package:shared_preferences/shared_preferences.dart';

const keyLastUpdateTime = 'SentianceTimelineEventLastUpdateTime';
final sentianceEventTimeline = SentianceEventTimeline();
const timelineStore = getTimelineStore(); // your own store implementation

@pragma('vm:entry-point')
void registerEventTimelineListener() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SentianceCore.ensureInitialized();
  
  SharedPreferences sharedPrefs = await SharedPreferences.getInstance();

  // Save a local copy of the most recent event update time, before setting
  // the listener below. This way, we make sure that this value does not
  // change by the listener callback method by the time we query for updates.
  final mostRecentEventUpdateTime = await getMostRecentEventUpdateTime();

  // Set the listener to start listening for timeline updates.
  SentianceEventTimeline.registerEventTimelineUpdateListener((timelineEvent) {
    await updateOrInsert(timelineEvent, true);
  });

  // Finally, let's make sure we haven't missed any events since the
  // last time our app ran (e.g. due to unexpected app termination).
  final events = await sentianceEventTimeline.getTimelineUpdates(mostRecentEventUpdateTime);
  for (var event in events.nonNulls) {
    await updateOrInsert(event, false);
  }
}

Future<void> updateOrInsert(TimelineEvent event, bool updateMostRecentEventUpdateTime) async {
  final existingEvent = await timelineStore.get(event.id); // your own store implementation
  if (existingEvent) {
    // Make sure the update that we're about to apply isn't for 
    // an older version of the event than the one we already have.
    if (existingEvent.lastUpdateTimeMs < event.lastUpdateTimeMs) {
      await timelineStore.update(event);
    }
  } else {
    await timelineStore.insert(event);
  }

  if (updateMostRecentEventUpdateTime) {
    // Note: events arriving through the listener callback method have
    // a monotonically increasing lastUpdateTimeMs.
    await setMostRecentEventUpdateTime(event.lastUpdateTimeMs);
  }
}

Future<void> setMostRecentEventUpdateTime(int timestamp) async {
  SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
  await sharedPrefs.setInt(keyLastUpdateTime, timestamp);
}

Future<int> getMostRecentEventUpdateTime() async {
  SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
  var updateTimeEpoch = sharedPrefs.getInt(keyLastUpdateTime);
  if (updateTimeEpoch == null) {
    // Store the current time, so that in the future, we start from here.
    updateTimeEpoch = DateTime.now().millisecondsSinceEpoch;
    await setMostRecentEventUpdateTime(updateTimeEpoch);
  }

  return updateTimeEpoch;
}
```

{% endcode %}

Add the following code according to your target platform.

For **iOS**, add the following to your app delegate class:

{% code title="AppDelegate.swift" %}

```swift
import Flutter
import sentiance_event_timeline

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
    
        // Other code
        
        // Make sure the SDK is initialized before calling this
        let flutterEngine = SentianceEventTimelinePlugin.initializeListener(
            withEntryPoint: "registerEventTimelineListener",
            libraryURI: "package:your_app_package_name/background.dart"
        )
        // This line below is required for shared_preferences to function
        GeneratedPluginRegistrant.register(with: flutterEngine)
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

{% endcode %}

For **Android**, add this code to your custom application class:

{% code title="MainApplication.kt" fullWidth="false" %}

```kotlin
import android.app.Application
import com.sentiance.event_timeline_plugin.EventTimelinePlugin
import io.flutter.plugins.GeneratedPluginRegistrant

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Other code

        val dartLibrary = "package:your_app_package_name/background.dart"
        // Make sure the SDK is initialized before calling this
        EventTimelinePlugin.initializeListener(this, dartLibrary, "registerEventTimelineListener") {
            // This line below is required for shared_preferences to function
            GeneratedPluginRegistrant.registerWith(it)
        }
    }
}
```

{% endcode %}

{% hint style="info" %}
If you're calling other 3rd party plugin APIs inside your `registerEventTimelineListener` Dart function, then you need to register these plugins with the Sentiance SDK. See [this](/sdk/appendix/flutter/declaring-3rd-party-plugins) for more details.
{% endhint %}
{% endtab %}
{% endtabs %}


# Lifestyle Insights

{% hint style="info" %}
To learn about the Lifestyle Insights feature, check out [this page](/getting-started/features-catalog/lifestyle-insights).

This feature is currently in **Early Access** and is still under active development
{% endhint %}

## Prerequisites

The Lifestyle Insights feature relies on additional library dependencies alongside the core SDK dependency. To start using this feature, you must first add these dependencies to you project.

<details>

<summary>iOS</summary>

This feature is included in the main SDK framework. No additional dependencies are needed.

</details>

<details>

<summary>Android</summary>

Open your app <mark style="color:$success;">**`build.gradle`**</mark> file and add the lifestyle dependency.

{% code title="app/build.gradle" %}

```kotlin
dependencies {
    implementation(platform('com.sentiance:sdk-bom:<sentiance-version>'))
    implementation("com.sentiance:sdk-lifestyle")
}
```

{% endcode %}

</details>

<details>

<summary>React Native</summary>

This feature is included after installing the following modules:

* [@sentiance-react-native/core](https://www.npmjs.com/package/@sentiance-react-native/core)
* [@sentiance-react-native/event-timeline](https://www.npmjs.com/package/@sentiance-react-native/event-timeline)
* [@sentiance-react-native/user-context](https://www.npmjs.com/package/@sentiance-react-native/user-context)

</details>

<details>

<summary>Flutter</summary>

This feature can be added by installing the following packages:

* [sentiance\_core](https://pub.dev/packages/sentiance_core)
* [sentiance\_event\_timeline](https://pub.dev/packages/sentiance_event_timeline)
* [sentiance\_user\_context](https://pub.dev/packages/sentiance_user_context)

</details>

## Utilize the User Context APIs

In this section, you can find examples of how to query the Sentiance SDK for the user's current context, and how to register to receive context updates in your app, as the user's context changes.

### Query for the User's Current Context

The user's context includes the following information:

* Recent transport and stationary events, with venue information if available.
* The user's last known location.
* The user's home and work locations, if detected.
* The user's current semantic time (e.g. morning time, lunch time).
* The user's segments, if detected (e.g. dog walker, aggressive driver).

The population of this context information happens offline, on the device.

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.requestUserContext { context, error in
    guard let context = context else {
        print("Error: \(error?.failureReason)")
        return
    }
    
    print("Recent events:")
    
    // Print the recent events
    context.events.forEach({ event in
        print("Event ID: \(event.eventId)")
        print("Started on: \(event.startDate)")
        print("Ended on: \(event.endDate)")
        
        if event.type == .inTransport {
            let transport =  event as! SENTTransportEvent
            
            print("Type: transport")
            print("Mode: \(toStringMode(transport.transportMode))")
            
            if let distance = transport.distanceInMeters {
                print("Distance: \(distance)")
            }
            
            print("Waypoints: \(transport.waypoints)")
            
        }
        else if event.type == .stationary {
            let stationary =  event as! SENTStationaryEvent
            
            print("Type: stationary")
            print("Location: \(stationary.location)")
            print("Venue: \(stationary.venue)")
        }
        else if event.type == .offTheGrid {
            print("Type: off-the-grid")
        }
        else {
            print("Type: unknown")
        }
        
        print("")
    })
    
    // Print the home & work locations, semantic time, and last known location
    print("Home venue: \(context.home)")
    print("Work venue: \(context.work)")
    print("Semantic time: \(toString(context.semanticTime))")
    print("Last known location: \(context.lastKnownLocation)")
    
    // Print the user's active segments
    print("Active segments:")
    
    context.activeSegments.forEach { segment in
        print("  Category: \(toString(segment.category))")
        print("  Subcategory: \(toString(segment.subcategory))")
        print("  Type: \(toString(segment.type))")
        print("  Start date: \(toString(segment.startDate))")
        print("  End date: \(toString(segment.endDate))")
        
        print("  Attributes: ")
        segment.attributes.forEach { attribute in
            print("    \(attribute.name): \(attribute.value)")
        }
    }
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
UserContextApi.getInstance(context).requestUserContext()
    .addOnFailureListener { error ->
        print ("User context retrieval failed with reason ${error.reason}")
    }
    .addOnSuccessListener { context ->

        // Print the recent events
        print("Recent events:")

        context.events.forEach { event ->
            print("Event ID: ${event.id}")
            print("Started on: ${event.startTime}")
            print("Ended on: ${event.endTime}")

            when (event.eventType) {
                EventType.IN_TRANSPORT -> {
                    val transport = event as TransportEvent!

                    print("Type: transport")
                    print("Mode: ${transport.transportMode}")

                    if (transport.distanceInMeters != null) {
                        print("Distance: ${transport.distanceInMeters}")
                    }

                    print("Waypoints: ${transport.waypoints}")
                }
                EventType.STATIONARY -> {
                    val stationary = event as StationaryEvent!

                    print("Type: stationary")
                    print("Location: ${stationary.location}")
                    print("Venue: ${stationary.venue}")
                }
                EventType.OFF_THE_GRID -> {
                    print("Type: off-the-grid")
                }
                else -> {
                    print("Type: unknown")
                }
            }
        }

        // Print the home & work locations, semantic time, and last known location
        print("Home venue: ${context.home}")
        print("Work venue: ${context.work}")
        print("Semantic time: ${context.semanticTime}")
        print("Last known location: ${context.lastKnownLocation}")

        // Print the user's active segments
        print("Active segments:")

        context.activeSegments.forEach { segment ->
            print("  Category: ${segment.category}")
            print("  Subcategory: ${segment.subcategory}")
            print("  Type: ${segment.type}")
            print("  Start date: ${segment.startTime}")
            print("  End date: ${segment.endTime}")

            print("  Attributes: ")
            segment.attributes.forEach { attribute ->
                print("    ${attribute.name}: ${attribute.value}")
            }
        }
    }
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import SentianceUserContext from '@sentiance-react-native/user-context';

const context = await SentianceUserContext.requestUserContext();

// Print the recent events
for (const event of context.events) {
    console.log(`Event ID: ${event.id}`);
    console.log(`Started on: ${event.startTime}`);
    console.log(`Ended on: ${event.endTime}`);

    switch (event.type) {
        case 'IN_TRANSPORT':
            const transport = event;
            console.log('Type: transport');
            console.log(`Mode: ${transport.transportMode}`);

            if (transport.distance) {
                console.log(`Distance: ${transport.distance}`);
            }
            console.log(`Waypoints: ${JSON.stringify(transport.waypoints)}`);
            break;
        case 'STATIONARY':
            const stationary = event;
            console.log('Type: stationary');
            console.log(`Location: ${JSON.stringify(stationary.location)}`);
            console.log(`Venue: ${JSON.stringify(stationary.venue)}`);
            break;
        case 'OFF_THE_GRID':
            console.log('Type: off-the-grid');
            break;
        default:
            console.log('Type: unknown');
    }
}

// Print the home & work locations, semantic time, and last known location
console.log(`Home venue: ${JSON.stringify(context.home)}`)
console.log(`Work venue: ${JSON.stringify(context.work)}`)
console.log(`Semantic time: ${context.semanticTime}`)
console.log(`Last known location: ${JSON.stringify(context.lastKnownLocation)}`)

// Print the user's active segments
console.log('Active segments:')

for (const segment of context.activeSegments) {
    console.log(`  ID: ${segment.id}`)
    console.log(`  Category: ${segment.category}`)
    console.log(`  Subcategory: ${segment.subcategory}`)
    console.log(`  Type: ${segment.type}`)
    console.log(`  Start date: ${segment.startTime}`)
    console.log(`  End date: ${segment.endTime}`)

    console.log('  Attributes:')
    segment.attributes.forEach { attribute ->
        console.log(`    ${attribute.name}: ${attribute.value}`)
    }
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_user_context/sentiance_user_context.dart';
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';

final sentianceUserContext = SentianceUserContext();

void fetchUserContext() async {
  final context = await sentianceUserContext.requestUserContext();

  // Print the recent events
  for (final event in context.events) {
    print('Event ID: ${event.id}');
    print('Started on: ${event.startTimeMs}');
    print('Ended on: ${event.endTimeMs}');

    if (event is TransportEvent) {
      final transport = event;
      print('Type: transport');
      print('Mode: ${transport.transportMode}');
      print('Distance: ${transport.distance}');
      print('Waypoints: ${transport.waypoints}');
    } else if (event is StationaryEvent) {
      print('Type: stationary');
      print('Location: ${event.location}');
      print('Venue: ${event.venue}');
    } else if (event is OffTheGridEvent) {
      print('Type: off-the-grid');
    } else if (event is UnknownEvent) {
      print('Type: unknown');
    }
  }

  // Print the home & work locations, semantic time, and last known location
  print('Home venue: ${context.home}');
  print('Work venue: ${context.work}');
  print('Semantic time: ${context.semanticTime}');
  print('Last known location: ${context.lastKnownLocation}');

  // Print the user's active segments
  print('Active segments:');

  for (final segment in context.activeSegments) {
    print('  ID: ${segment.segmentId}');
    print('  Category: ${segment.category}');
    print('  Subcategory: ${segment.subcategory}');
    print('  Type: ${segment.type}');
    print('  Start date: ${segment.startTimeMs}');
    print('  End date: ${segment.endTimeMs}');

    print('  Attributes:');
    for (final attribute in segment.attributes.nonNulls) {
      print('    ${attribute.name}: ${attribute.value}');
    }
  }
}
```

{% endtab %}
{% endtabs %}

### Subscribe for User Context Updates

{% hint style="info" %}
Checkout [Real-time Listeners](/sdk/appendix/real-time-listeners) to read more about implementing the SDK's Listeners
{% endhint %}

You can subscribe to receive updates, as the user's current context changes.&#x20;

{% tabs %}
{% tab title="iOS" %}
{% code lineNumbers="true" %}

```swift
public class UserContextHandler: SENTUserContextDelegate {
    
    public func subscribe() {
        Sentiance.shared.userContextDelegate = self
    }    
        
    public func didUpdate(_ userContext: SENTUserContext, 
       forCriteriaMask criteriaMask: SENTUserContextUpdateCriteria) {
        
        // Check the updated criteria
        if criteriaMask.contains(.currentEvent) {
            print("The user's current event was updated")
        }
        if criteriaMask.contains(.visitedVenues) {
            print("The user's visited venue information was updated")
        }
        if criteriaMask.contains(.activeSegments) {
            print("The user's active segments were updated")
        }
       
        // Handle the updated context data (see the Query for the 
        // User's Current Context example above)
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Android" %}

```kotlin
UserContextApi.getInstance(context)
  .addUserContextUpdateListener { criteria, context ->
    // Check the updated criteria
    if (criteria.contains(UserContextUpdateCriteria.CURRENT_EVENT)) {
        print("The user's current event was updated")
    }
    if (criteria.contains(UserContextUpdateCriteria.VISITED_VENUES)) {
        print("The user's visited venue information was updated")
    }
    if (criteria.contains(UserContextUpdateCriteria.ACTIVE_SEGMENTS)) {
        print("The user's active segments were updated")
    }

    // Handle the updated context data (see the Query for the
    // User's Current Context example above)
}
```

{% endtab %}

{% tab title="React Native" %}
To get user context updates even when your app is in the background, place the following code inside your app's entrypoint **index.js** file. If you're only interested in these updates when your app is foregrounded, place this code inside the appropriate UI code instead.

```javascript
import {addUserContextUpdateListener} from "@sentiance-react-native/user-context";
import SentianceCore from "@sentiance-react-native/core";

await SentianceCore.ensureInitialized(); // Ensure SDK is initialized

// If you're subscribing to event updates only in the foreground, make sure
// to call subscription.remove() inside your component's componentWillUnmount() function
const subscription = addUserContextUpdateListener(userContext => {
    // Handle the updated context data (see the Query for the
    // User's Current Context example above)
});
```

{% endtab %}

{% tab title="Flutter" %}
Create a **background.dart** file under your project's **lib** folder with the following code:

{% code title="background.dart" %}

```dart
import 'package:sentiance_user_context/sentiance_user_context.dart';
import 'package:sentiance_core/sentiance_core.dart';

@pragma('vm:entry-point')
void registerUserContextListener() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SentianceCore.ensureInitialized();

  SentianceUserContext.registerUserContextUpdateListener((criteria, context) {
    // Check the updated criteria
    if (criteria.contains(UserContextUpdateCriteria.currentEvent)) {
      print("The user's current event was updated");
    }
    if (criteria.contains(UserContextUpdateCriteria.visitedVenues)) {
      print("The user's visited venue information was updated");
    }
    if (criteria.contains(UserContextUpdateCriteria.activeSegments)) {
      print("The user's active segments were updated");
    }

    // Handle the updated context data (see the Query for the
    // User's Current Context example above)
  });
}
```

{% endcode %}

Add the following code, depending on your target platform.

For **iOS**, add the following to your app delegate class:

{% code title="AppDelegate.swift" %}

```swift
import Flutter
import sentiance_user_context

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
    
        // Other code
        
        // Make sure the SDK is initialized before calling this
        SentianceUserContextPlugin.initializeListener(
            withEntryPoint: "registerUserContextListener",
            libraryURI: "package:your_app_package_name/background.dart"
        )
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

{% endcode %}

For **Android**, add this code to your custom application class:

{% code title="MainApplication.kt" %}

```kotlin
import android.app.Application
import com.sentiance.user_context_plugin.UserContextPlugin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Other code

        val dartLibrary = "package:your_app_package_name/background.dart"
        // Make sure the SDK is initialized before calling this
        UserContextPlugin.initializeListener(this, dartLibrary, "registerUserContextListener")
    }
}
```

{% endcode %}

{% hint style="info" %}
If you're calling other 3rd party plugin APIs inside your `registerUserContextListener` Dart function, then you need to register these plugins with the Sentiance SDK. See [this](/sdk/appendix/flutter/declaring-3rd-party-plugins) for more details.
{% endhint %}
{% endtab %}
{% endtabs %}


# Crash Insights

{% hint style="info" %}
To learn about the Crash Insights feature, check out [this page](/getting-started/features-catalog/crash-insights).
{% endhint %}

## Prerequisites

The Crash Insights feature relies on additional library dependencies alongside the core SDK dependency. To start using this feature, you must first add these dependencies to you project.

<details>

<summary>iOS</summary>

This feature is included in the main SDK framework. No additional dependencies are needed.

</details>

<details>

<summary>Android</summary>

Open your app <mark style="color:$success;">**`build.gradle`**</mark> file and add the Crash Detection dependency.

{% code title="app/build.gradle" %}

```kotlin
dependencies {
    implementation(platform('com.sentiance:sdk-bom:<sentiance-version>'))
    implementation('com.sentiance:sdk-crash-detection')
}
```

{% endcode %}

</details>

<details>

<summary>React Native</summary>

This feature is included after installing the following modules:

* [@sentiance-react-native/core](https://www.npmjs.com/package/@sentiance-react-native/core)
* [@sentiance-react-native/crash-detection](https://www.npmjs.com/package/@sentiance-react-native/crash-detection)

</details>

<details>

<summary>Flutter</summary>

This feature can be added by installing the following packages:

* [sentiance\_core](https://pub.dev/packages/sentiance_core)
* [sentiance\_crash\_detection](https://pub.dev/packages/sentiance_crash_detection)

</details>

## Utilize the Crash Detection APIs

In this section, you’ll find examples showing how to check whether vehicle crash detection is supported on a device, how to subscribe to crash event notifications, and additional details about the contents of a crash event and how to test your integration.

### Check if Vehicle Crash Detection Is Supported

There are several reasons why vehicle crash detection may not be supported on the device. The two most common of these are:

* the feature is not enabled for your app;
* the device lacks the necessary sensors (e.g. accelerometer).

You can check to see whether vehicle crash detection is support on the device, as follows:

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.isVehicleCrashDetectionSupported
```

{% endtab %}

{% tab title="Android" %}

```kotlin
CrashDetectionApi.getInstance(context).isVehicleCrashDetectionSupported
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import { 
    isVehicleCrashDetectionSupported 
} from '@sentiance-react-native/crash-detection';

const result = isVehicleCrashDetectionSupported();
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_crash_detection/sentiance_crash_detection.dart';

final sentianceCrashDetection = SentianceCrashDetection();
bool result = await sentianceCrashDetection.isVehicleCrashDetectionSupported();
```

{% endtab %}
{% endtabs %}

### Subscribe for Vehicle Crash Events

In order to be notified of vehicle crash events, you can specify the listener that the SDK will invoke when it detects a vehicle crash.

{% hint style="info" %}
Checkout [Real-time Listeners](/sdk/appendix/real-time-listeners) to read more about implementing the SDK's Listeners
{% endhint %}

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.setVehicleCrashHandler { crashEvent in
    let date = crashEvent.date
    let confidence = crashEvent.confidence
    let deltaV = crashEvent.deltaV
    let magnitude = crashEvent.magnitude
    let speedAtImpact = crashEvent.speedAtImpact
    let location = crashEvent.location
    let severity = crashEvent.severity
}
```

{% endtab %}

{% tab title="Android" %}

```java
CrashDetectionApi.getInstance(context).setVehicleCrashListener(new VehicleCrashListener() {
    @Override
    public void onVehicleCrash(VehicleCrashEvent crashEvent) {
        long epochTimeMs = crashEvent.getTime();
        Location location = crashEvent.getLocation();
        Float speedAtImpact = crashEvent.getSpeedAtImpact();
        Float magnitude = crashEvent.getMagnitude();
        Float deltaV = crashEvent.getDeltaV();
        Integer confidence = crashEvent.getConfidence();
        VehicleCrashSeverity severity = crashEvent.getSeverity();
    }
});
```

{% endtab %}

{% tab title="React Native" %}

<pre class="language-javascript"><code class="lang-javascript">import SentianceCore from "@sentiance-react-native/core";
<strong>import { 
</strong>    addVehicleCrashEventListener 
} from '@sentiance-react-native/crash-detection';

await SentianceCore.ensureInitialized();

addVehicleCrashEventListener(crashEvent => {
    const time = crashEvent.time;
    const location = crashEvent.location;
    const magnitude = crashEvent.magnitude;
    const speedAtImpact = crashEvent.speedAtImpact;
    const deltaV = crashEvent.deltaV;
    const confidence = crashEvent.confidence;
    const severity = crashEvent.severity;
});
</code></pre>

{% endtab %}

{% tab title="Flutter" %}
Create a **background.dart** file under your project's **lib** folder with the following code:

{% code title="background.dart" %}

```dart
import 'package:sentiance_crash_detection/sentiance_crash_detection.dart';
import 'package:sentiance_core/sentiance_core.dart';

@pragma('vm:entry-point')
void registerCrashDetectionListener() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SentianceCore.ensureInitialized();

  // Subscribe for vehicle crash events
  SentianceCrashDetection.registerCrashListener((crashEvent) {
    // Handle the vehicle crash event

    final epochTimeMs = crashEvent.time;
    final location = crashEvent.location;
    final speedAtImpact = crashEvent.speedAtImpact;
    final magnitude = crashEvent.magnitude;
    final deltaV = crashEvent.deltaV;
    final confidence = crashEvent.confidence;
    final severit = crashEvent.severity;
  });
}
```

{% endcode %}

Add the following code, depending on your target platform.

For **iOS**, add the following to your app delegate class:

{% code title="AppDelegate.swift" %}

```swift
import Flutter
import sentiance_crash_detection

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
    
        // Other code
        
        // Make sure the SDK is initialized before calling this
        SentianceCrashDetectionPlugin.initializeListener(
            withEntryPoint: "registerCrashDetectionListener",
            libraryURI: "package:your_app_package_name/background.dart"
        )
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

{% endcode %}

For **Android**, add this code to your custom application class:

{% code title="MainApplication.kt" %}

```kotlin
import android.app.Application
import com.sentiance.crash_detection_plugin.CrashDetectionPlugin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Other code

        val dartLibrary = "package:your_app_package_name/background.dart"
        // Make sure the SDK is initialized before calling this
        CrashDetectionPlugin.initializeListener(this, dartLibrary, "registerCrashDetectionListener")
    }
}
```

{% endcode %}

{% hint style="info" %}
If you're calling other Crash Detection plugin APIs or other 3rd party plugin APIs inside your `registerCrashDetectionListener` Dart function, then you need to register these plugins with the Sentiance SDK. See [this](/sdk/appendix/flutter/declaring-3rd-party-plugins) for more details.
{% endhint %}
{% endtab %}
{% endtabs %}

### Crash Event Details

A crash event contains the **time and location** of the detected crash, in addition to a number of metrics to estimate the severity of the crash:

<table data-header-hidden><thead><tr><th width="199"></th><th></th></tr></thead><tbody><tr><td>Speed at impact</td><td>The estimated speed of the vehicle before the impact, in m/s.</td></tr><tr><td>Magnitude</td><td>The magnitude of the impact, in m/s².</td></tr><tr><td>Delta-V</td><td>The estimated change in velocity at impact, in m/s.</td></tr><tr><td>Confidence</td><td>The level of confidence that the accelerometer signal reflects a true crash pattern (range 0 - 100). It is recommended to filter out events below the confidence of 50.</td></tr><tr><td>Severity</td><td>A categorical severity of the crash (low, medium, high).</td></tr></tbody></table>

### Test Your Integration

The final step is checking your integration, to make sure that your vehicle crash listener is properly set up to handle crash events. Add the following method call in your app to trigger a dummy crash event.

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.invokeDummyVehicleCrash()
```

{% endtab %}

{% tab title="Android" %}

```java
CrashDetectionApi.getInstance(context).invokeDummyVehicleCrash()
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import { 
    invokeDummyVehicleCrash 
} from '@sentiance-react-native/crash-detection';

invokeDummyVehicleCrash();
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_crash_detection/sentiance_crash_detection.dart';

final sentianceCrashDetection = SentianceCrashDetection();
await sentianceCrashDetection.invokeDummyVehicleCrash();
```

{% endtab %}
{% endtabs %}

This would invoke your listener, passing it a dummy crash event. You can test how your app handles the event at runtime.

### Crash Detection Diagnostic Information

It’s also possible to subscribe to additional diagnostic data produced by the SDK to better understand the state and decision-making of the crash detection and to facilitate testing.

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.setVehicleCrashDiagnosticHandler { diagnostic in
    let state = diagnostic.crashDetectionState
    let description = diagnostic.crashDetectionStateDescription
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
CrashDetectionApi.getInstance(context).setVehicleCrashDiagnosticListener { diagnostic ->
    val state = diagnostic.crashDetectionState
    val description = diagnostic.crashDetectionStateDescription
}
```

{% endtab %}

{% tab title="React Native" %}

```javascript
await SentianceCrashDetection.addVehicleCrashDiagnosticListener(diagnostic => {
    const state = diagnostic.crashDetectionState;
    const description = diagnostic.crashDetectionStateDescription;
}
```

{% endtab %}

{% tab title="Flutter" %}
Inside the `registerCrashDetectionListener` method described in [the subscription section](#subscribe-for-vehicle-crash-events) above, add the following code to register for crash diagnotic updates.

```dart
 SentianceCrashDetection.registerCrashDiagnosticListener((diagnostic) {
    final state = diagnostic.crashDetectionState;
    final description = diagnostic.crashDetectionStateDescription;
 });
```

{% endtab %}
{% endtabs %}

The result will reflect one of the following states:

* Crash candidate detected
* Crash candidate discarded - impact is too weak
* Crash candidate discarded - transport mode is not a vehicle
* Crash candidate discarded - pre-impact signal contains too much noise
* Crash candidate discarded - speed before impact is too low
* Crash candidate discarded - post-impact signal contains too much noise
* Crash candidate discarded - speed after impact is too high


# Smart Geofences

***

{% hint style="info" %}
To learn about the Smart Geofences feature, check out [this page](/getting-started/features-catalog/smart-geofences).\
\
Smart Geofences can be configured for an entire population, but not at the individual user level. At the moment, creating geofences directly through the SDK is not supported. Instead, you can provide Sentiance with the list of geofences to configure.
{% endhint %}

## Prerequisites

The Smart Geofences feature relies on additional library dependencies alongside the core SDK dependency. To start using this feature, you must first add these dependencies to you project.

<details>

<summary>iOS</summary>

This feature is included in the main SDK framework. No additional dependencies are needed.

</details>

<details>

<summary>Android</summary>

Open your app <mark style="color:$success;">**`build.gradle`**</mark> file and add the smart geofences dependency.

{% code title="app/build.gradle" %}

```kotlin
dependencies {
    implementation(platform("com.sentiance:sdk-bom:<sentiance-version>"))
    implementation("com.sentiance:sdk-smart-geofences")
}
```

{% endcode %}

</details>

<details>

<summary>React Native</summary>

This feature is included after installing the following modules:

* [@sentiance-react-native/core](https://www.npmjs.com/package/@sentiance-react-native/core)
* [@sentiance-react-native/smart-geofences](https://www.npmjs.com/package/@sentiance-react-native/smart-geofences)

</details>

<details>

<summary>Flutter</summary>

This feature can be added by installing the following packages:

* [sentiance\_core](https://pub.dev/packages/sentiance_core)
* [sentiance\_smart\_geofences](https://pub.dev/packages/sentiance_smart_geofences)

</details>

## Utilize the Smart Geofences APIs

In this section, you’ll find examples showing how to subscribe for geofence entry and exit events, how to force-update the geofence list, and how to check the geofence monitoring status.

### Listen to Smart Geofence Entry and Exit Events

{% hint style="info" %}
Checkout [Real-time Listeners](/sdk/appendix/real-time-listeners) to read more about implementing the SDK's Listeners
{% endhint %}

You can subscribe to receive geofence entry and exit event notification as they are detected.

{% tabs %}
{% tab title="iOS" %}

```swift
// Create an instance of the delegate that will handle the events.
private let smartGeofenceEventDelegate = MySmartGeofenceEventDelegate()

// Set the delegate on the Sentiance SDK. Note that the SDK holds a weak
// reference to this delegate.
Sentiance.shared.smartGeofenceEventsDelegate = smartGeofenceEventDelegate

// Define the delegate class that will handle the events.
class MySmartGeofenceEventDelegate: SmartGeofenceEventDelegate {
    func onSmartGeofenceEvent(_ smartGeofenceEvent: SmartGeofenceEvent) {
        // Handle the events here.
    }
}
```

{% endtab %}

{% tab title="Android" %}
{% code overflow="wrap" fullWidth="false" %}

```kotlin
import com.sentiance.sdk.smartgeofences.api.SmartGeofenceApi

SmartGeofenceApi.getInstance(mContext).setSmartGeofenceEventListener { event ->
    // Handle the events here.
}
```

{% endcode %}
{% endtab %}

{% tab title="React Native" %}
To get smart geofence entry/exit event updates even when your app is in the background, place the following code inside your app's entrypoint **index.js** file. If you're only interested in these updates when your app is foregrounded, place this code inside the appropriate UI code instead.

```javascript
import {addSmartGeofenceEventListener} from "@sentiance-react-native/smart-geofences";
import SentianceCore from "@sentiance-react-native/core";

await SentianceCore.ensureInitialized();

// If you're subscribing to event updates only in the foreground, make sure
// to call subscription.remove() inside your component's componentWillUnmount() function
const subscription = addSmartGeofenceEventListener(smartGeofenceEvent => {
    // Handle the events here.
});
```

{% endtab %}

{% tab title="Flutter" %}
Create a **background.dart** file under your project's **lib** folder with the following code:

{% code title="background.dart" %}

```dart
import 'package:sentiance_smart_geofences/sentiance_smart_geofences.dart';
import 'package:sentiance_core/sentiance_core.dart';

@pragma('vm:entry-point')
void registerSmartGeofenceEventListener() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SentianceCore.ensureInitialized();

  SentianceSmartGeofences.registerSmartGeofenceEventListener((smartGeofenceEvent) {
    // Handle the events here.
  });
}
```

{% endcode %}

Add the following code, depending on your target platform.

For **iOS**, add the following to your app delegate class:

{% code title="AppDelegate.swift" %}

```swift
import Flutter
import sentiance_smart_geofences

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
    
        // Other code
        
        // Make sure the SDK is initialized before calling this
        SentianceSmartGeofencesPlugin.initializeListener(
            withEntryPoint: "registerSmartGeofenceEventListener",
            libraryURI: "package:your_app_package_name/background.dart"
        )
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

{% endcode %}

For **Android**, add this code to your custom application class:

{% code title="MainApplication.kt" %}

```kotlin
import android.app.Application
import com.sentiance.smart_geofences_plugin.SmartGeofencesPlugin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Other code

        val dartLibrary = "package:your_app_package_name/background.dart"
        // Make sure the SDK is initialized before calling this
        SmartGeofencesPlugin.initializeListener(this, dartLibrary, "registerSmartGeofenceEventListener")
    }
}
```

{% endcode %}

{% hint style="info" %}
If you're calling other Smart Geofences plugin APIs or other 3rd party plugin APIs inside your `registerSmartGeofenceEventListener` Dart function, then you need to register these plugins with the Sentiance SDK. See [this](/sdk/appendix/flutter/declaring-3rd-party-plugins) for more details.
{% endhint %}
{% endtab %}
{% endtabs %}

### Refresh the List of Monitored Geofences

The SDK regularly refreshes the list of monitored geofences. You can request an immediate refresh as follows:

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.refreshSmartGeofences { result, error in
    if let result {
        print("Geofences refreshed")
    }
    if let error {
        print("Error happened with smart geofence refreshing:" + error.description)
    }
}
```

{% endtab %}

{% tab title="Android" %}
{% code fullWidth="false" %}

```kotlin
import com.sentiance.sdk.smartgeofences.api.SmartGeofenceApi

SmartGeofenceApi.getInstance(mContext).refreshGeofences()
    .addOnSuccessListener {
        Log.d(TAG, "Geofences refreshed")
    }.addOnFailureListener {
        Log.d(TAG, "Failed to refresh geofences. Error: ${it.reason}")
    }
```

{% endcode %}
{% endtab %}

{% tab title="React Native" %}

```javascript
import {refreshGeofences} from "@sentiance-react-native/smart-geofences";

try {
     await refreshGeofences();
     console.log('Geofences refreshed');
} catch (error) {
     const refreshError = error.userInfo;
     console.error('Failed to refresh geofences. Error: ' + refreshError.reason);
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_smart_geofences/sentiance_smart_geofences.dart';

final sentianceSmartGeofences = SentianceSmartGeofences();

void refreshGeofences() async {
  String result;

  try {
    await sentianceSmartGeofences.refreshGeofences();
    result = "Geofences refreshed successfully.";
  } on SmartGeofencesRefreshError catch (e) {
    result = "Failed to refresh geofences, reason: ${e.reason.name} - details: ${e.details}";
  } catch (e) {
    result = "An unexpected error occurred: $e";
  }

  print(result);
}
```

{% endtab %}
{% endtabs %}

### Get the Current Smart Geofences Detection Mode

You can check the smart geofences detection mode as follows:

{% tabs %}
{% tab title="iOS" %}

```swift
let detectionMode = Sentiance.shared.smartGeofenceDetectionMode
print("Detection mode is:" + String(describing: detectionMode))
```

{% endtab %}

{% tab title="Android" %}
{% code fullWidth="false" %}

```kotlin
val detectionMode = SmartGeofenceApi.getInstance(this).detectionMode
Log.d(TAG, "Detection mode is: $detectionMode")
```

{% endcode %}
{% endtab %}

{% tab title="React Native" %}

```javascript
import {getDetectionMode} from "@sentiance-react-native/smart-geofences";

const detectionMode = await getDetectionMode();
console.log('Detection mode is:', detectionMode);
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:sentiance_smart_geofences/sentiance_smart_geofences.dart';

final sentianceSmartGeofences = SentianceSmartGeofences();

void getDetectionMode() async {
  final detectionMode = await sentianceSmartGeofences.getDetectionMode();
  print('Detection mode is: $detectionMode');
}
```

{% endtab %}
{% endtabs %}

When enabled, the detection mode can either be in the foreground (i.e when the app is visible), or both foreground and background, depending on the type of location permission that has been granted (i.e. "always" or "while-in-use").

When detection mode is disabled, it indicates a general issue with the SDK’s detection. You can check the SDK status to identify the cause.


# Engagement

The Engagement product offering sits on top of Sentiance's Insights and can be used to build coaching modules to increase user engagement and retention in your applications.

{% hint style="info" %}
All Engagement features require data to be synced with the Sentiance backend.\
\
In the Engagement section of documentation, 'transport' refers to a journey someone takes, whether it's by walking, driving, or biking.
{% endhint %}

## Available features

The Engagement platform contains the following features. All features can be implemented separately, or together with any of the other features.

Select a feature to discover more details and learn how to integrate it.

* [User Adaptive Score](/implementing-features/engagement/user-adaptive-score): Adapts to individual user behavior to provide scores.
* [Streaks](/implementing-features/engagement/streaks): Encourages better driving habits by rewarding users for consistent safe driving behaviors
* [Challenges](/implementing-features/engagement/challenges): Engages users with driving-related challenges, designed to promote safer driving practices and improve overall driving behavior through engaging and goal-oriented tasks
* [Reward System](/implementing-features/engagement/reward-system): Awards users with visual tokens of achievement for completing specific milestones or activities.
* [Communication Campaigns](/implementing-features/engagement/communication-campaign): Engages users with targeted messages and notifications, enhancing interaction and retention.
* [Social Groups](/implementing-features/engagement/social-groups): Enables users to connect with others by sharing live locations, managing points of interest, and participating in behaviour comparison within private, invitation-only groups
* Transport Scores: Offers detailed insights based on transportation behavior, including adaptive scores, focused scores, etc., aimed at enhancing understanding and improvement of driving and transportation habits.
* Event Logs (Engagement): Allows users to submit logs, with some logs carrying special meanings related to the operation of the other engagement features,

## Querying the platform

Just like the rest of the cloud platform, the engagement features can be accessed and queried through our GraphQL (GQL) API. All engagement features can be queried through our GraphQL (GQL) API, specifically through the subquery at path `user.engagement`

```graphql
query User($user_id: String) {
  user(user_id: $user_id) {
    engagement {
      // Specify the engagement feature and details you wish to query
    }
  }
}
```

## Sample Wireframe

<figure><img src="/files/VPquYqeQTBL9BkuuCZ4D" alt=""><figcaption></figcaption></figure>

***

Additional helpful links

* [Accessing via the Cloud API](/getting-started/accessing-insights/cloud-api)


# User Adaptive Score

The **User Adaptive Score** provides a dynamic and ongoing assessment of a user’s driving or riding behavior over time, based on the user's car and motorcycle trips respectively. Unlike trip-level scores that evaluate individual trips, the User AdaptiveScore is a rolling aggregate that reflects a user’s overall performance across multiple trips. This continuous evaluation helps in tracking long-term trends in driving or riding behavior, making it easier to identify improvements or areas that require further attention.

The User Adaptive Score is updated at the end of each trip and is calculated based on the current adaptive score and the score of the completed trip.

{% hint style="info" %}
**GQL Definition**: UserEngagementScore

**GQL Path**: `user.engagement.scores.slice.name`
{% endhint %}

```graphql
query User($user_id: String) {
  user(user_id: $user_id) {
    engagement {
      scores {
	slice {
	  name # name of the score. Ex: OVERALL_SCORE, ATTENTION_SCORE, LEGAL_SCORE, etc
	  value # value of the score. Between [0, 100] for OVERALL_SCORE and between [0,1] for rest of scores ]
        }
      }
    }
  }
}
```

## Sub Scores

The Dynamic Scoring Model features an adaptive overall score, denoted as `OVERALL_SCORE`, which represents a comprehensive assessment of a user's driving or riding behavior. In addition to this overarching score, there are specific sub scores designed to highlight particular aspects of driving or riding behavior. All scores, including the overall score, are computed per transport mode. For some use cases it might be useful to merge scores for cars and motorcycles a.k.a Compound Scores.

<table><thead><tr><th width="278.3828125">Score</th><th>Comment</th></tr></thead><tbody><tr><td>FOCUS_SCORE</td><td>Acknowledges periods of attentive driving or riding without active phone handling</td></tr><tr><td>CALL_WHILE_MOVING_SCORE</td><td>Identifies instances where drivers or riders avoid making calls while moving over a speed of 15 km/h</td></tr><tr><td>ATTENTION_SCORE</td><td>Acknowledges periods of attentive driving without distractions like hands free calling, handheld calling, phone handling or screen use</td></tr><tr><td>LEGAL_SCORE</td><td>Rewards adherence to speed limits</td></tr><tr><td>MFFS_SCORE</td><td>Variation of LEGAL_SCORE that excludes congested roads</td></tr><tr><td>HARSH_MOVEMENT_SCORE</td><td>Recognises sequences of smooth driving, emphasising gentle acceleration and braking</td></tr><tr><td>SMOOTH_SCORE</td><td>Similar to HARSH_MOVEMENT_SCORE but also takes into account harsh turning events</td></tr></tbody></table>

## Example Wireframe

<div align="left"><figure><img src="/files/U135OLqR8ZzzgmG7VHlg" alt="" width="375"><figcaption></figcaption></figure></div>

***

Additional helpful links:

* GQL [UserEngagementScore](https://graphqldocs.sentiance.com/#definition-UserEngagementScores) definition


# Streaks

**Streaks** are a powerful feature designed to motivate users by encouraging consistent improvement in their driving or riding behavior. They leverage the psychology of habit formation by rewarding users for maintaining or improving their performance over consecutive trips or time periods. There are two types of streaks in the Engagement Module:

* **Strict Streaks**\
  In a Strict Streak, users must maintain their scores above a predefined threshold to continue the streak. This threshold can apply to trip scores, or aggregated scores over a time period (usually DAY by DAY). The idea is to challenge users to consistently meet a minimum standard of safe and efficient driving or riding. If the user’s score falls below the threshold at any point, the streak ends and it will start over. This type of streak is effective for reinforcing a baseline level of behavior.
* **Self-Competing Streaks**\
  Self-Competing Streaks encourage users to compete against themselves by striving for a streak of continuously improving scores. Unlike Strict Streaks, which have only a fixed threshold, Self-Competing Streaks require the user to keep the scores above the threshold while also surpassing their previous performance with each successive trip or period. This approach taps into the motivation to achieve personal bests and fosters ongoing improvement by rewarding users for incremental progress.

Both types of streaks are designed to keep users engaged and motivated, making the pursuit of safer and more efficient driving or riding behaviour a rewarding and continuous process.

Streaks are configurable and can be evaluated either on a TRIP by TRIP basis or on a DAY by DAY basis:

* **TRIP** variant will evaluate the streak state after each trip
* **DAY** variant will evaluate streaks state at the end of the day using the average score of all the trips in that time frame

Examples of customisable streaks include:

* **Excellent Trips Streak (default behavior):** Tracks the number of consecutive trips rated as 'Excellent' (e.g >90)
* **Excellent Days Streak**: Tracks the number of consecutive days where the average score was 'Excellent' (e.g >90)
* **Good Trips Streak:** Tracks the number of consecutive trips rated as 'good'
* **Action Consistency Streak:** Monitors the maximum number of times a user consecutively performs a specific positive action.

{% hint style="info" %}
**GQL Definition**: UserEngagementStreaks

**GQL Path for current streaks**: `user.engagement.streaks.current.slice.name`

**GQL Path for best streaks**: `user.engagement.streaks.best.slice.name`
{% endhint %}

{% code fullWidth="false" %}

```graphql
query getStreaks($user_id: String!) {
  user(user_id: $user_id) {
    user_id
    engagement {
      streaks(transport_mode: CAR, variant: STREAK_VARIANT_DAY, type: STREAK_TYPE_STRICT) {
        
        # provides the best streaks 
	best {
	  slice {
	    name # name of the score the streak is tracking. Ex: OVERALL_SCORE, LEGAL_SCORE
	    value # value of the streak
	  }
	}
	
	# provides the current on-going streaks
	current {
	  slice {
	    name # name of the score the streak is tracking. Ex: OVERALL_SCORE, LEGAL_SCORE
	    value # value of the streak
	  }
        }
      }
    } 
  }
}
      
 
```

{% endcode %}

## Sub Streaks

In addition to recognising overall safe driving and riding behaviour, the "Streaks" feature offers a variety of sub-streaks designed to acknowledge and promote specific aspects of user conduct.

<table><thead><tr><th width="273">Streaks</th><th>Comment</th></tr></thead><tbody><tr><td>FOCUS_SCORE</td><td>Acknowledges periods of attentive driving without active phone handling</td></tr><tr><td>CALL_WHILE_MOVING_SCORE</td><td>Identifies instances where drivers avoid making calls while moving over a speed of 15 km/h</td></tr><tr><td>ATTENTION_SCORE</td><td><p>Acknowledges periods of attentive driving without distractions like hands free calling, handheld</p><p>calling, phone handling or screen use</p></td></tr><tr><td>LEGAL_SCORE</td><td>Rewards adherence to speed limits</td></tr><tr><td>MFFS_SCORE</td><td>Variation of LEGAL_SCORE that excludes congested roads</td></tr><tr><td>HARSH_MOVEMENT_SCORE</td><td>Recognises sequences of smooth driving, emphasising gentle acceleration and braking</td></tr><tr><td>SMOOTH_SCORE</td><td>Similar to HARSH_MOVEMENT_SCORE but also takes into account harsh turning events</td></tr></tbody></table>

## Streaks Context

Streaks can also be enriched with a context that gives more insights into how the streak progressed towards the current state.

{% code fullWidth="false" %}

```graphql
query getStreaks($user_id: String!) {
  user(user_id: $user_id) {
    user_id
    engagement {
      streaks(transport_mode: CAR, variant: STREAK_VARIANT_DAY, type: STREAK_TYPE_STRICT) {
        	
	# provides the current on-going streaks
	current {
	  slice {
	    name # name of the score the streak is tracking. Ex: OVERALL_SCORE, LEGAL_SCORE
	    value # value of the streak
	  }
	
  	  context {
	    scores_trend { # trend represing how your last trip influenced you average scores for the DAY
	      slice {
		key
		value {
		  trend
          	  start_datetime
          	  end_datetime
		}
	      }
	    }
	    streak_thresholds { # as defined in Streaks configuration
	      slice {
		name
		value
	      }
	    }
	    daily_avg_scores { # average scores for the past 7 days
	      slice {
		 key
		 value {
		   slice {
		     name
		     value
	     	   }
	         }
	      }
	    }
	    today_avg_scores { # today's average scores
	      slice {
		name
		value
	      }
	    }
	  }
        }
      }
    } 
  }
}
```

{% endcode %}

## Example Wireframe

<div align="left"><figure><img src="/files/6Y7NmEx2vFjKHg5ksdtf" alt="" width="375"><figcaption></figcaption></figure></div>

***

Additional helpful links:

* GQL [UserEngagementStreaks](https://graphqldocs.sentiance.com/#definition-UserEngagementStreaks) definition


# Challenges

The "Challenges" module is meticulously crafted to provide a structured, step-by-step approach aimed at enhancing users' driving behavior.

## Types of Challenges

The "Challenges" feature in the Sentiance platform offers a variety of targeted challenges designed to improve specific aspects of driving behavior. By engaging users with these challenges, the platform aims to encourage safer driving practices and habits. Below are the types of challenges available:

* **Focused Challenges**: These challenges are designed to help drivers improve their concentration on the road, reducing distractions and thereby enhancing overall safety.
* **Speeding Challenges**: Aimed at encouraging drivers to adhere to speed limits, these challenges focus on reducing instances of speeding.

{% hint style="info" %}
**GQL Definition**: UserEngagementChallenges

**GQL Path**: `user.engagement.challenges.available`
{% endhint %}

```graphql
query User($user_id: String) {
  user(user_id: $user_id) {
    engagement {
      challenges {
        available {
          slice {
            challenge_id
            category
            image_url
            progress
            status
          }
        }
      }
    }
  }
}
```

## Workflow

The Sentiance platform's "Challenges" feature introduces an interactive model that differentiates it from more passive features like the "user adaptive score" and "streaks". While these passive features primarily involve the application querying the platform for insights to display to the user, "Challenges" actively engages users by allowing them to interact directly with the application. Here's how the workflow is designed:

* **List Available Challenges**: Display available challenges to users.
* **Accept Challenge**: Users choose a challenge to accept.
* **Participation (IN\_PROGRESS)**: Users engage in the challenge.
* **Winning**: Successfully completing challenge criteria.
* **Failing**: Option to retake uncompleted challenges.

Note: A user can opt to abandon a challenge at any time.

<figure><img src="/files/86LDIOXnXQIO9hjG19AZ" alt=""><figcaption></figcaption></figure>

***

Additional helpful links:

* GQL [UserEngagementChallenges](https://graphqldocs.sentiance.com/#definition-UserEngagementChallenges) definition


# Reward System

Integrate an in-app rewards system that leverages aggregated Sentiance data to celebrate user progress and achievements. This system is designed to visualize users' progress based on specific actions or accomplishments within the app, offering recognition, motivation, and a tangible sense of progress and accomplishment. By aligning rewards with desired behaviors or milestones, you encourage continued engagement and positive behavior change among your users. Here's how the rewards system can be customized and implemented:

* **Tailorable Badges:** Create badges that are fully customizable, allowing them to be specifically defined based on population data or individual achievements. This flexibility ensures that badges can be relevant and motivating for different user groups or objectives.
* **Behavior Recognition:** The system can recognize and reward a wide range of behaviors, including but not limited to:
  * **Driving Behavior:** Reward safe driving practices, improvements in driving habits, or participation in driving-related challenges.
  * **In-app Actions:** Acknowledge user engagement within the app, such as completing set tasks, engaging with new features, or consistent app usage over time.
  * **Combined Features:** Integrate rewards with other platform features, like challenges, streaks, or transport scores, to create a comprehensive motivation system that encourages a variety of positive behaviors.

{% hint style="info" %}
**GQL Definition**: UserEngagementBadges

**GQL Path**: `user.engagement.badges`
{% endhint %}

```graphql
query User($user_id: String) {
  user(user_id: $user_id) {
    engagement {
      badges {
        slice {
          id
          progress
          description
          status
        }
      }
    }
  }
}
```

## Sample Wireframe

<div align="left"><figure><img src="/files/TXvyilP9ahbDGBMVpISa" alt="" width="375"><figcaption></figcaption></figure></div>

***

Additional helpful links:

* GQL [UserEngagementBadges](https://graphqldocs.sentiance.com/#definition-UserEngagementBadge) definition


# Communication Campaign

The Communication Campaign feature encompasses the suite of in-app messaging tools designed to provide timely and relevant feedback to users about their driving behavior, challenges, streaks, and nudges. Based on configuration set, the Sentiance Engagement Platform generates user-specific messages informed by their behavior and journeys.

Ideally, these messages are designed to be versatile in their presentation, allowing for display as widgets on a screen, as popup messages (for example, "You have succeeded in your challenge"), or within a dedicated page for listing notifications.

Every message belongs to a **category** and a **type**

### Message Category

<table><thead><tr><th width="191">Category</th><th></th></tr></thead><tbody><tr><td>ALL</td><td>Messages relevant to all aspects of the app's engagement features.</td></tr><tr><td>CHALLENGES</td><td>Communications specific to the challenges users are participating in or have completed.</td></tr><tr><td>BADGES</td><td>Messages related to badges earned for various achievements.</td></tr><tr><td>STREAKS</td><td>Updates and information on users' streaks.</td></tr><tr><td>SCORES</td><td>Communications concerning users' scores from their driving behavior</td></tr><tr><td>LEADERBOARD</td><td>Messages related to leaderboard standings, encouraging competitive engagement.</td></tr><tr><td>TRIPS</td><td>Updates on users' trips.</td></tr><tr><td>PROFILE</td><td>Messages related to the user's profile information or updates.</td></tr><tr><td>SCHEDULED</td><td>Scheduled messages meant for timely delivery on specific dates or events.</td></tr></tbody></table>

### Message Types

<table><thead><tr><th width="217">Type</th><th>Comment</th></tr></thead><tbody><tr><td>IN_APP_MESSAGE</td><td>Direct messages displayed within the app, providing immediate feedback or information.</td></tr><tr><td>REACTIVE_MESSAGE</td><td>Messages triggered by specific user actions or behaviors, offering real-time engagement.<br>(e.g you have successfully completed your challenge)</td></tr></tbody></table>

{% hint style="info" %}
**GQL Definition**: UserEngagementCommunication

**GQL Path**: user.engagement.communications
{% endhint %}

```graphql
query User($user_id: String) {
  user(user_id: $user_id) {
    engagement {
      communications {
        new(type: IN_APP_MESSAGE) {
          limit: 1
          slice {
            message
          }
        }
      }
    }
  }
}
```

## Sample Wireframe & Workflow

<figure><img src="/files/eMXBuhP8uJXvkNFeuEu1" alt=""><figcaption></figcaption></figure>

***

Additional helpful links:

* GQL [UserEngagementCommunication](https://graphqldocs.sentiance.com/#definition-UserEngagementCommunication) definition


# Social Groups

Social Groups is a feature designed to keep users connected with family and friends, emphasising both safety and engagement. It focuses on two main components:

* **Family Safety**
* **Behaviour Comparison**

### Family Safety

Stay connected and ensure peace of mind with **Family Safety**. This component centres around live location tracking and points of interest, allowing group members to visualise on a map where others are in real-time.

#### Live Location Tracking

* **Shared Map**: See the real-time locations of group members on a shared map.
* **Stay Connected**: Easily coordinate meet-ups and check on loved ones' whereabouts.

#### Points of Interest (POIs)

* **Create POIs**: Mark important locations like home, school, or favourite spots.
* **Notifications**: Get alerts when group members arrive at or leave designated POIs.
* **Shared Experiences**: Encourage group activities by highlighting places of interest.

### Behaviour Comparison

Add a fun and motivating element to your users interactions with Behaviour Comparison. This component introduces leaderboards based on user scores, encouraging positive behaviours and friendly rivalry.

#### Leaderboards

* **User Scores**: Earn points for actions like safe driving habits.
* **Rankings**: See how you rank within your group.
* **Dynamic Updates**: Receive notifications when rankings change.

#### Events Feed

* **Activity Updates**: Stay informed about group members' achievements, such as completing challenges, earning badges, or changes in leaderboard positions.

By combining safety features with interactive challenges, **Social Groups** offers a platform that keeps your users connected and engaged with their community, all while promoting safe behaviours and family well-being.


# Technical Details

This section provides technical details to help you integrate the Social Groups feature into your application, including examples of GraphQL queries and mutations.

## Group Management

Manage social groups effectively within your application using intuitive group management features.

#### Invitation-Based Groups

* **Registration Codes**: Groups are invitation-based, utilising unique registration codes that can be shared.
* **Sharing Invitations**: Users share the registration code with family and friends to invite them to join a group.
* **Secure Access**: Only those with the registration code can request to join, keeping groups secure.

#### Membership Requests and Roles

* **Membership Approval**: Existing group members can accept or decline membership requests from new users.
* **User Roles**: Each member within a group is assigned a specific role:
  * **ADMIN**: Has full control over group settings, including managing members and approving requests.
  * **MEMBER**: Standard participant with access to all group features.
  * **PENDING**: Users who have requested to join the group and are awaiting approval.
* **Role Management**: ADMINs can change the roles of members as needed to maintain group integrity.

#### Managing Groups

* **Approving Requests**: Members receive notifications of pending requests and can approve or deny them.
* **Maintaining Privacy**: The role system ensures that only approved members have access to the group's information and features.

## Technical Implementation

#### Group Features

Groups have specific features selected during creation, which determine their behavior in subsequent mutations and queries. These features, such as live location tracking, leaderboard rankings, or Points of Interest (POIs), influence how the group’s data is managed and retrieved.

Available features:

* leaderboard: set flag <mark style="color:blue;">**with\_leaderboard**</mark> to <mark style="color:blue;">**true**</mark>
* event feed: set flag <mark style="color:blue;">**with\_feed**</mark> to <mark style="color:blue;">**true**</mark>
* live locations: set flag <mark style="color:blue;">**with\_locations**</mark> to <mark style="color:blue;">**true**</mark>
* points of interest: set flag <mark style="color:blue;">**with\_poi**</mark> to <mark style="color:blue;">**true**</mark>

### Create Group Mutation

**Description:** Creates a new social group with specified attributes.

```graphql
mutation Create_group(
  $features: CreateUserEngagementGroupRequest_UserEngagementGroupFeaturesInput, # Group features
  $name: String, # Name of the group
  $group_type: UserEngagementGroupTypeEnum, # Type of the group (e.g., SOCIAL)
  $origin: String, # Origin of the group (e.g., 'client_app')
  $ranking_attributes: [UserEngagementGroupRankingAttributeEnum] # Attributes for group ranking
) {
  create_group(
    features: $features,
    name: $name,
    group_type: $group_type,
    origin: $origin,
    ranking_attributes: $ranking_attributes
  ) {
    group {
      group_id # ID of the created group
      name # Name of the created group
      created_at # Timestamp of creation
      reg_code # Registration code to be used by other users to request memebership
    }
    status # Success or failure status
  }
}

# VARIABLES
{
  "features": { "with_locations": true },
  "name": "My Social Group",
  "group_type": "SOCIAL",
  "origin": "client_app",
  "ranking_attributes": ["DRIVER_COACHING_SCORE"]
}
```

### Remove Group Membership Mutation

**Description:** Removes a user from a specified group.

```graphql
mutation Remove_group_membership(
  $removed_user_id: String, # ID of the user to remove
  $group_id: String # ID of the group
) {
  remove_group_membership(
    removed_user_id: $removed_user_id,
    group_id: $group_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "removed_user_id": "abc123",
  "group_id": "xyz789"
}
```

### Update Group Mutation

**Description:** Updates details of an existing group.

```graphql
mutation Update_group(
  $group_id: String, # ID of the group to update
  $name: String # New name of the group
) {
  update_group(
    group_id: $group_id,
    name: $name
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "abc123",
  "name": "Updated Group Name"
}
```

### Delete Group Mutation

**Description:** Deletes a group using its ID.

```graphql
mutation Delete_group($group_id: String) { # ID of the group to delete
  delete_group(group_id: $group_id) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "abc123"
}
```

### Approve Group Join Mutation

**Description:** Approves a pending request for a user to join a group.

```graphql
mutation Approve_group_join(
  $pending_user_id: String, # ID of the pending user
  $group_id: String # ID of the group
) {
  approve_group_join(
    pending_user_id: $pending_user_id,
    group_id: $group_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "pending_user_id": "user123",
  "group_id": "group456"
}
```

### Decline Group Join Mutation

**Description:** Declines a pending request for a user to join a group.

```graphql
mutation Decline_group_join(
  $group_id: String, # ID of the group
  $pending_user_id: String # ID of the pending user
) {
  decline_group_join(
    group_id: $group_id,
    pending_user_id: $pending_user_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "pending_user_id": "user456"
}
```

### Make Group Admin Mutation

**Description:** Assigns a user as an admin of a group.

```graphql
mutation Make_group_admin(
  $group_id: String, # ID of the group
  $new_admin_user_id: String # ID of the new admin user
) {
  make_group_admin(
    group_id: $group_id,
    new_admin_user_id: $new_admin_user_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "new_admin_user_id": "user456"
}
```

### Unmake Group Admin Mutation

**Description:** Removes a user's admin privileges in a group.

```graphql
mutation Unmake_group_admin(
  $group_id: String, # ID of the group
  $removed_admin_user_id: String # ID of the admin to remove
) {
  unmake_group_admin(
    group_id: $group_id,
    removed_admin_user_id: $removed_admin_user_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "removed_admin_user_id": "user456"
}
```

### Join Group Mutation

**Description:** Allows a user to join a group using a registration code.

```graphql
mutation Join_group($reg_code: String) { # Registration code of the group
  join_group(reg_code: $reg_code) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "reg_code": "reg789"
}
```

### Leave Group Mutation

**Description:** Allows a user to leave a group.

```graphql
mutation Leave_group($group_id: String) { # ID of the group
  leave_group(group_id: $group_id) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group456"
}
```

### Set In-Group Status Mutation

**Description:** Sets a specific status for a user within a group.

```graphql
mutation Set_in_group_status(
  $status: String, # Status of the user in the group
  $expire_in: Int, # Expiration time in seconds
  $group_id: String # ID of the group
) {
  set_in_group_status(
    status: $status,
    expire_in: $expire_in,
    group_id: $group_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "status": "Active",
  "expire_in": 31536000,  # 1 year
  "group_id": "group123"
}
```

### Create Group POI Mutation

**Description:** Creates a new Point of Interest (POI) within a group.

```graphql
mutation Create_group_poi(
  $group_id: String, # ID of the group
  $poi: EngagementPOIInput # POI details
) {
  create_group_poi(
    group_id: $group_id,
    poi: $poi
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "poi": {
    "name": "Central Park",
    "coordinates": {
      "latitude": 40.785091,
      "longitude": -73.968285
    },
    "radius": 100
  }
}
```

### Delete Group POI Mutation

**Description:** Deletes a Point of Interest (POI) from a group.

<pre class="language-graphql"><code class="lang-graphql"><strong>mutation Delete_group_poi(
</strong>  $group_id: String, # ID of the group
  $poi_id: String # ID of the POI
) {
  delete_group_poi(
    group_id: $group_id,
    poi_id: $poi_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "poi_id": "poi456"
}
</code></pre>

### &#x20;Subscribe Group POI Mutation

**Description:** Subscribes to updates related to a POI in a group.

```graphql
mutation Subscribe_group_poi(
  $group_id: String, # ID of the group
  $poi_id: String # ID of the POI
) {
  subscribe_group_poi(
    group_id: $group_id,
    poi_id: $poi_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "poi_id": "poi789"
}
```

### Unsubscribe Group POI Mutation

**Description:** Unsubscribes from updates related to a POI in a group.

```graphql
mutation Unsubscribe_group_poi(
  $group_id: String, # ID of the group
  $poi_id: String # ID of the POI
) {
  unsubscribe_group_poi(
    group_id: $group_id,
    poi_id: $poi_id
  ) {
    status # Success or failure status
  }
}

# VARIABLES
{
  "group_id": "group123",
  "poi_id": "poi789"
}
```

### **Group Members Query**

**Description:** Retrieves the members of a specific group, including their roles, status, and location details.

```graphql
query getGroupMembers($user_id: String, $group_id: String) {
  user(user_id: $user_id) {
    engagement {
      groups(group_id: $group_id) {
        slice {
          members {
            slice {
              poi_subscriptions
              user_id
              role
              status
              status_expires_at
              joined_at
              last_known_location {
                latitude
                longitude
                timestamp
              }
              current_poi {
                poi_id
                poi_details {
                  name
                  coordinates {
                    latitude
                    longitude
                  }
                }
                entered_poi_at
              }
            }
          }
        }
      }
    }
  }
}

# VARIABLES
{
  "user_id": "xyz789",
  "group_id": "abc123"
}
```

### **Group Leaderboard Query**

**Description:** Retrieves leaderboard details for a specific group, including user ranks and scores.

```graphql
query getGroupLeaderboard($user_id: String, $group_id: String) {
  user(user_id: $user_id) {
    engagement {
      groups(group_id: $group_id) {
        slice {
          leaderboard {
            slice {
              user_id
              rank
              ranking_attr
              ranking_score
            }
          }
        }
      }
    }
  }
}

# VARIABLES
{
  "user_id": "xyz789",
  "group_id": "abc123"
}
```

### &#x20;**Group Feed Query**

**Description:** Retrieves the feed messages of a specific group.

```graphql
query getGroupFeed($user_id: String, $group_id: String) {
  user(user_id: $user_id) {
    engagement {
      groups(group_id: $group_id) {
        slice {
          feed {
            slice {
              message
              params
            }
          }
        }
      }
    }
  }
}

# VARIABLES
{
  "user_id": "xyz789",
  "group_id": "abc123"
}
```

### **Group POIs Query**

**Description:** Retrieves the points of interest (POIs) for a specific group.

```graphql
query getGroupPOIs($user_id: String, $group_id: String) {
  user(user_id: $user_id) {
    engagement {
      groups(group_id: $group_id) {
        slice {
          points_of_interest {
            slice {
              poi_id
              poi_details {
                name
                coordinates {
                  latitude
                  longitude
                }
                address {
                  city
                }
              }
            }
          }
        }
      }
    }
  }
}

# VARIABLES
{
  "user_id": "xyz789",
  "group_id": "abc123"
}
```

***

Additional helpful links:

* [GQL](https://graphqldocs.sentiance.com/#introduction) definition


# Testing data

### Fake Transports

We support injecting fake transport data for testing our Engagement technology built on top of transports data ([Engagement](/getting-started/features-catalog/engagement): streaks, challenges, badges, etc).

This can be achieved by using the [create\_fake\_transport](https://graphqldocs.sentiance.com/#mutation-create_fake_transport) mutation using an [API KEY](/sdk/appendix/request-authentication) with the scope [FAKE\_DATA\_INSERT](/sdk/appendix/request-authentication#api-keys).&#x20;

```graphql
mutation ($user_id: String) {
    create_fake_transport(
	user_id: $user_id # has to be a valid Sentince ID
	mode: CAR
	safety_scores: {
		overall_safety: 1.0
		legal: 1.0
		smooth: 1.0
		focus: 1.0
		call_while_moving: 1.0
	},
	with_trajectory: true) # the trip will have waypoints for trajectory
    {
	transport_id # the ID of the injected fake transport
    }
}
```

This feature is only available for dev app IDs; get in touch with your contact at Sentiance or contact <support@sentiance.com> to get you started.


# Appendix


# Android


# Android 10 Update Behavior

If you are still targeting API level 28 or lower, read the following information to understand the impact of upgrading to Android 10 on the location and activity recognition permissions.

### Location Permission

![](https://3097961207-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FB9ZHBaHKglgKmgIlyHT0%2Fuploads%2Fgit-blob-64946e274858b692f4c9c46afe54c372d4cbdfd4%2Fandroid10_location.png?alt=media)

#### Summary

The sooner you add the background location permission to your app, the better. This is because after an Android 10 upgrade, you will benefit from the auto-granted background location access (see #2.1 above). However, the caveat is that if a user has already upgraded to Android 10, updating to this new version of your app that includes the new permission will cause the auto-granted background access to be revoked, regardless of the app's target API level (see #1.1.3 above).

So you have two options:

1. Add the new background location permission to your app now (while Android 10 population is low) and benefit from the auto-granted access as your users upgrade to Android 10, but you'll need to handle your existing Android 10 users by asking them to re-grant the background location access permission, or
2. postpone adding the new permission and benefit from the auto-granted background location access for your existing and upgrading Android 10 users, however, once deciding to update your app's target API level to 29, add the new permission and ask all your Android 10 users to re-grant the background location permission.

### Activity Recognition Permission

`com.goo...ACTIVITY_RECOGNITION` represents Google Play Services' activity recognition permission, which is already added to your app by the Sentiance SDK.

![](https://3097961207-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FB9ZHBaHKglgKmgIlyHT0%2Fuploads%2Fgit-blob-3d06b4ccb92e4ce664de8cc1cf86870c27817497%2FAndroid10_activity.png?alt=media)

#### Summary

If you're still targeting API level 28 and lower, there's no special benefit in adding the new activity recognition permission to your app. There is a down side though. You lose the install-time auto-granted access for new installations on Android 10 (see #4.2 above). You can wait until after targeting API level 29, but after doing so, remember to handle fresh Android 10 installations by asking your users to explicitly grant the permission.

If you're already targeting API level 29, add the permission to your manifest and ask your users to grant it.


# Android Battery Optimization

When running the SDK on Android 6 and higher, it is recommended to ask the user to disable battery optimization for your app. This makes sure that SDK detections continue to work properly when the device is in Doze mode. This is particularly important with manufacturers like OnePlus and Nokia (HMD Global) who customize Android to do aggressive battery optimization, keeping the device in Doze mode longer than intended.

Additionally, on Android 9, disabling this will prevent Adaptive Battery from [bucketing](https://developer.android.com/about/versions/pie/power#buckets) your app based on usage and [restricting background processing](https://developer.android.com/reference/android/app/ActivityManager#isBackgroundRestricted\(\)), all of which that can impact the detection quality of the SDK.

After explaining to the user about the benefits of disabling battery optimization, call the [`disableBatteryOptimization()`](https://docs.sentiance.com/important-topics/api-reference/android/sentiance#disablebatteryoptimization) method of the [`Sentiance`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/sentiance) class. This will trigger a system dialog asking the user to allow disabling battery optimization for your app.

```kotlin
Sentiance.getInstance(context).disableBatteryOptimization()
```

The Sentiance SDK does not define the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission required for this feature. You must therefore explicitly add this permission to your app.

{% code title="AndroidManifest.xml" %}

```xml
<uses-permission 
	android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
```

{% endcode %}

{% hint style="danger" %}
Please note that Google Play policies prohibit apps from requesting this permission unless the app's core functionality is affected. For more information about the supported use cases, see [here](https://developer.android.com/training/monitoring-device-state/doze-standby#whitelisting-cases).
{% endhint %}


# Artifacts & Dependencies

### Version 6.x

Below are the dependencies of the various Sentiance SDK modules, based on their latest published version.

{% hint style="danger" %}
Sentiance artifacts that depend on each other share a common version number. This is denoted as **\<sentiance-version>** below. Mixing different versions of these artifacts is not supported. You can avoid version conflicts by making use of the SDK's bill of materials (com.sentiance:sdk-bom).
{% endhint %}

#### com.sentiance:sdk-bom

This is the bill of materials artifact. You can add a platform dependency to this artifact to avoid version mismatches between different Sentiance artifacts.

{% code title="build.gradle" %}

```groovy
dependencies {
    implementation(platform('com.sentiance:sdk-bom:<sentiance-version>'))
    implementation('com.sentiance:sdk')
    implementation('com.sentiance:sdk-crash-detection')
    ...
}
```

{% endcode %}

#### com.sentiance:sdk

This is the core library artifact which offers the main SDK functionality, such as user creation and detections.

**Dependencies**

```
com.google.android.gms:play-services-location:18.0.0
com.google.android.gms:play-services-basement:18.0.2
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
com.sentiance:sdk-breakpad:1.0.2
```

#### com.sentiance:sdk-breakpad

This library artifact allows the Sentiance SDK to capture and collect information about native app crashes.

You do not need to manually add this artifact as a dependency in your app.

**Dependencies**

This artifact has no dependencies.

#### com.sentiance:sdk-on-device-common

This library artifact provides internal SDK functionality that is common among Sentiance's on-device features.

You do not need to manually add this artifact as a dependency in your app.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
org.tensorflow:tensorflow-lite:v2.16.2-sentiance.3
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-crash-detection

This library artifact adds vehicle crash detection functionality to the SDK, and makes the `CrashDetectionApi` available to your app.

You must add this artifact as a dependency in your app if you want to enable crash detection.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-on-device-common:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-event-timeline

This library artifact provides internal SDK functionality that is required for creating a user's event-timeline on the device. Other SDK features, such as user context information and segment detection, are built on top of this timeline data.

You do not need to manually add this artifact as a dependency in your app.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-on-device-common:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-venue-mapper

This library artifact provides internal SDK functionality that is required for enriching a user's event-timeline using venue information.

You do not need to manually add this artifact as a dependency in your app.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-event-timeline:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
org.jetbrains.kotlinx:kotlinx-datetime:0.4.1
org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1
org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
```

#### com.sentiance:sdk-user-context

This library artifact adds the user context SDK functionality, and makes the `UserContextApi` available to your app. The user's context includes information about the recent timeline events (e.g. transports and stationaries), home and work venues, and the user's segments (if enabled/available).

You must add this artifact as a dependency in your app if you want to access the user's context information. Alternatively, you can add the *sdk-lifestyle* artifact to enable all lifestyle features, including the user's context.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-venue-mapper:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-segments

This library artifact adds user segment detection functionality to the SDK. Segment data is added to the user's context information (if enabled).

This artifact must be added as a dependency in your app if you want to enable segment detection. However, the recommended approach is to add the *sdk-lifestyle* artifact to enable all lifestyle features, including segment detection.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-event-timeline:<sentiance-version>
com.sentiance:sdk-venue-mapper:<sentiance-version>
com.sentiance:sdk-decision-engine:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-decision-engine

This library artifact is required by *sdk-segments* to do segment detection.

You do not need to manually add this artifact as a dependency in your app.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-venue-mapper:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
org.jetbrains.kotlinx:kotlinx-datetime:0.4.1
org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1
org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
co.touchlab:stately-common:1.2.5
co.touchlab:stately-concurrency:1.2.5
co.touchlab:kermit-jvm:1.2.2
```

#### com.sentiance:sdk-lifestyle

This library artifact adds the full Sentiance lifestyle functionality that is available via the SDK. This includes user event timeline creation, user context information, and user segment detection.

You must add this artifact as a dependency in your app if you want to enable the full lifestyle functionality.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-user-context:<sentiance-version>
com.sentiance:sdk-segments:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-driving-insights

This library artifact adds the driving insights SDK functionality, and makes the `DrivingInsightsApi` available to your app. The driving insights includes information about detected transports, such as scores for various safe driving attributes (e.g. smooth driving).

You must add this artifact as a dependency in your app if you want to access the driving insights information.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-event-timeline:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

#### com.sentiance:sdk-smart-geofences

This library artifact adds the Smart Geofence SDK functionality, and makes the `SmartGeofencesApi` available to your app. Smart Geofences allow you to monitor points of interest for entry and exit events.

You must add this artifact as a dependency in your app if you want to utilize the Smart Geofences feature.

**Dependencies**

```
com.sentiance:sdk:<sentiance-version>
com.sentiance:sdk-event-timeline:<sentiance-version>
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
```

### Version 4.x

```
com.google.android.gms:play-services-location:12.0.1
org.tensorflow:tensorflow-lite:2.7.0
```


# Foreground Service

When the Sentiance SDK runs in the background, it starts a foreground service to maintain reliable detections. This is a requirement enforced by Android to ensure long-running background processing can continue even when the app is not in focus.

### Foreground Service Notifications

Starting a foreground service requires showing a persistent notification. This is mandated by the Android OS to inform users about ongoing background work.

By default, the SDK displays a minimal notification using the app’s name and icon. However, you can fully customize this notification to match your app’s branding and use case.

To customize the notification, create a notification object and [set it on the SDK initialization options](https://docs.sentiance.com/a-complete-integration/android-sdk/initialization#id-3.-customize-the-android-notification-optional). At runtime, you can update this notification by calling [updateSdkNotification](https://docs.sentiance.com/important-topics/api-reference/android/sentiance#updatesdknotification).

### Foreground Service Types

In compliance with Android 14 (API level 34) and higher, foreground services must declare a specific service type in the manifest. The Sentiance SDK declares the following service types in its Android Manifest:

* **location**: indicates that the service is accessing location data in the background.
* **shortService**: indicates that the service is expected to run short tasks.


# Manifest Permissions

When building your app, the following permissions, which are defined in the Sentiance SDK, will be automatically added to your app

* android.permission.ACCESS\_WIFI\_STATE
* android.permission.ACCESS\_NETWORK\_STATE
* android.permission.ACCESS\_COARSE\_LOCATION
* android.permission.ACCESS\_FINE\_LOCATION
* android.permission.FOREGROUND\_SERVICE
* android.permission.FOREGROUND\_SERVICE\_LOCATION
* android.permission.HIGH\_SAMPLING\_RATE\_SENSORS
* android.permission.INTERNET
* android.permission.RECEIVE\_BOOT\_COMPLETED
* android.permission.SCHEDULE\_EXACT\_ALARM
* android.permission.WAKE\_LOCK
* com.google.android.gms.permission.ACTIVITY\_RECOGNITION


# Notification Management

When specifying a channel/priority for the SDK notification, use a separate channel/priority configured to show non-intrusive notifications. When the SDK is running, your application should show a subtle notification icon, with an appropriate message informing your users about the ongoing detection.

Setting the channel importance to `IMPORTANCE_LOW` and the notification priority to `PRIORITY_MIN` will create notifications that do not pop up in front of the user, or cause sounds and device vibrations. The goal is to not distract the user every time the Sentiance SDK runs, as this will cause frustration and lead to eventual app removal.

Properly wording your notification is also important to let your users know why your application is running. State what value you are adding to your user at that moment. For example:

> **Insights is running**\
> Working in the background to improve your driving habits

Finally, it's important to let your users know that they can easily prioritize and disable notifications. Using the following snippet on Android 8 and above, you can take your user to the channel configuration setting directly from within your app:

```kotlin
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
            .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
            .putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
startActivity(intent)
```

For more on notification channel best practices, check out Android's [design guidelines](https://material.io/design/platform-guidance/android-notifications.html#settings).


# Sample Notification

The example below shows how to create a notification that can be passed to `SentianceOptions`. We use the `androidx.core.app.NotificationCompat` class to build a notification suitable for both old and recent Android versions.

The notification channel, necessary for Android 8 and above, has the channel importance set to `IMPORTANCE_LOW`, the minimum [recommended](https://developer.android.com/reference/android/app/NotificationManager#IMPORTANCE_MIN) by Google for notifications used for running services.

```java
private fun createNotification(): Notification {
    // PendingIntent that will start your application's MainActivity
    val intent = Intent(context, MainActivity::class.java)
    val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)

    // On Oreo and above, you must create a notification channel
    val channelId = "background_detections"
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(channelId, "Background Detections", NotificationManager.IMPORTANCE_LOW)
        channel.setShowBadge(false)
        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
    return Builder(context, channelId)
        .setContentTitle(context.getString(R.string.app_name).toString() + " is running")
        .setContentText("Touch to open.")
        .setContentIntent(pendingIntent)
        .setShowWhen(false)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setPriority(NotificationCompat.PRIORITY_MIN)
        .build()
}
```

{% hint style="info" %}
You can quickly test the notification appearance by running a fresh app/SDK installation on an Android 8+ device. The notification will appear the first time the SDK starts automatic detections, or when you manually start a trip (more about this [here](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/android/broken-reference/README.md)).
{% endhint %}


# Supported API Levels

<table><thead><tr><th width="172.33333333333331">SDK Version</th><th align="center">Minimum API Level (Compilation)</th><th align="center">Required API Level for Detections</th></tr></thead><tbody><tr><td>4.0.0 - 4.14.0</td><td align="center">14</td><td align="center">14+</td></tr><tr><td>4.16.0 - 4.22.x</td><td align="center">14</td><td align="center">21+</td></tr><tr><td>6.0.0 - 6.10.1</td><td align="center">23</td><td align="center">23+</td></tr><tr><td>6.11.0 -</td><td align="center">24</td><td align="center">24+</td></tr></tbody></table>

If your app supports an API level that is lower than the minimum specified above, you can check out [this troubleshooting guide](https://docs.sentiance.com/important-topics/troubleshooting/android#manifest-merger-failed-uses-sdk-minsdkversion-x-cannot-be-smaller-than-version-y-declared-in-library) to learn how to work around the limitation.

When the SDK is initialized on an Android version that has a lower API level than the one specified in **Required API Level for Detections**, initialization will fail with reason `UNSUPPORTED_OS_VERSION`.


# Android Wake Lock Usage

## Why the Sentiance SDK Uses Wake Locks <a href="#why-the-sentiance-sdk-uses-wake-locks" id="why-the-sentiance-sdk-uses-wake-locks"></a>

The Sentiance SDK delivers contextual intelligence by collecting motion data, like accelerometer and gyro readings, in the background. To achieve this, Android requires the device to remain awake. Wake locks ensure that during short periods of motion-triggered data collection, the device doesn’t sleep, allowing us to deliver accurate insights.

## Google Play Vitals Warning <a href="#google-play-vitals-warning" id="google-play-vitals-warning"></a>

<figure><img src="/files/8Q5cGwgWeQODSHYqbTFR" alt=""><figcaption></figcaption></figure>

Google Play monitors wake lock usage. If more than 5% of users have wake locks exceeding two hours over a rolling 28-day window, a battery usage warning may appear. The Sentiance SDK is designed to minimize wake lock usage. Wake locks are held only when motion is detected, and once data collection completes, the lock is released. As users move, wake lock duration varies, but it remains purpose-driven.

## Our Commitment

We prioritize both user experience and battery life. Wake locks are held only when needed, during motion-based data collection. We rigorously test the SDK to ensure wake lock usage stays within expected, minimal boundaries before release.


# iOS


# iOS Region Monitoring

The Sentiance SDK uses the [region monitoring](https://developer.apple.com/documentation/corelocation/monitoring-the-user-s-proximity-to-geographic-regions) feature provided by iOS. There is currently a per-app limit of 20 regions that can be monitored at any point in time. This limit is enforced by iOS.

For proper operation and detections, please ensure that the Sentiance SDK can monitor **2 regions** at any given time.


# App Store Release & Privacy

## App Release

When releasing an app that includes the Sentiance SDK, it is required to inform your users that the use of background locations can have an impact on the device's battery life.

When submitting an app for approval on the App Store, you must include the following message in the app description:

> Caution: continued use of GPS running in the background can dramatically decrease battery life.

## Privacy Section

The iOS App Store requires developers to include details about the data they capture and what they do with it, in the app's privacy section.

> "With the release of iOS/iPadOS 14.3 on Dec. 2020, any new or updated app must include a privacy label, otherwise it won't be allowed on the App Store. This requirement applies not just to third-party apps but to Apple's own programs, such as Apple Music, Apple TV, and Apple Wallet, though built-in apps aren't included."

#### Where to start?

{% hint style="warning" %}
Being transparent about the data you capture is important to your users. We recommend you to keep you and your team informed and up-to-date with current and future changes to the App Store policies.
{% endhint %}

Some useful links to get started:\
[App Store - Privacy Details](https://developer.apple.com/app-store/app-privacy-details/)\
[App Store - Privacy and user data use](https://developer.apple.com/app-store/user-privacy-and-data-use/)\
[App Store - Protecting the users' privacy](https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy)\
[App Store - Review](https://developer.apple.com/app-store/review/)

#### Data captured by our SDK

What follows will focus solely on the Sentiance SDK in relation to the new Apple privacy policy. It is important to stress that your privacy section should still be adjusted to the data captured by your specific app.

{% tabs %}
{% tab title="Health & Fitness" %}

* Used for Analytics
* Used for tracking purposes

If User Linking is **enabled:**

* Linked to the user's identity
  {% endtab %}

{% tab title="Location" %}

* Used for Analytics
* Used for tracking purposes

If User Linking is **enabled:**

* Linked to the user's identity
  {% endtab %}

{% tab title="Identifiers" %}
**User ID**

* Used for Analytics
* Used for tracking purposes

If User Linking is **enabled:**

* Linked to the user's identity

**Device ID**

* Used for Analytics
* Used for tracking purposes

If User Linking is **enabled:**

* Linked to the user's identity
  {% endtab %}

{% tab title="Diagnostics" %}
**Crash Data**

* Used for App Functionality
* Linked to the user's identity (if ‘User Linking’ is enabled).

**Performance Data**

* Used for App Functionality
* Linked to the user's identity (if ‘User Linking’ is enabled).

**Other Diagnostic Data**

* Used for App Functionality
* Linked to the user's identity (if ‘User Linking’ is enabled).
  {% endtab %}
  {% endtabs %}

## Apple Privacy Manifest

Our SDK includes its own [privacy manifest file](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files). You can find the contents of that file here as well:

<details>

<summary>SDK Privacy manifest file</summary>

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSPrivacyTracking</key>
	<false/>
	<key>NSPrivacyTrackingDomains</key>
	<array/>
	<key>NSPrivacyCollectedDataTypes</key>
	<array>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeProductInteraction</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeOtherDataTypes</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeOtherDiagnosticData</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypePerformanceData</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeFitness</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeDeviceID</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeCrashData</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypePreciseLocation</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyCollectedDataType</key>
			<string>NSPrivacyCollectedDataTypeCoarseLocation</string>
			<key>NSPrivacyCollectedDataTypeLinked</key>
			<true/>
			<key>NSPrivacyCollectedDataTypeTracking</key>
			<false/>
			<key>NSPrivacyCollectedDataTypePurposes</key>
			<array>
				<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
			</array>
		</dict>
	</array>
	<key>NSPrivacyAccessedAPITypes</key>
	<array>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>CA92.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>E174.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>35F9.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>C617.1</string>
			</array>
		</dict>
	</array>
</dict>
</plist>

```

</details>


# ARM Simulator Support

{% hint style="info" %}
**Deprecated**

Starting from v6.9.0, the Sentiance iOS SDK targets TensorFlowLiteC v2.17.0, which inherently supports ARM simulators. Using this custom-built TensorFlowLiteC framework is therefore no longer required.
{% endhint %}

It's possible to build your Sentiance integrated app for the ARM Simulator (M1/2/3).

The SDK framework includes support for the arm64 simulator architecture, which is the target architecture for the M1 Mac simulator. However, the SDK has a dependency on TensorFlow Lite (TFL) v2.7.0, which does not support arm64 simulators. Support was added in v2.9.1.

To address this limitation, we have prepared a custom TensorFlowLiteC framework which combines different architectures, to make it possible to use the same TensorFlowLiteC framework on devices and simulators. The XCFramework file is composed of:

* TFL frameworks v2.7.0 for armv7 and arm64, for iphoneos.
* TFL frameworks v2.9.1 for x86\_64 and arm64, for iphonesimulator.

You can find the XCFramework in [our repository](https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/TensorFlowLiteC/2.7.0/SENTTensorFlowLiteC.xcframework.zip), along with the [podspec file](https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/TensorFlowLiteC/2.7.0/TensorFlowLiteC.podspec). This custom TFL framework is also bundled in our [umbrella framework](https://docs.sentiance.com/important-topics/sdk/appendix/v6.x-framework-files#umbrella).

To use it with CocoaPods, you can add the following to your Podfile:

```ruby
pod 'TensorFlowLiteC', :podspec => 'https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/TensorFlowLiteC/2.7.0/TensorFlowLiteC.podspec'
```

This will replace the TFL framework referenced from CocoaPods with the custom one.

At the moment, this framework allows you to build and run your Sentiance integrated app on an ARM simulator, however the SDK is not yet fully compatible with TFL v2.9.1, and will therefore not produce meaningful results.


# Dependencies

This page lists the dependencies of the Sentiance iOS SDK. These dependencies are bundled as XCFramework packages within the [umbrella SDK XCframework](https://docs.sentiance.com/important-topics/sdk/appendix/v6.x-framework-files#umbrella), which you can utilize when doing a manual integration. For CocoaPods and Swift Package Manager integrations, they are added as external or additional dependences.

### TensorFlowLiteC v2.7.0

<table><thead><tr><th width="205">Integration Method</th><th>Notes</th></tr></thead><tbody><tr><td>CocoaPods</td><td>The Sentiance SDK podspec references the official Pod hosted on CocoaPods. It does not support ARM simulators. You have the option of using Sentiance's repackaged framework instead.</td></tr><tr><td>SPM</td><td>There is no official Swift package. The Sentiance package references a Sentiance-hosted framework, which is a repackaged version that support ARM simulators too.</td></tr><tr><td>Manual</td><td>A custom repackaged version of the framework is used, which also supports ARM simulators.</td></tr></tbody></table>

### ProtocolBuffers (ObjectiveC) v3.18.3

<table><thead><tr><th width="207">Integration Method</th><th>Notes</th></tr></thead><tbody><tr><td>CocoaPods</td><td>The Sentiance SDK podspec references the official Pod hosted on CocoaPods.</td></tr><tr><td>SPM</td><td>There is no official Swift package. The Sentiance package references a Sentiance-hosted framework v3.18.3-p, which is a packaged version of the <a href="https://github.com/protocolbuffers/protobuf/tree/v3.18.3/objectivec">ProtocolBuffers source code</a>, with an added Apple Privacy Manifest file.</td></tr><tr><td>Manual</td><td>A Sentiance-packaged framework, v3.18.3-p, built from the <a href="https://github.com/protocolbuffers/protobuf/tree/v3.18.3/objectivec">ProtocolBuffers source code</a>, with an added Apple Privacy Manifest file.</td></tr></tbody></table>

### UnzipKit v1.9

<table><thead><tr><th width="209">Integration Method</th><th>Notes</th></tr></thead><tbody><tr><td>CocoaPods</td><td>The Sentiance SDK podspec references the official Pod hosted on CocoaPods.</td></tr><tr><td>SPM</td><td>There is no official Swift package. The Sentiance package references a Sentiance-hosted framework, which is a packaged version of the <a href="https://github.com/abbeycode/UnzipKit/tree/1.9">UnzipKit source code</a>.</td></tr><tr><td>Manual</td><td>A Sentiance-packaged framework, built from the <a href="https://github.com/abbeycode/UnzipKit/tree/1.9">UnzipKit source code</a>.</td></tr></tbody></table>

### Other Frameworks

Apart from the frameworks mentioned above, all variants of the Sentiance SDK XCFramework include *mpde.xcframework* and *dskoball.xcframework* under the framework's *External* directory\_,\_ which must always be added to your project during manual integrations (done automatically for CocoaPods and SPM integrations).


# iOS 13 permission changes

Please refer to the document below in order to get a high-level overview about the changes iOS 13 brings to location permissions.

{% file src="/files/tlWxrwcaHR8CWXHLqyfc" %}

For more technical details on how the location framework transitions from iOS 12 to iOS 13, read our in-depth assessment below.

{% file src="/files/HBEG1coWPYfvLOYgkths" %}


# Supported iOS Versions & Architectures

| SDK Version     | Minimum iOS Version | Supported Architectures       |
| --------------- | ------------------- | ----------------------------- |
| 5.0.0 - 5.14.1  | iOS 9.0             | arm64 armv7 i386 x86\_64      |
| 5.15.0          | iOS 9.0             | arm64 x86\_64 arm64-sim       |
| 5.15.1 - 5.15.x | iOS 9.0             | arm64 armv7 x86\_64 arm64-sim |
| 6.x             | iOS 13.0\*          | arm64 x86\_64 arm64-sim       |

\*You can target iOS 12.0 in your build settings, however, the SDK will run only on iOS 13.0 and above.


# v6.x Framework Files

On this page, you'll find links to the v6.x SDK framework files. We provide the following variants of the SDK framework:

**Umbrella**

An SDK framework that additionally includes all its dependency frameworks within it. This variant is used for manual integrations and referenced by our Carthage spec.

You can download the Umbrella framework by using the following URL, after replacing the version markers with the desired SDK version:

```
https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/<version>/SENTSDK-<version>.xcframework.zip
```

For example:

> <https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/**6.8.0**/SENTSDK-**6.8.0**.xcframework.zip>

**Thin**

An SDK framework that additionally includes other Sentiance-built dependency frameworks within it. This variant is referenced by the SDK's CocoaPods spec. Additional dependencies are defined in the spec as CocoaPods dependencies.

You can download the Thin framework by using the following URL, after replacing the version markers with the desired SDK version:

```
https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/<version>/SENTSDK-thin-<version>.xcframework.zip
```

For example:

> <https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/**6.8.0**/SENTSDK-thin-**6.8.0**.xcframework.zip>

**SPM**

An SDK framework that does not include other frameworks within it. This variant is referenced by our Swift Package. Dependency frameworks are included in the Swift Package definition as binary targets, or dependency Swift Packages (if supported).

You can download the SPM framework by using the following URL, after replacing the version markers with the desired SDK version:

```
https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/<version>/SENTSDK-spm-<version>.xcframework.zip
```

For example:

> <https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/**6.8.0**/SENTSDK-spm-**6.8.0**.xcframework.zip>

**No CallKit**

Similar to the Umbrella framework, but excludes CallKit. This variant is intended to be used when making your app available in territories where CallKit usage is not allowed. With this variant, some features, such as distracted driving detection, may be impacted.

You can download the No CallKit framework by using the following URL, after replacing the version markers with the desired SDK version:

```
https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/<version>/SENTSDK-noCK-<version>.xcframework.zip
```

For example:

> <https://sentiance-u1-sdk-downloads.s3.eu-west-1.amazonaws.com/ios/frameworks/SENTSDK/**6.8.0**/SENTSDK-noCK-**6.8.0**.xcframework.zip>


# Flutter


# Declaring 3rd Party Plugins

Chances are, your app has other dependencies than just the Sentiance SDKs.

In case you needed to use a certain dependency/plugin inside your listener registration callbacks, you need to let the Sentiance SDK know so that its usage possible. Without it, you will not be able to utilize other plugins inside the Dart callback.

Consider the example below:

{% code title="background.dart" lineNumbers="true" %}

```dart
import 'package:sentiance_core/sentiance_core.dart';
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

@pragma('vm:entry-point')
Future<void> registerEventTimelineListener() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SentianceCore.ensureInitialized(); // Make sure the SDK is initialized

  final sqliteDatabase = sqflite.openDatabase(...);
  final corePlugin = SentianceCore();
  final eventTimelinePlugin = SentianceEventTimeline();

  SentianceEventTimeline.registerEventTimelineUpdateListener((timelineEvent) async {
    final userId = await corePlugin.getUserId();
    eventTimelinePlugin.setTransportTags({"tag": "value"});
  });
}
```

{% endcode %}

The call on line **10** will fail unless you register the [**sqflite**](https://pub.dev/packages/sqflite) plugin. However, the calls on line **11** and **12** will work out of the box without you having to register the [**core**](https://pub.dev/packages/sentiance_core) and [**event timeline**](https://pub.dev/packages/sentiance_event_timeline) plugins since the Sentiance SDK takes care of registering them for you on the Flutter engine that is currently running this Dart function.&#x20;

To register the sqflite plugin, follow the recommended approach as follows by registering the plugin individually:

{% tabs %}
{% tab title="iOS" %}
{% code title="AppDelegate.swift" %}

```swift
import Flutter
import UIKit
import sentiance_core
import sentiance_event_timeline
import sqflite_darwin

@main
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // other code

        // Initialize the Sentiance SDK first
        SentianceCorePlugin.shared.initializeAsync(launchOptions: launchOptions) { result, error in
            if result != nil {
                // Replace '<your_app_package_name>' with the name of your app package
                // as shown inside your project's pubspec.yaml file
                let libraryURI = "package:<your_app_package_name>/background.dart"
                let dartCallbackName = "registerEventTimelineListener"

                SentianceEventTimelinePlugin.initializeListener(
                    withEntryPoint: dartCallbackName,
                    libraryURI: libraryURI,
                    includeProvisionalEvents: true
                ) { flutterEngine in
                    // flutterEngine is the environment where your Dart callback runs
                    SqflitePlugin.register(with: flutterEngine.registrar(forPlugin: "sqflite")!)
                }
            } else {
                print("Sentiance SDK initialization failed, reason: \(String(describing: error?.failureReason))")
            }
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

```

{% endcode %}
{% endtab %}

{% tab title="Android" %}
{% code title="MainApplication.kt" %}

```kotlin
import android.app.Application
import com.sentiance.core_plugin.CorePlugin
import com.sentiance.event_timeline_plugin.EventTimelinePlugin
import com.tekartik.sqflite.SqflitePlugin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Initialize the Sentiance SDK first
        CorePlugin.initializeAsync(this)
            .addOnSuccessListener {
                val dartLibrary = "package:<your_app_package_name>/background.dart"
                val dartCallbackName = "registerEventTimelineListener"

                EventTimelinePlugin.initializeListener(
                    context = this,
                    dartEntryPointLibrary = dartLibrary,
                    dartEntryPointFunctionName = dartCallbackName,
                    includeProvisionalEvents = true
                ) { flutterEngine ->
                    // flutterEngine is the environment where your Dart callback runs
                    flutterEngine.plugins.apply {
                        add(SqflitePlugin())
                        // add here any other non-Sentiance plugin that you intend to use
                    }
                }
            }

        // Other code
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

You can also choose to register all the plugins of your app instead, if that would be more practical. Please note that this **will result** in the SDK logging a warning that the Sentiance plugins you registered (via `GeneratedPluginRegistrant`) are already auto-registered on every Sentiance background engine, so registering them yourself is no longer needed.&#x20;

{% tabs %}
{% tab title="iOS" %}
{% code title="AppDelegate.swift" %}

```swift
import Flutter
import UIKit
import sentiance_core
import sentiance_event_timeline

@main
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // other code

        // Initialize the Sentiance SDK first
        SentianceCorePlugin.shared.initializeAsync(launchOptions: launchOptions) { result, error in
            if result != nil {
                // Replace '<your_app_package_name>' with the name of your app package
                // as shown inside your project's pubspec.yaml file
                let libraryURI = "package:<your_app_package_name>/background.dart"
                let dartCallbackName = "registerEventTimelineListener"

                SentianceEventTimelinePlugin.initializeListener(
                    withEntryPoint: dartCallbackName,
                    libraryURI: libraryURI,
                    includeProvisionalEvents: true
                ) { flutterEngine in
                    // flutterEngine is the environment where your Dart callback runs

                    // GeneratedPluginRegistrant is generated by the Flutter tool
                    // available under the GeneratedPluginRegistrant.h header file, and
                    // allows you to register all of your app's plugins with a single flutter engine
                    GeneratedPluginRegistrant.register(with: flutterEngine)
                }
            } else {
                print("Sentiance SDK initialization failed, reason: \(String(describing: error?.failureReason))")
            }
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Android" %}
{% code title="MainApplication.kt" %}

```kotlin
import android.app.Application
import com.sentiance.core_plugin.CorePlugin
import com.sentiance.event_timeline_plugin.EventTimelinePlugin
import io.flutter.plugins.GeneratedPluginRegistrant

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Initialize the Sentiance SDK first
        CorePlugin.initializeAsync(this)
            .addOnSuccessListener {
                val dartLibrary = "package:<your_app_package_name>/background.dart"
                val dartCallbackName = "registerEventTimelineListener"

                EventTimelinePlugin.initializeListener(
                    context = this,
                    dartEntryPointLibrary = dartLibrary,
                    dartEntryPointFunctionName = dartCallbackName,
                    includeProvisionalEvents = true
                ) { flutterEngine ->
                    // flutterEngine is the environment where your Dart callback runs

                    // The GeneratedPluginRegistrant class is generated by the Flutter tool
                    // and allows you to register all of your app's plugins with a single flutter engine
                    GeneratedPluginRegistrant.registerWith(flutterEngine)
                }
            }

        // Other code
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

That's it. Your listener is now set up to get updates from the Sentiance SDK in the background, and can work side-by-side with other plugins that your app requires.


# SDK Initialization

### What Does Initialization Do? <a href="#what-does-initialization-do" id="what-does-initialization-do"></a>

Initialization prepares the SDK to perform detections in the background.

If no Sentiance user exists on the device, initialization sets up the internal components required to create a user at a later stage.

If a Sentiance user is already present, the SDK initializes all components needed for detections, networking, and maintenance. Additionally, if detections were previously enabled, they are automatically resumed in the background without requiring any app intervention.

### Why Initialize in the Application / AppDelegate Class? <a href="#why-initialize-in-the-application-appdelegate-class" id="why-initialize-in-the-application-appdelegate-class"></a>

During initialization, in addition to setting up internal components, the SDK registers the required delegates and listeners at the OS level. This enables it to receive system-generated events such as geofence exits, significant location changes, and visits.

When such an event occurs and the app is not running, the OS launches it in the background and expects these delegates and listeners to be set during the **startup phase** before delivering the event. To avoid missing events, the SDK must also complete its setup during this phase. For this reason, the SDK must be initialized as part of app startup.

{% hint style="info" %}
On iOS, during initialization, the SDK not only sets up delegates but also registers its background processing tasks with the OS. This registration must happen during the app’s startup phase; otherwise, these tasks will not be scheduled or executed.\
\
On iOS, the startup phase ends when `application(_:didFinishLaunchingWithOptions:)` returns. On Android, it ends when `Application.onCreate()` returns.
{% endhint %}

Note that app startup logic runs on the main thread. If SDK initialization is offloaded to a background thread, there is no guarantee it will complete before the startup phase ends. It is therefore important to perform initialization on the main thread. The SDK is designed to initialize quickly and should not noticeably delay app startup.

### Initialize On Every Startup, Once <a href="#initialize-on-every-startup-once" id="initialize-on-every-startup-once"></a>

You must initialize the SDK on every app startup. In most cases, initialization only needs to happen once during the app process lifetime. Any subsequent initialization attempts will fail unless the SDK has been reset.

Before a Sentiance user is created, it is technically possible to call initialization multiple times, for example to override previously provided options. However, once a user has been created, any further initialization attempts within the same app lifecycle will fail.

To verify whether the SDK has already been initialized, check the `initState` property of the `Sentiance` class.

### SDK Reset

To remove all SDK data and user-related information from a device, call the `.reset()` method on the SDK’s core instance.

This will remove:

* Stored SDK data
* Settings and configurations
* Detection data
* All user-related data on the device

{% hint style="warning" %}
**After a reset:**

This method will remove the Sentiance user from the device and will require re-authentication. \
The user is removed from the device, but **not necessarily from the Sentiance platform**
{% endhint %}

**Important Notes on Reset**

* During an ongoing reset operation, other SDK method calls may be ignored, fail, or return default values
* Ensure the app remains in the foreground during the reset process (e.g., an activity is open or a foreground service is running) to avoid interruption. The SDK cannot guarantee completion if the app is backgrounded or terminated
* The `reset()` method can be called even if the SDK has not been initialized


# Real-Time Listeners

## Overview

Instead of querying the SDK for data or insights, you can subscribe to listeners to receive notifications whenever specific events occur or when insights are processed and available. This is done through **listeners**.

Listeners can operate:

* **In the foreground** – while the app is open and visible.
* **In the background** – even when the app is closed or the screen is turned off.

Background listeners are available for the following main features:

* **Driving Insights** \
  Notifies when a transport is fully processed and driving insights are available.
* **Lifestyle Insights**\
  Notifies when the user's current context changes (e.g. a new segment is detected, the user's venue is detected (such as home or work), or the user transitions between stationary and transport).
* **Mobility Insights**\
  Notifies when a new timeline event is detected (e.g., stationary, transport).
* **Smart Geofences**\
  Notifies when the user enters or exits a place of interest.
* **Crash Detection**\
  Near real-time notification when a crash is detected, including crash details.
* **SDK Status Changes**\
  Notifies when the SDK's status changes (permissions, location availability, detection status, etc.).

{% hint style="info" %}
Listeners should be subscribed **as early as possible during app startup**, ideally in the **main thread**, and after a successful SDK initialization. This ensures that your app does not miss any notifications.
{% endhint %}

## Platform Configuration

When implementing background listeners, you must include the required platform dependencies and perform the necessary initialization in the native code of your application.&#x20;

Refer to the code examples below to understand how to register and initialize background listeners. In the following example, we use the Event Timeline listener, but the same workflow applies to the other background listeners.

<details>

<summary><strong>iOS</strong></summary>

The dependecies are already included in the SDK module, so no new imports are nessesary.

1. To register, set: `Sentiance.shared.eventTimelineDelegate = yourDelegate`

   This delegate will trigger the logic defined in the `onEventTimelineUpdate(e)` method of your delegate class.
2. Make sure to implement the necessary actions inside the onEventTimelineUpdate method.

{% code expandable="true" %}

```swift
public class EventTimelineUpdateReceiver: EventTimelineDelegate {
    init() {
        ...
    }
    
    public func listenForUpdates() {
        // Set the delegate to start listening for timeline updates.
        Sentiance.shared.eventTimelineDelegate = self
    }
    
    public func onEventTimelineUpdate(event: SENTTimelineEvent) {
        // Note: this delegate method invocation happens on the main/UI thread.
        // Logic to run when notified
    }
    
    // Unset the delegate to stop receiving updates
    public func stopListening() {
        Sentiance.shared.eventTimelineDelegate = nil
    }
}
```

{% endcode %}

</details>

<details>

<summary><strong>Android</strong></summary>

1. Include the nessesary dependecies in your app module's **build.gradle** file

{% code expandable="true" %}

```kotlin
// example for EventTimeline Listener:
dependencies {
    implementation(platform('com.sentiance:sdk-bom:<sentiance-version>'))
    implementation('com.sentiance:sdk-event-timeline')
}
```

{% endcode %}

2. Implement the listeners using the **setTimelineUpdateListener** method.

```kotlin
public class EventTimelineUpdateReceiver(val context: Context) {
    private val api = EventTimelineApi.getInstance(context.applicationContext)
    
    init {
        ...
    }

    fun listenForUpdates() {
        api.setTimelineUpdateListener { event ->
            // Note: this invocation happens on the main/UI thread.
           // logic to run when event is detected.
        }
    }
    
    // Stop listening for updates
    fun stopListening() {
        api.setTimelineUpdateListener(null)
    }
}
```

</details>

<details>

<summary><strong>React Native</strong></summary>

To get the listener to work when your app is in the background, place the code inside your app's entrypoint (ex. **index.js / index.ts**). if you are only interested in the listener when the app is in the foreground, you can place the logic in the appropriate UI code instead.

{% code expandable="true" %}

```typescript
import EventTimelineApi from '@sentiance-react-native/event-timeline';

// Make sure the SDK is initialized by awaiting SentianceCore.ensureInitialized()
// before you call this function.
async function startListening(){
    const suscribtion = await EventTimelineApi.addTimelineUpdateListener( 
        async (event)=> {
            // logic to run when event is detected.
        } 
    )
}

async function stopListening(){
    // pass null as the argument to stop listenning.
    EventTimelineApi.addTimelineUpdateListener(null)
}

```

{% endcode %}

</details>

<details>

<summary><strong>Flutter</strong></summary>

To enable background listeners in Flutter, you need to subscribe to them through a Dart VM entry point. This creates a separate background process that can run independently from the main application logic.

Keep in mind that this background process has its own memory space and cannot directly communicate with the main process. To exchange data between them, you must set up communication channels such as `ReceivePort`/`SendPort`.

Additionally, some initialization code is required for background functions to work correctly.

#### Recommended Approach

1. Create a `background.dart` file\
   This file will contain your listeners and `vm:entry-point` functions
2. Import the required dependencies\
   For example: import SentianceEventTimeline
3. Define a Dart VM entry point\
   create a function and annotate the function with `@pragma('vm:entry-point')` this will tell the OS that the function can be executed in a separate background isolate
4. Subscribe to listeners\
   Within the entry point function, subscribe to the appropriate listener and define the logic that should run when an event is detected.

{% code title="background.dart" expandable="true" %}

```dart
...
import 'package:flutter/material.dart';
import 'package:sentiance_core/sentiance_core.dart';
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';

@pragma('vm:entry-point')
void registerEventTimelineListener() async {
    WidgetFlutterBinding.ensureInitialized();
    await SentianceCore.ensureInitialized(); // Make sure the SDK is initialized
    
    SentianceEventTimeline.registerEventTimelineUpdateListener((event){
        // Logic to run when event is detected
    })
}
```

{% endcode %}

{% hint style="info" %}
Make sure to import `background.dart` in your `main.dart` file. This guarantees that the Dart VM entry point and related background code are retained and included in the final compiled binary.
{% endhint %}

#### Register the listener in Native code

{% hint style="info" %}
Every Sentiance plugin your app depends on is registered automatically on the background engine, so you can call any Sentiance API from inside your listener. checkout [this page](/sdk/appendix/flutter/declaring-3rd-party-plugins) if your callback also uses **non-Sentiance** plugins, as they need to be properly initialized and registered **together** with the Sentiance Background Listener.
{% endhint %}

**iOS**

* Initialize the listener in the `AppDelegate` class.
* Point it to the Dart VM entry point method.
* Add the following code inside your AppDelegate.swift file:

{% code title="AppDelegate Class" expandable="true" %}

```swift
import Flutter
import UIKit
import sentiance_core
import sentiance_event_timeline

@main
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // other code

        SentianceCorePlugin.shared.initializeAsync(launchOptions: launchOptions) { result, error in
            if result != nil {
                // Replace '<your_app_package_name>' with the name of your app package
                // as shown inside your project's pubspec.yaml file
                let libraryURI = "package:<your_app_package_name>/background.dart"
                let dartCallbackName = "registerEventTimelineListener"

                SentianceEventTimelinePlugin.initializeListener(
                    withEntryPoint: dartCallbackName,
                    libraryURI: libraryURI,
                    includeProvisionalEvents: true
                )
            }
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

```

{% endcode %}

**Android**

* Initialize the listener in your `Application` class.
* Point it to the Dart VM entry point method.
* Create an Application class (if you haven't already) and add the following code inside:

{% code title="Application Class" expandable="true" %}

```kotlin
import android.app.Application
import com.sentiance.event_timeline_plugin.EventTimelinePlugin

// Don't forget to update your app's manifest:
// <application android:name=".MainApplication" ...></application>
class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Initialize the Sentiance SDK first
        CorePlugin.initializeAsync(this)
            .addOnSuccessListener {
                // Replace '<your_app_package_name>' with the name of your app package
                // as shown inside your project's pubspec.yaml file
                val dartLibrary = "package:<your_app_package_name>/background.dart"
                // Make sure the callback name specified here matches the name of the
                // callback you previously defined in your Dart code
                val dartCallbackName = "registerEventTimelineListener"

                EventTimelinePlugin.initializeListener(
                    context = this,
                    dartEntryPointLibrary = dartLibrary,
                    dartEntryPointFunctionName = dartCallbackName,
                    includeProvisionalEvents = true
                )
            }

        // Other code
    }
}
```

{% endcode %}

</details>


# User Creation and Management

For the SDK to perform detections and deliver valuable insights, a Sentiance user must first be created. Each Sentiance user is linked to a unique user in your system. When creating a Sentiance user, you must provide an identifier that uniquely represents the user on your side.

Each Sentiance app environment is completely segregated, meaning no data or SDK users can be shared across environments or Sentiance apps. The SDK on the user's device is linked to a specific Sentiance app environment during user creation, so the API key you use determines the environment.

{% hint style="warning" %}
Always use the API key matching the intended Sentiance environment when creating a SDK user.
{% endhint %}

Linking a user requires a server-to-server interaction. The Sentiance SDK does not accept your system's user identifier directly from the app, since a mobile app is not considered a trusted source. Instead, your backend must send the unique user identifier to Sentiance, authorized using a Sentiance API key.

If the provided unique user identifier is already linked to an existing Sentiance user in the target environment, that user is restored on the device. Otherwise, a new Sentiance user is created.

{% stepper %}
{% step %}

### User Creation flow

You provide to us your unique user identifier when making an authentication code request. In return, Sentiance will provide to you a short-lived authentication code, which you will then use to create a Sentiance user on the device.

The steps are as follows:

1. When decided to create a Sentiance user, contact your backend and request a Sentiance authentication code.
2. On your backend, create a corresponding request towards the Sentiance backend, which will include your user's unique identifier. This request is authenticated using a Sentiance [API Key](/sdk/appendix/request-authentication).
3. Retrieve the authentication code provided by Sentiance and forward it to your app.
4. In the app, create a Sentiance user by passing this authentication code.

Check out [User Creation](/getting-started/sdk-integration/4.-user-creation) for detailed instructions

{% endstep %}

{% step %}

### Switching a user to a different environment

Linking happens only at user creation, there is no method to move an existing user between environments. To switch a user to a different environment, they must go through the user creation flow again, using the target environment's API key when requesting an authentication code from your backend.

Only one SDK user can exist per app instance at a time. If a user is already active and authenticated on the device, attempting to create a new one will fail. To free up the instance for a new user, call the SDK's `reset` method first. This clears all SDK data and removes the current user instance from the device, freeing it for a new user to be authenticated.

{% hint style="info" %}
**The core rule**

No new SDK user can be created while a user instance is still active in the same app instance, the existing one must be cleared first.
{% endhint %}
{% endstep %}

{% step %}

### Multi-Device support

A Sentiance user can only be active on one device at a time.

If a user is already authenticated on one device and authenticates on another, the new device takes priority, and the user will be deactivated on the previous device.

The only exception to this is when the existing active user instance is on a mobile phone, and you attempt to restore the user on a tablet device. In this case, Sentiance will prioritize the mobile phone , and keep the user active.

When a user is deactivated:

* The SDK stops performing detections
* The user must re-authenticate before detections can resume

{% endstep %}

{% step %}

### User Deletion

#### From the Device

To remove all SDK data and user-related information from a device, call the `reset()` method on the SDK’s core instance.

This will remove:

* Stored SDK data
* Settings and configurations
* Detection data
* All user-related data on the device

**Important Notes**

* During an ongoing reset operation, other SDK method calls may be ignored, fail, or return default values.
* Make sure the app stays in the foreground while the reset is in progress, for example by keeping an activity open or a foreground service running. If the app is backgrounded or terminated, the SDK cannot guarantee that the reset will complete successfully.
* The `reset()` method can be called even if the SDK has not been initialized.

#### From the Sentiance Platform

Deleting a user from the Sentiance platform must be done via server-to-server communication.

User Deletion can be achieved by using the [delete\_user](https://graphqldocs.sentiance.com/#mutation-delete_user) mutation. An [API Key](/sdk/appendix/request-authentication#api-keys) with the [USER\_DELETE](/sdk/appendix/request-authentication) scope must be used to call the deletion mutation.

When initiating a deletion request, a `request_id` is returned in the response. This ID is intended for debugging purposes only and can be shared with Sentiance support if needed.

After the deletion request has been successfully completed, you should [reset the SDK on the user’s device](#from-the-device). Resetting the SDK will:

* Stop all SDK operations
* Remove all user-related data from the device
  {% endstep %}
  {% endstepper %}


# Request Authentication

Certain actions require communication to the Sentiance Backend. to perform these request an API Key with the required permissions must be included in the the Authorization header if the request.

```
Authorization: Bearer e5c3b842231543f.mGCUhfi0uI4J13k010V49D2GaBZ3j1E708X4a4396XNx48X3
```

An `Authorization` header with value `Bearer <token>` authenticates and authorizes your request. The token can either be an API Key or an SDK User Token. see below on how these are created and what they give you access to.

{% stepper %}
{% step %}

### SDK User Token

SDK User Tokens have access to all queries and mutations available in a single user context. this token can be generated in the app by requesting this in the SDK instance like below.&#x20;

{% tabs %}
{% tab title="iOS" %}

```swift
 Sentiance.shared.requestUserAccessToken { result, error in
    if let result = result {
        print("Token: \(result.token)")
    }
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
sentiance.requestUserAccessToken().addOnCompleteListener { operation ->
    if (operation.isSuccessful) {
        val token = operation.result
    } else {
        val error = operation.error
        Log.e(TAG, "Failed to get access token, reason ${error.reason.name}.")
    }
}
```

{% endtab %}

{% tab title="React Native" %}

{% endtab %}

{% tab title="Flutter" %}

```dart
try {
  final tokenResult = await requestAccessToken(options);
  // 
} on PlatformException catch(e) {
  // An error occurs when communicating with the host platform.
} on UserAccessTokenError catch(e) {
  // Failed to request a user access token, find out more about the reason why
  final reason = e.reason;
}
```

{% endtab %}
{% endtabs %}

The token is valid for a limited time (several days). Generally, requesting a token from the SDK will complete instantly by returning a cached token. But if a new one has to be obtained from the Sentiance API, there is a possibility that it will fail (e.g. no network connection).
{% endstep %}

{% step %}

### **API Keys**

API Keys can access all queries and mutations for the App to which they belong. These are secure, revocable, and scope-restricted credentials used for user registration, data queries to the Sentiance GraphQL, user deletion and any other backend-to-backend operation.&#x20;

Users with Developer permissions on a Sentiance App can create API Keys in [ICT](/getting-started/insights-control-tower/developer-dashboard/api-keys) by specifying a name, scope, and expiry date. Each key is disclosed **only once** to its creator so it must be securely stored immediately. While only the creator initially sees the key, any user with Developer permissions on the App can later revoke it.

{% hint style="info" %}
Management of API Keys is done in ICT and requires an account with Developer permissions
{% endhint %}

**Expiry Period**

For increased security, API Keys are self-expiring. The expiry time is 1 year from the time of creation. After 1 year, the old API Key will stop working and a new one will have to be created. We allow up to 10 active API Keys at any given time, per app. An active key is one that hasn't been revoked or expired.

**Permission scopes**

Scopes allow you to specify what operations an API Key can perform. A single key must have at least 1 permission scopes but is allowed to have multiple. We always strongly recommend to set as minimal scopes for a api key for security reasons, best is to use multiple api keys with different scopes instead of 1 api key with many scopes

<table><thead><tr><th width="282.80029296875">Scope name</th><th>Scope description</th></tr></thead><tbody><tr><td><strong>USER_READ</strong></td><td>Use this scope to read user data. This scope should be used with the <a href="/pages/q7hoEfrbuqI9aq83fm1y">Sentiance GraphQL</a></td></tr><tr><td><strong>USER_DELETE</strong></td><td>Use this scope to delete a user along with all historical data.</td></tr><tr><td><strong>USER_LINK</strong></td><td>Use this scope to perform <br>User Creation &#x26; Authentication.</td></tr><tr><td><strong>OFFLOADS_READ</strong></td><td>Use this scope to list Offloads available for download.</td></tr><tr><td><strong>OFFLOADS_GENERATE_URL</strong></td><td>Use this scope to generate URLs at which offloads can be downloaded.</td></tr><tr><td><strong>FAKE_DATA_INSERT</strong></td><td>Use this scope to inject fake data.<br>Engagement platform only!</td></tr></tbody></table>
{% endstep %}
{% endstepper %}


# Data Sychronization

Within the Sentiance platform, there are different data synchronization methodologies. \
When we refer to **data synchronization**, we mean the process of:\
Collecting data detected on the user’s device by the Sentiance SDK and making it available across different systems. Both synchronization methods serve different use cases and are implemented in different ways.<br>

{% stepper %}
{% step %}

## Sentiance Platform (SDK Data Syncing)

When you opt in to data visualization on the Sentiance platform, the SDK enables data to be collected from the device and synched to the Sentiance Backend. This method allows visiualization of insights and analytics in your[ Insights Control Tower](/getting-started/insights-control-tower)

This includes insights such as:

* Analytics
* Transports
* Driving insights
* Mobility insights
* User data (such as Device info, SDK info etc...)

#### **How It Works**

* Data is automatically aggregated on the device by the SDK
* Insights are sent in batches to Sentiance backend systems
* Once processed, the data becomes available in **ICT**

⚠️ **Note:**

* Not all ICT dashboards are updated at the same frequency
* Some dashboards update shortly after data arrives
* Others refresh daily at fixed intervals

Users with **Spectator access** in ICT can view and interact with these insights.

#### **Benefits**

Synchronizing data to the Sentiance platform enables:

* Advanced troubleshooting and support
* Continuous improvement of detection models
* Access to analytics and visualization tools

⚠️ **Data retention:** Data on the Sentiance platform is stored for a limited period (approximately 90 days).<br>
{% endstep %}

{% step %}

## Your Backend Systems (Custom Data Syncing)

This method involves synchronizing SDK data with your own backend systems by implementing the necessary logic for data syncronization in your application.

This can be achieved by:

* Using **real-time listeners** provided by the SDK
* Querying the SDK on demand via available APIs

#### **Best Practices**

To ensure reliable and efficient data handling:

* **Process real-time events immediately**\
  Handle incoming data in listener callbacks and ensure all required fields are captured
* **Define your own data structures**\
  Map SDK data to structures that align with your business logic
* **Choose a synchronization strategy**
  * Send data immediately for real-time use cases
  * Temporarily store data localy on the device and sync it to your backend in batches
* **Schedule periodic jobs**\
  Implement background tasks to query the SDK for insights
* **Validate SDK data**\
  Always validate data from SDK before transforming to your data structures and transport
  * Prevents data loss
  * Avoids incorrect mappings to your own data structures
    {% endstep %}
    {% endstepper %}


# Battery Optimization

### Battery usage

Battery consumption greatly depends on enabled features: the more features an app requires, the more data is processed and the more battery power is used. Users who make many trips or very long trips will naturally experience higher battery consumption.

Processing sensor and location data is the primary source of battery consumption. This occurs when the SDK requests data from the device’s sensors and the location services.

A high frequency of sensor and location requests increases the volume and rate of data processing by the SDK, which significantly improves the accuracy of detections performed by the SDK, but also results in more battery usage

Sensor and location data is requested and processed only when the SDK is in an active state. \
Learn more about this in the next section.

### Optimization features

To minimize battery impact from sensor and location processing, multiple optimization techniques are applied that reduce background processing and battery consumption by the SDK.<br>

* When the user is stationary, the SDK enters an idle/sleep state. In this state the SDK is not detecting or processing any data.<br>
* The SDK only wakes up when:
  * A geofence is broken: meaning the user is in significant movement
  * The state of the user transitions from Stationary to in-transport by or automatic trip start.<br>
* After approximately 3 minutes of being stationary, the SDK goes back to sleep and stops active detection until awakened again

### Battery saving by systems

Native battery saving features, such as Low Power Mode and Adaptive Battery on Android, can reduce SDK detection quality.\
\
Sentiance SDK built-in battery optimizations already improve energy efficiency, so we recommend disabling battery-saving features for any app running the Sentiance SDK to ensure optimal performance.

For consistent and reliable detection performance, it is recommended to:

* Inform end users about the restrictions of native battery-saving features of the device
* Explain why disabling certain battery-saving features for the app improves SDK accuracy

See below for the best practices regarding battery optimization

<details>

<summary>iOS</summary>

On iOS, you can’t fully prevent your app from being terminated in the background especially under low battery or system pressure. The OS strictly controls background execution to preserve battery and performance.

* Apps cannot disable Low Power Mode or system battery optimizations
* The system may suspend or terminate your app at any time in the background
* There is no equivalent to Android’s “disable battery optimization”

The Sentiance SDK already has excellent built-in battery optimazation features. and detections are being processed in the most efficient way possible.&#x20;

</details>

<details>

<summary>Android</summary>

Certain Android device manufacturers aggressively restrict battery usage, often prioritizing low battery consumption over background app quality.&#x20;

[Don't Kill My App](https://dontkillmyapp.com/) (no affiliation to Sentiance) keeps track of different manufacturers' practices on battery optimization techniques that severely restrict how your app can run in the background and the potential solutions.

Manufacturers listed below are notorious for their strict requirements for background process restrictions, and without **proper user-onboarding, and correct battery-related settings, Sentiance SDK cannot guarantee a consistent output on these devices.**

* [OnePlus](https://dontkillmyapp.com/oneplus)
* [Huawei](https://dontkillmyapp.com/huawei)
* [Samsung](https://dontkillmyapp.com/samsung)
* [Xiaomi](https://dontkillmyapp.com/xiaomi)
* [Others](https://dontkillmyapp.com/)<br>

### Programmatically Prompting for Battery Optimization Permission

When running the SDK on Android 6 and higher, it is recommended to ask the user to disable battery optimization for your app. This makes sure that SDK detections continue to work properly when the device is in Doze mode. This is particularly important with manufacturers like OnePlus and Nokia (HMD Global) who customize Android to do aggressive battery optimization, keeping the device in Doze mode longer than intended.

Additionally, on Android 9, disabling this will prevent Adaptive Battery from [bucketing](https://developer.android.com/about/versions/pie/power#buckets) your app based on usage and [restricting background processing](https://developer.android.com/reference/android/app/ActivityManager#isBackgroundRestricted\(\)), all of which that can impact the detection quality of the SDK.

After explaining to the user about the benefits of disabling battery optimization, call the [`disableBatteryOptimization()`](broken://pages/P8jG1UGByE3PSarnGTOd#disablebatteryoptimization) method of the [`Sentiance`](broken://pages/P8jG1UGByE3PSarnGTOd) class. This will trigger a system dialog asking the user to allow disabling battery optimization for your app.

```kotlin
Sentiance.getInstance(context).disableBatteryOptimization()
```

The Sentiance SDK does not define the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission required for this feature. You must therefore explicitly add this permission to your app.

{% code title="AndroidManifest.xml" %}

```xml
<uses-permission 
	android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
```

{% endcode %}

{% hint style="danger" %}
Please note that Google Play policies prohibit apps from requesting this permission unless the app's core functionality is affected. For more information about the supported use cases, see [here](https://developer.android.com/training/monitoring-device-state/doze-standby#exemption-cases).
{% endhint %}

{% hint style="success" %}
Please take advantage of [SDK Status](broken://pages/rgc1AKjbW9IZXD93CFC9) updates to react to any incorrect configurations.
{% endhint %}

### Embeddable Projects

#### (These are not maintained by Sentiance)

{% embed url="<https://github.com/DoubleDotLabs/doki>" %}

{% embed url="<https://github.com/judemanutd/AutoStarter>" %}

</details>


# Custom User Metadata

Custom metadata allows you to store text-based key-value user properties into the Sentiance Platform. These keys and values are treated as opaque strings and are meant for retrieval only. There is a limit of 50 properties per user and a limit of 500 characters for keys and values.

Typical examples include custom user and application-related properties you need after the processing.

### Example

Here we will add a single field "correlation\_id" to the metadata, with a value of '3a5276ec-b2b2-4636-b893-eb9a9f014938'.

{% tabs %}
{% tab title="iOS" %}

```swift
Sentiance.shared.addUserMetadataFields(["correlation_id": "3a5276ec-b2b2-4636-b893-eb9a9f014938"])
```

{% endtab %}

{% tab title="Android" %}

```kotlin
sentiance.addUserMetadataField("correlation_id", "3a5276ec-b2b2-4636-b893-eb9a9f014938")
```

{% endtab %}
{% endtabs %}


# Shift Based Detections

This guide explains how to implement shift-based or session-based SDK detection. This guide will help with implementing the logic if you want to fully control the detections for your users, expecially if your bussiness logic requires shift based detections example being:

* You want to only enable detections during the working hours of your users.
* You want to enable detections only during the start and end of order delivery for your drivers
* You want to perform detections only during a chauffeur session for a chauffeur service

### SDK Detection Control

The Sentiance SDK exposes two primary methods to control its detection state:

<table><thead><tr><th width="230.79296875">Method</th><th>Description</th></tr></thead><tbody><tr><td>enableDetections()</td><td>Prepares the SDK for detection. Verifies permissions, sensors, and location services. Does not immediately start data collection.</td></tr><tr><td>disableDetections()</td><td>Stops the SDK from performing any further detections.</td></tr></tbody></table>

Calling enableDetections() puts the SDK into a ready state  it verifies that all required permissions are granted and that the necessary sensors and location services are available. It does not start a trip or trigger any GPS or sensor data collection immediately.

Active data collection only begins once the device's geofence is broken, which typically occurs after approximately 2–3 minutes of movement or when the user has travelled 200–300 meters. This means the user must already be in motion before the SDK starts actively collecting location and sensor data.

{% hint style="info" %}
**enableDetections()** and **disableDetections()** can be triggered programmatically at any point based on your application's business logic.
{% endhint %}

### Identifying & Tagging Transports

A trip may contain multiple transport events if the user switches between transport modes, or if the user stops moving for approximately 3 minutes, which triggers a stationary event. Each transport has a unique ID that can be used to query transports within a specific time range and associate them with a shift or session.

A more reliable approach is to use transport tags. By calling `setTransportTags()` and passing a map or dictionary of your own identifiers such as `driverID`, `sessionID`, or `orderID`  those identifiers will be attached to all subsequent transports until the method is cleared or called again with different values.

Tags can also be applied while a transport is already in progress, as long as it has not yet been finalized by the SDK. Once the transport is completed, the tags will be included in the final `TransportEvent` under the `tags` attribute.

### Example: Shift-Based Detection Flow

The following illustrates a typical implementation of shift-based detection, from the start to the end of a shift

{% stepper %}
{% step %}

### Start shift

1. User taps the "Start shift" button in the application
   1. Log and store current time as the start time of the shift and appoint a shitID
   2. This action should call the enableDetections() to prepare the SDK for detections
2. Call eventTimelineAPI.setTransportTags({"shiftID": "123XXX"}) -> this will attach your custom identifiers to all subsequent Transport Events.

When the SDK will now be able to detect the users transports. This will be done on a automatic basis, so the sdk will detect when the user is in movement and when they stopped moving. after every completed transport, the transports detected details and insights will be made available in the Event Timeline and Driving Insights.&#x20;

The detections will be enabled and active data collection be started and stopped automatically util the disableDetections method is called.

{% endstep %}

{% step %}

### End shift

User taps the "End shift" button in the application

1. Log and store the current time as the end time of the shift&#x20;
2. Call disableDetections() to stop the SDK from performing further detections until manually enabled again.
3. make sure to clear the TransportTags by calling setTransportTags and passing an map value.

{% hint style="info" %}
The SDK will terminate a transport if there was an ongoing transport at the moment. and provide the nessesary details in the EventTimeline and Driving Insights if available.
{% endhint %}
{% endstep %}

{% step %}

{% endstep %}
{% endstepper %}

You can query the SDK for the detected transports that belong to a certain session by querying the EventTimeline and requesting all events that were captured during the start time and end time of a transport session


# Migration Guide

If you've integrated an older version of our SDK and would like to migrate to the latest one, please follow these guides.


# Android

### Migrating from 4.x to 6.x

Version 6.x brings a lot of improvements and new features to the Sentiance SDK, like a simpler API for initialization, user creation, authentication and linking users. Some of these improvements result in breaking changes in the existing integration. This section describes all the changes that were made since version 4.22.1, and provides two code migration paths: a minimal one that utilizes existing (but deprecated) APIs, and a full one that utilizes only the new APIs.

{% hint style="info" %}
Please note that new SDK features and functionality will be supported via the new API methods and classes. As such, you are encouraged to do a full migration, in order to benefit from upcoming features and improvements.
{% endhint %}

#### General Changes

**Minimum Supported Android Version**

We have bumped the minimum supported Android version to 6.0 (Marshmallow—API level 23). If you target an older version of Android, please see [this](https://docs.sentiance.com/important-topics/troubleshooting/android#manifest-merger-failed-uses-sdk-minsdkversion-x-cannot-be-smaller-than-version-y-declared-in-library) troubleshooting guide to learn how to resolve related issues.

**Java 8 Compatibility**

Version 6.x makes use of Java 8 language features. Make sure your app is configured to support Java 8. See [this](https://developer.android.com/studio/write/java8-support#supported_features) guide.

**Proguard Rules**

Version 4.x automatically applied the following rule when compiling your app:

```
-keepattributes SourceFile, LineNumberTable
```

Though beneficial for [troubleshooting crashes](https://developer.android.com/studio/build/shrink-code?authuser=0\&hl=sr-CS\&skip_cache=true#decode-stack-trace) using stack traces, there was no way for developers to omit this rule if desired to do so. In version 6.x, we have omitted this rule from our libraries.

{% hint style="warning" %}
**Important**

If you want to retain debugging information for your app (e.g. to troubleshoot crashes), make sure that this rule exists in your app's own proguard rules file.
{% endhint %}

**Google Play Services Location Library**

We have updated the minimum required version of the Google Play Services location library (com.google.android.gms:play-services-location) to 18.0.0. If your app targets a different version, please note that Gradle will automatically pick the highest one. You can verify which version is being picked by running:

```
$ ./gradlew app:dependencies

...
releaseRuntimeClasspath - Runtime classpath of compilation 'release' (target  (androidJvm)).
+--- com.sentiance:sdk:6.0.0
|    +--- com.google.android.gms:play-services-location:18.0.0
```

You should see something similar to line 6 above. However, if you have explicitly excluded the location library from the Sentiance SDK's transitive dependencies (see the below example), please either omit this exclusion, or update the location library version defined in your app's dependencies.

{% code title="build.gradle" %}

```groovy
// If you have an exclusion rule similar to line 7, either omit it or 
// update your own GMS location library version (line 10).

dependencies {
	implementation ('com.sentiance:sdk:4.19.2@aar') {
		transitive = true
		exclude group: 'com.google.android.gms'
	}
	
	implementation "com.google.android.gms:play-services-location:18.0.0"
}
```

{% endcode %}

After making the above change, run `./gradlew app:dependencies` to verify the chosen library version.

**Android Support Libraries**

As version 4.x targeted an older version of the Google Play Service location library, it brought along with it some old android-support transitive dependencies. With the version 18.0.0, these support libraries have now been replaced with their AndroidX counterparts.

**SDK Artifacts & Dependencies**

Version 6.x breaks down SDK features and functionality into multiple artifacts, each with its own dependencies. Check [this page](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md) to see all the artifacts and dependencies.

{% hint style="warning" %}
If you reference our SDK as a local aar file inside your project, or by hosting it in a private repository, please make sure all of the new dependencies are met, including transitive ones.
{% endhint %}

The core Sentiance functionality is provided by the *com.sentiance:sdk* artifact. This includes operations like creating a Sentiance user, and starting detections. Other library artifacts act as extensions that include additional Sentiance features and functionality. For example, *com.sentiance:sdk-crash-detection* includes the crash detection feature.

To enable these additional features, you need to add the corresponding artifact as a dependency in your app. You can make use of the SDK's bill of materials (BOM) artifact to reference a single version and avoid incompatibility issues:

{% code title="build.gradle" %}

```groovy
dependencies {
    ...
    implementation(platform('com.sentiance:sdk-bom:6.0.0'))
    implementation('com.sentiance:sdk')
    implementation('com.sentiance:sdk-crash-detection')
}
```

{% endcode %}

{% hint style="warning" %}
Some features must be activated by Sentiance before you can start using them. Please reach out to our support team to make sure that the feature you want to use is activated.
{% endhint %}

**Android XML Backup Rules**

The [XML Android backup rules](https://developer.android.com/guide/topics/data/autobackup#IncludingFiles) that are applied by the Sentiance SDK have been updated. If you have overwritten the backup rules in your app's manifest, please update it by including any missing SDK rule. See [this](https://docs.sentiance.com/important-topics/troubleshooting/android#manifest-merger-failed-attribute-fullbackupcontent) troubleshooting guide.

#### Code Changes & Deprecations

We have updated the SDK API methods and classes to make it easier to integrate our SDK into your app. By doing so, we have deprecated some existing API methods and classes. These APIs will still function properly, however we plan to remove them in the next major release.

**User Linking**

The following classes and interfaces under the *com.sentiance.sdk* package have been renamed:

<table><thead><tr><th width="150" align="center">Old Name</th><th align="center">New Name</th></tr></thead><tbody><tr><td align="center">MetaUserLinker</td><td align="center">UserLinker</td></tr><tr><td align="center">MetaUserLinkerAsync</td><td align="center">UserLinkerAsync</td></tr><tr><td align="center">MetaUserLinkerCallback</td><td align="center">UserLinkerCallback</td></tr></tbody></table>

The `setMetaUserLinker` method of the `SdkConfig.Builder` class has been renamed to `setUserLinker`.

**SdkStatus**

The `startStatus` field has been deprecated. The new `detectionStatus` field has been added as its successor.

The boolean status field `isLocationPermGranted` has been removed, and replaced with a new enum field [**locationPermission**](https://docs.sentiance.com/important-topics/api-reference/android/sdkstatus#locationpermission).

**Crash Detection**

Crash detection is now made available via a separate library artifact. You therefore need to add *com.sentiance:sdk-crash-detection* as a dependency to your app, in order to activate the feature.

The new entry point for crash detection related functionality is the `CrashDetectionApi` class. The following methods have therefore been removed from the `Sentiance` class and are now available in the `CrashDetectionApi` class:

* `invokeDummyVehicleCrash` (no signature change)
* `setVehicleCrashListener` (no signature change)
* `isVehicleCrashDetectionSupported` (signature update: the `TripType` parameter has been removed)

Additionally, the following classes and interfaces have been moved to the *com.sentiance.sdk.crashdetection.api* package:

* `VehicleCrashEvent` (the existing properties of this class are no longer nullable).
* `VehicleCrashListener`

Lastly, the following (previously deprecated) API classes and methods have been removed:

* `com.sentiance.sdk.crashdetection.CrashCallback`
* `Sentiance.setCrashCallback(CrashCallback callback)`

**Asynchronous Methods**

Existing asynchronous SDK methods have been deprecated. New asynchronous methods have been added, that return a [`PendingOperation<Result, Error>`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/pendingoperation), which is similar to the [Java Future](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html).

`PendingOperation` allows you to set success, failure, and completion handlers to asynchronously handle an operation's result.

```kotlin
// Enable SDK detections and 
val pendingOperation = sentiance.enableDetections()
    .addOnSuccessListener { result ->
        //...
    }
    .addOnFailureListener { error -> 
        //...
    }
    .addOnCompleteListener { operation ->
        if (operation.isSuccessful) {
            //...
        } else {
            //...
        }
    }
```

Moreover, it offers the possibility to wait for the result up to a certain duration, or until completion.

```kotlin
// Wait 30 seconds
val pendingOperation = sentiance.enableDetections().waitFor(30, TimeUnit.SECONDS)

// Wait till completion
val pendingOperation = sentiance.enableDetections().waitTillCompletion()
```

To learn more about what `PendingOperation` offers, see the API reference page [here](https://docs.sentiance.com/important-topics/sdk/api-reference/android/pendingoperation).

**SDK Initialization & User Creation**

The existing SDK initializer served two purposes:

1. Create a Sentiance user if one does not exist;
2. initialize internal SDK components and resume detections.

We have deprecated the existing initializer `init` in favor of a new method called `initialize`. This new method **does not create a Sentiance user**. Instead, if a Sentiance user already exists, it initializes the SDK internally and resumes detections (if detections were enabled in the past).

To create a Sentiance user, we have added a new method called `createUser`. You can use this method to create a Sentiance user anywhere in your app. Note that you can only create one user at any time. To create a different user, you must first reset the SDK.

Given that user creation is not part of initialization anymore, **we expect apps to initialize the SDK during app startup, without postponing or delaying it**. The new initializer can safely be called when a Sentiance user does not exist yet, as it will only do minimal work to prepare the SDK for user creation.

{% hint style="warning" %}
As long as a Sentiance user exists on the device, you are expected to initialize the SDK during app startup, without delay or postponement. If you intend to not run detections for a brief period, simply call *disableDetections* instead. If you intend to not run detections for a longer duration, simply reset the SDK to remove the user from the device.
{% endhint %}

The deprecated method `isInitialized()` from `Sentiance` has also been removed. Use `getInitState()` instead.

**Checking User Existence & Linking Status**

You can use the new `userExists()` method to find out whether a Sentiance user already exists. And to check whether the user has been successfully linked (i.e. user linking), you can call `isUserLinked()`.

Unlike the other SDK methods, these two methods can be called without needing to initialize the SDK first.

#### **Minimal Migration Steps**

The following steps describe the minimal changes that you have to do, in order to compile and run your app with v6.x of the Sentiance SDK. Depending on your development environment and integration method, you might need to make additional changes that are not mentioned here. If you haven't already done so, please go through the General Changes section in order to get a complete list of the changes that were made in version 6.x.

**1. Add Java 8 Support**

Make sure your app supports Java 8. See [this](https://developer.android.com/studio/write/java8-support#supported_features) guide.

**2. Update the SDK Dependency**

In your app, update the Sentiance SDK dependency to use the BOM artifact, and reference v6.x of the SDK. See SDK Artifacts and Dependencies.

If your app makes use of the vehicle crash detection feature, add the *com.sentiance:sdk-crash-detection* dependency.

**3. Update the Android XML Backup Rules**

If you have specified custom backup rules in your app's manifest file, add the missing SDK rules. See [here](https://docs.sentiance.com/important-topics/troubleshooting/android#manifest-merger-failed-attribute-fullbackupcontent).

**4. Apply Code Changes**

* For user linking, update the class and interface names. See here.
* Replace the SDK status `isLocationPermGranted` field usage with `locationPermission`.
* Replace `isInitialized()` usage with `getInitState()`.
* For vehicle crash detection, update the package name of the imported classes and utilize the `CrashDetectionApi` class to access the crash detection methods with their updated signatures. See here. If you were using the deprecated crash detection methods from the `Sentiance` class, migrate to the new ones.

#### Full Migration Steps

The following steps describe all the changes that you have to do, in order to replace deprecated Sentiance API usage with non-deprecated counterparts, and successfully compile and run your app after updating to v6.x. Depending on your development environment and integration method, you might need to make additional changes that are not mentioned here. If you haven't already done so, please go through the General Changes section in order to get a complete list of the changes that were made in version 6.x.

**1. Add Java 8 Support**

Make sure your app supports Java 8. See [this](https://developer.android.com/studio/write/java8-support#supported_features) guide.

**2. Update the SDK Dependency**

In your app, update the Sentiance SDK dependency to use the BOM artifact, and reference v6.x of the SDK. See SDK Artifacts and Dependencies.

If your app makes use of the vehicle crash detection feature, add the *com.sentiance:sdk-crash-detection* dependency.

**3. Update the Android XML Backup Rules**

If you have specified custom backup rules in your app's manifest file, add the missing SDK rules. See [here](https://docs.sentiance.com/important-topics/troubleshooting/android#manifest-merger-failed-attribute-fullbackupcontent).

**4. Update the SDK Initialization**

In your existing integration, you initialize the Sentiance SDK by calling `init` from within your Application's `onCreate` method. Replace `init` with the new `initialize` method. This method accepts a `SentianceOptions` object which you can create using its `Builder`. You can set a custom Android notification that the SDK can use via this `Builder`.

The following options are no longer available as initialization options:

* Base URL (i.e. Sentiance platform URL): you will only need to specify this once, when creating a Sentiance user.
* User linker: you will need to pass a linker when creating a user instead.
* Sentiance credentials (i.e. app ID & secret): you will only need to specify these once, when creating a Sentiance user.
* SDK status update handler: use the `setSdkStatusUpdateListener` method from `Sentiance`.
* [Triggered trips flavor](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md): it's not possible to set this flavor programmatically anymore. This must be configured for your app on the Sentiance platform. Please reach out to our support team to make sure it is configured properly.

The Sentiance SDK must be initialized before creating a user. Therefore, remove any conditional checks that require the presence of a user before initializing the SDK. Calling `initialize` during app startup when there is no Sentiance user yet will not create a user. It will only do the necessary setup to make it possible to create a user later on.

Initialization is now always synchronous, and therefore you will receive the result as an `InitializationResult` object after calling `initialize`.

{% hint style="info" %}
**Detecting Incorrect Initializations**

We expect apps to initialize the Sentiance SDK during app startup, without any postponement. Therefore, the new initializer must be called synchronously on the main thread, before *Application.onCreate()* completes ([more about this requirement](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md)).

To assist you with the integration, we have added a notification mechanism for detecting most common incorrect initializations. When detected, the SDK will show a notification with a warning message, and a link to our documentation (when tapped). This notification is limited to debug builds of your app, and can be disabled via *SentianceOptions*.
{% endhint %}

**5. Update the User Creation**

In your existing integration, you will likely have some conditional logic that calls `init` to create a Sentiance user. Replace this call with `createUser`. This method accepts `UserCreationOptions`, allowing you to specify the Sentiance app credentials (app ID and secret), the Sentiance platform URL (optional), and a user linker. If your existing integration does not make use of user linking, pass `UserLinker.NO_OP` as the linker.

{% hint style="info" %}
**User Creation Without the Need for App Credentials**

In addition to the user-linker based approach of creating users, we have introduced an improved linked-user creation mechanism, which replaces the Sentiance app credentials with a temporary authentication code, obtained from Sentiance at the moment of user creation. This is our recommended approach for creating users in all new integrations. You can read more about it [here](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md).
{% endhint %}

`createUser` returns the newly introduced `PendingOperation`, which you can use to capture the user creation result.

**6. Update Enabling/Disabling Detections**

We have deprecated the existing `start()` and `stop()` methods, and replaced them with `enableDetections()` and `disableDetections()` respectively. We believe these new methods give more clarity on what `start` and `stop` did in the past, as calling `start` did not always start detections, for example, due to a detection issue (e.g. in case of insufficient permissions).

These new methods are **persistent across app restarts and device reboots**. Therefore, you only need to call them once, and the SDK will automatically resume (or halt) detections upon initialization.

Additionally, we've added a new method called `getDetectionStatus()` which we believe offers better clarity on the status of the detections. The possible results are: `DISABLED`, `EXPIRED`, `ENABLED_BUT_BLOCKED`, and `ENABLED_AND_DETECTING`. For cases when the detections are blocked, the `SdkStatus` is still available to get more information about what is blocking detections (e.g. insufficient permissions).

**7. Update Other Asynchronous Method Usages**

Replace the usage of the following asynchronous SDK methods with their new `PendingOperation` returning counterparts:

<table><thead><tr><th width="357.5878561470098" align="center">Deprecated Method</th><th align="center">Replacement Method</th></tr></thead><tbody><tr><td align="center">reset(ResetCallback)</td><td align="center">reset()</td></tr><tr><td align="center">getUserAccessToken(TokenResultCallback)</td><td align="center">requestUserAccessToken()</td></tr><tr><td align="center">startTrip(Map, TransportMode, StartTripCallback)</td><td align="center">startTrip(Map, TransportMode)</td></tr><tr><td align="center">stopTrip(StopTripCallback)</td><td align="center">stopTrip()</td></tr><tr><td align="center">submitDetections(SubmitDetectionsCallback)</td><td align="center">submitDetections()</td></tr></tbody></table>

The new methods return a `PendingOperation` result which you can use to capture the operation's result.

**8. Update the Vehicle Crash Detection Code**

Update the package name of the imported classes and utilize the `CrashDetectionApi` class to access the crash detection methods with their updated signatures. See here. If you were using the deprecated crash detection methods from the `Sentiance` class, migrate to the new ones.

**9. Update Other Code Usages**

* Replace the SDK status `isLocationPermGranted` field usage with `locationPermission`.
* Replace `isInitialized()` usage with `getInitState()`.

### Migrating from 3.x to 4.x

{% hint style="info" %}
This update brings Android Oreo compatibility. You will now be able to target API level 26.
{% endhint %}

#### SDK Initialization

Initializing the SDK is done by calling the [`init()`](https://docs.sentiance.com/important-topics/api-reference/android/sentiance#init) method and passing an [`SdkConfig`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/sdkconfig) object.[`SdkConfig.Builder`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/sdkconfig/sdkconfig-builder) now expects a notification as the 3rd parameter. `enableForeground(notification)` is no longer available for this purpose. The SDK will show a notification depending on the OS version, targeted API level, and remote app configuration.

#### SDK Control

The `stopAfter(seconds)` method is no longer available.

#### User Metadata

User metadata methods no longer accept `MetadataCallback` as a 3rd parameter. Adding and removing metadata is now done asynchronously via the payload submission system.

#### External Events

Registering external events is no longer available.

#### Trip Control

**Starting and Stopping Trips**

[`startTrip()`](https://docs.sentiance.com/important-topics/api-reference/android/sentiance#starttrip-map-transportmode-starttripcallback) and [`stopTrip()`](https://docs.sentiance.com/important-topics/api-reference/android/sentiance#stoptrip-stoptripcallback) methods now require [`StartTripCallback`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/trip/starttripcallback) and [`StopTripCallback`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/trip/stoptripcallback) parameters respectively.

**Trip Details**

The SDK no longer returns a `Trip` object when a trip is stopped. The [`onTripTimeout()`](https://docs.sentiance.com/important-topics/api-reference/android/trip/triptimeoutlistener#ontriptimeout)method of [`TripTimeoutListener`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/trip/triptimeoutlistener) no longer returns a `Trip` object. Similarly, the `onTripStopped()` method of [`StopTripCallback`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/trip/stoptripcallback) no longer exists. This interface now provides an [`onSuccess()`](https://docs.sentiance.com/important-topics/api-reference/android/trip/stoptripcallback#onsuccess) and [`onFailure(sdkStatus)`](https://docs.sentiance.com/important-topics/api-reference/android/trip/stoptripcallback#onfailure) methods.

**Checking Ongoing Trips**

The [`isTripOngoing()`](https://docs.sentiance.com/important-topics/api-reference/android/sentiance#istripongoing) method now expects a parameter of type [`TripType`](https://docs.sentiance.com/important-topics/sdk/api-reference/android/trip/triptype).

#### Callbacks

All callback and listener methods are now executed on the application's main thread. If you perform any long running or network operations directly in the callbacks, please take care to move them to a background thread instead.

#### Other

`getWiFiLastSeenTimestamp()` is no longer available.

The `CrashCallback`'s Location parameter is now `@Nullable`.


# iOS

### Migrating from 5.x to 6.x

Version 6.x brings a lot of improvements and new features to the Sentiance SDK, like a simpler API for initialization, user creation, authentication and linking users. Some of these improvements result in breaking changes in the existing integration. This section describes all the changes that were made since version 5.15.0, and provides two code migration paths: a minimal one that utilizes existing (but deprecated) APIs, and a full one that utilizes only the new APIs.

{% hint style="info" %}
Please note that new SDK features and functionality will be supported via new API methods and classes. As such, you are encouraged to do a full migration, in order to benefit from upcoming features and improvements.
{% endhint %}

#### **General Changes**

**Minimum Supported iOS Version**

We have bumped the minimum supported iOS version to 13.0. You can still target iOS 12.0 in your app, but the SDK will initialize and operate only on iOS 13.0 and above.

**Fat .framework Artifact**

We will no longer provide our SDK as a fat **.framework**. We will instead offer two variants of **.xcframework** artifacts:

* A thin one which is referenced by our podspec and used for CocoaPods integrations.
* A regular one which bundles other dependency frameworks as mentioned below.

**Dependencies**

Apart from TensorFlow Lite v2.7, the SDK now has the following additional dependencies.

* Protobuf: v3.18
* UnzipKit: v1.9

These dependencies are bundled as xcframeworks within our regular xcframework, which you can utilize when doing a manual integration. If you are using CocoaPods (which references our thin framework), we have updated the SDK *podspec* to include these dependencies.

**SDK Bundle**

The bundle that is included in the SENTSDK framework has been updated. If you've done a manual integration, please make sure that you update **SENTSDK.bundle** in your project\*\*.\*\*

**Background Modes**

We have added background task scheduling capabilities in the SDK, to run maintenance and feature related operations in the background. This requires *Background fetch* and *Background processing* capabilities to be enabled for your app (Project settings -> select a target -> Signing & Capabilities -> Background Modes).

Additionally, the following task identifiers must be added to the project info (Info.plist), if you'd like to utilize the SDK's [on-device features](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md).

* com.sentiance.backgroundtask.tilerefresh
* com.sentiance.backgroundtask.segment\_detection

You can add these by following these steps:

1. Project settings -> select a target -> Info.
2. Add "Permitted background task scheduler identifiers".
3. Add the above identifiers as sub-items.

#### Code Changes & Deprecations

We have updated the SDK API methods and classes to make it easier to integrate our SDK into your app and to conform more to Swift standards. While doing so, we have renamed and removed some classes, methods, and properties, and deprecated others. Deprecated APIs will still function properly, however we plan to remove them in the next major release.

**Nullability Specification**

We have applied proper nullability annotations to all our public API classes and methods.

**Shared Instance**

In Swift, `SENTSDK.sharedInstance()` has been replaced with the more concise `Sentiance.shared` for accessing the Singleton instance of the SDK.

**Invoking SDK Methods**

SDK methods can no longer be invoked without first having initialized the SDK, unless it is stated otherwise in the method's documentation. For example, you must not call `submitDetections` without having initialized the SDK, but you are allowed to call `userExists` to check whether a Sentiance user us present. Check each method's documentation to know more.

**Callback Execution Thread**

Unless otherwise documented, callback or handler methods that are passed to the SDK are executed on the main thread. Be sure to check that you're not running intensive operations within these methods.

**Renamed Classes and Methods**

The following classes and methods have been renamed:

<table><thead><tr><th width="150" align="center">Old Name</th><th align="center">New Name</th></tr></thead><tbody><tr><td align="center">MetaUserLinker</td><td align="center">SENTUserLinker</td></tr><tr><td align="center">setTripTimeOutListener:</td><td align="center">setTripTimeoutListener:</td></tr></tbody></table>

**Swift Friendly Properties**

The following deprecated methods now have alternative Swift friendly properties:

<table><thead><tr><th width="150" align="center">Old Method</th><th align="center">New Property</th></tr></thead><tbody><tr><td align="center">getInitState</td><td align="center">initState</td></tr><tr><td align="center">getSDKStatus</td><td align="center">sdkStatus</td></tr><tr><td align="center">getVersion</td><td align="center">version</td></tr><tr><td align="center">getWifiQuotaLimit</td><td align="center">wifiQuotaLimit</td></tr><tr><td align="center">getWifiQuotaUsage</td><td align="center">wifiQuotaUsage</td></tr><tr><td align="center">getMobileQuotaLimit</td><td align="center">mobileQuotaLimit</td></tr><tr><td align="center">getMobileQuotaUsage</td><td align="center">mobileQuotaUsage</td></tr><tr><td align="center">getDiskQuotaLimit</td><td align="center">diskQuotaLimit</td></tr><tr><td align="center">getDiskQuotaUsage</td><td align="center">diskQuotaUsage</td></tr></tbody></table>

**Renamed Enums**

The `SENTSDKInitState` enum values have been updated for easier usage in Swift code.

|      Old Name      |            New Name            |
| :----------------: | :----------------------------: |
| SENTInitInProgress |   SENTSDKInitStateInProgress   |
|   SENTInitialized  |   SENTSDKInitStateInitialized  |
|    SENTResetting   |    SENTSDKInitStateResetting   |
| SENTNotInitialized | SENTSDKInitStateNotInitialized |

**SDK Status**

The `isLocationPermGranted` property has been deprecated and replaced with the new enum property `locationPermission`.

The `startStatus` property has been deprecated and replaced with `detectionStatus`***.***

**Asynchronous Methods**

Existing asynchronous SDK methods have been deprecated. New asynchronous methods have been added with a `completionHandler` with `Result` and `Error` as returned parameter types.

```swift
Sentiance.shared.submitDetections { result, error in
    guard let result = result else {
        // Error 
    }
    
    // Handle successful result
}
```

**SDK Initialization & User Creation**

The existing SDK initializer served two purposes:

1. Create a Sentiance user if one does not exist;
2. Initialize internal SDK components and resume detections.

We have deprecated the existing initializer `initWithConfig` in favour of a new method called `initializeWithOptions`. This new method **does not create a Sentiance user**. Instead, if a Sentiance user already exists, it initializes the SDK internally and resumes detections (if detections were enabled in the past).

To create a Sentiance user, we have added a new method called `createUser`. You can use this method to create a Sentiance user anywhere in your app. Note that you can only create one user at any time. To create a different user, you must first reset the SDK.

Given that user creation is not part of initialization anymore, **we expect apps to initialize the SDK during app startup, without postponing or delaying it**. The new initializer can safely be called when a Sentiance user does not exist yet, as it will only do minimal work to prepare the SDK for user creation.

{% hint style="warning" %}
As long as a Sentiance user exists on the device, you are expected to initialize the SDK during app startup, without delay or postponement. If you intend to not run detections for a brief period, simply call *disableDetections* instead. If you intend to not run detections for a longer duration, simply reset the SDK to remove the user from the device.
{% endhint %}

The deprecated method `isInitialized()` from `Sentiance` has also been removed. Use `initState` property instead.

**Checking User Existence & Linking Status**

You can use the new `userExists` property to find out whether a Sentiance user already exists. And to check whether the user has been successfully linked (i.e. user linking), you can use `isUserLinked`.

Unlike the other SDK methods, these two methods can be called without needing to initialize the SDK first.

#### **Minimal Migration Steps**

The following steps describe the minimal changes that you have to do, in order to compile and run your app with v6.x of the Sentiance SDK. Depending on your development environment and integration method, you might need to make additional changes that are not mentioned here. If you haven't already done so, please go through the General Changes section in order to get a complete list of the changes that were made in version 6.x.

**1. Update the SDK Dependency**

Your dependency manager should point to version 6.0.0 of the Sentiance SDK.

**2. Enable Background Modes**

Enable the additional background modes in your project's capabilities setting page. See here.

**3. Apply Code Changes**

* Replace the usage of `SENTSDK.sharedInstance()` with `Sentiance.shared`
* Replace the usage of `MetaUserLinker` class with `SENTUserLinker`
* Rename the enum values for `SENTSDKInitState` as described here.
* Replace the usage of the `SENTSDKStatus.isLocationPermGranted` property with `SENTSDKStatus.locationPermission`.
* Adapt to the nullability changes that impact the optional/non-optional nature of arguments.
* The existing initializer method `initWithConfig` copies the properties from the `SENTConfig` during initialization. If you currently update the `SENTConfig` after passing it to the initializer, the update will not be consumed. Move your code to before invoking the initializer.
* Remove the argument from `isVehicleCrashDetectionSupported`

#### Full Migration Steps

The following steps describe all the change that you have to do, in order to replace deprecated Sentiance API usage with non-deprecated counterparts, and successfully compile and run your app after updating to v6.x. Depending on your development environment and integration method, you might need to make additional changes that are not mentioned here. If you haven't already done so, please go through the General Changes section in order to get a complete list of the changes that were made in version 6.x.

**1. Update the SDK Dependency**

Your dependency manager should point to version 6.0.0 of the Sentiance SDK.

**2. Enable Background Modes**

Enable the additional background modes in your project's capabilities setting page. See here.

**3. Update the SDK Initialization**

In your existing integration, you initialize the Sentiance SDK by calling `initWithConfig` from within your Application's `didFinishLaunchingWithOptions` method. Replace `initWithConfig` with the new `initializeWithOptions` method. This method accepts a `SENTOptions` object.

The following options are no longer available as initialization options:

* User linker; you need to pass a linker when creating a user instead.
* Sentiance credentials (i.e. app ID & secret); you will only need to specify these once, when creating a Sentiance user.
* SDK status update handler; use the `setDidReceiveSdkStatusUpdateHandler:` method from to listen to status updates.
* [Triggered trips flavor](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md): it's not possible to set this flavor programmatically anymore. This must be configured for your app on the Sentiance platform. Please reach out to our support team to make sure it is configured properly.

Moreover, the new `SENTOptions` class requires specifying the purpose of its use. When creating an instance that will be used for initializing the SDK in the app delegate (during app launch), set it to `SENTOptionsInitPurposeAppLaunch`. This will allow the SDK initializer to execution certain actions that should only be done from within the app delegate, for example, registering background task identifiers.

If you specify a `baseURL` in the `SENTConfig` that you pass to the existing initializer, you should set the URL to the `platformUrl` property of the `SENTOptions` object that you will pass to the `initializeWithOptions` method.

{% hint style="info" %}
For a completely new integration, where you're not migrating an existing user, you do not need to specify the platform URL during initialization. Instead, you specify it during user creation.
{% endhint %}

The Sentiance SDK must be initialized before creating a user. Therefore, remove any conditional checks that require the presence of a user before initializing the SDK. Calling `initializeWithOptions` during app startup when there is no Sentiance user yet will not create a user. It will only do the necessary setup to make it possible to create a user later on.

Initialization is now always synchronous, and therefore you will receive the result as an `SENTInitializationResult` object after calling `initializeWithOptions`.

{% hint style="info" %}
**Detecting Incorrect Initializations**

We expect apps to initialize the Sentiance SDK during app startup, without any postponement. Therefore, the new initializer must be called synchronously on the main thread, before `didFinishLaunchingWithOptions` completes ([more about this requirement](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md)).

To assist you with the integration, we have added a mechanism for detecting most common incorrect initializations. When detected, the SDK will log a warning message in the console, with a link to our documentation.
{% endhint %}

**4. Enable Detections**

Having replaced the deprecated SDK initializer with the new one, you need to make a follow up change by replacing the SDK's start invocation with `enableDetections`. As the start call was being done after a successful initialization, you need to call `enableDetections` after the new initializer succeeds. Your code should therefore look as follows:

```swift
let result = Sentiance.shared.initialize(options: options)
if (result.isSuccessful) {
    Sentiance.shared.enableDetections { result, error in   
    }
}
```

Note that enabling detections (i.e. starting the SDK) is now persistent. Therefore, calling `enableDetections` is not necessary upon every app startup, but doing so does not have any side effects. For purposes of this migration, you'll have to enable detections at least one time after initializing the SDK. In a complete new integration, the call to `enableDetections` can normally be placed elsewhere in the application's flow, usually after user creation.

**5. Update User Creation**

In your existing integration, you will likely have some conditional logic inside `initWithConfig` to create a Sentiance user. Replace this call with `createUser`. This method accepts `UserCreationOptions`, allowing you to specify the Sentiance app credentials (app ID and secret), the Sentiance platform URL (optional), and a user linker. If your existing integration does not make use of user linking, pass `nil` as the linker.

{% hint style="info" %}
**User Creation Without the Need for App Credentials**

In addition to the user-linker based approach of creating users, we have introduced an improved linked-user creation mechanism, which replaces the Sentiance app credentials with a temporary authentication code, obtained from Sentiance at the moment of user creation. This is our recommended approach for creating users in all new integrations. You can read more about it [here](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md).
{% endhint %}

`createUser` has a completion handler with result and error as arguments, which you can use to capture the user creation result or the corresponding error in case of failure.

**6. Update Enabling/Disabling Detections**

We have deprecated the existing `start()` and `stop()` methods, and replaced them with `enableDetections()` and `disableDetections()` respectively. We believe these new methods give more clarity on what `start` and `stop` did in the past, as calling `start` did not always start detections, for example, due to a detection issue (e.g. in case of insufficient permissions).

These new methods are **persistent across app restarts and device reboots**. Therefore, you normally only need to call them once (e.g. after user creation), and the SDK will automatically resume (or halt) detections upon initialization.

We've also added a new property called `detectionStatus` which we believe offers better clarity on the status of the detections. The possible results are: `SENTDetectionStatusDisabled`, `SENTDetectionStatusExpired`, `SENTDetectionStatusEnabledButBlocked`, and `SENTDetectionStatusEnabledAndDetecting`. For cases when the detections are blocked, the `SENTSDKStatus` is still available to get more information about what is blocking detections (e.g. insufficient permissions).

**7. Switch to Using Properties**

For the deprecated methods that have been replaced with properties, switch to using the properties. See here.

**8. Update Other Asynchronous Method Usages**

Replace the usage of the following asynchronous SDK methods with their new counterparts with a result and error based completion handler:

<table><thead><tr><th width="150">Deprecated Method</th><th>Replacement Method</th></tr></thead><tbody><tr><td>start:completion:</td><td>enableDetectionsWithCompletionHandler:</td></tr><tr><td>startWithStopDate:completion:</td><td>enableDetectionsWithExpiryDate: completionHandler:</td></tr><tr><td>stop</td><td>disableDetectionsWithCompletionHandler:</td></tr><tr><td>getUserAccessToken:failure:</td><td>requestUserAccessTokenWithCompletionHandler:</td></tr><tr><td>startTrip:transportModeHint: success:failure:</td><td>startTripWithMetadata:<br>transportModeHint:<br>completionHandler:</td></tr><tr><td>stopTrip:failure:</td><td>stopTripWithCompletionHandler:</td></tr><tr><td>submitDetections:failure:</td><td>submitDetectionsWithCompletionHandler:</td></tr><tr><td>requestUserContext:failure:</td><td>requestUserContextWithCompletionHandler:</td></tr><tr><td>reset:failure:</td><td>resetWithCompletionHandler:</td></tr></tbody></table>

**9. Update Other Code Usages**

* Replace the usage of `SENTSDK.sharedInstance()` with `Sentiance.shared`
* Rename the enum values for `SENTSDKInitState` as described here.
* Replace the usage of the `SENTSDKStatus.isLocationPermGranted` property with `SENTSDKStatus.locationPermission`.
* Adapt to the nullability changes that impact the optional/non-optional nature of arguments.
* Remove the argument from `isVehicleCrashDetectionSupported`

### Migrating from 4.6.x to 5.x

#### Updating compiler build settings

1\. Go to the **Build Settings** tab of your target settings\
2\. Look for **Other Linker Flags** in the **Linking** section.\
3\. Add `-lc++` flag

#### Include the SDK bundle in your project

1\. Go to the **Build Phases** tab of your target settings\
2\. Expand the **Copy Bundle Resources** row and click the `+` button\
3\. Choose the `SENTSDK.bundle` file located inside `SENTSDK.framework`

#### Permission message

Make sure a value for the key `NSLocationAlwaysAndWhenInUseUsageDescription`has been added to the **Info.plist**

#### Sentiance SDK import update

Replace `#import <SENTTransportDetectionSDK/SENTTransportDetectionSDK.h>` to `@import SENTSDK;` in the import section

#### CoreData import

Make sure you have added `CoreData` library as linked framework in your Xcode project

#### User access token API change

If you need to get the user access token, use the code below

```objectivec
Sentiance.shared.requestUserAccessToken { result, error in
    guard let result = result else {
        // error
        print("Error: \(error!.failureReason)")
        return
    }
        
    print("Token: \(result.token)")
}
```

#### SDK Control

The `stopAfter(seconds)` method is no longer available.

#### User Metadata

User metadata methods no longer accept a `success/failure` block parameter. Adding and removing metadata is now done asynchronously via the payload submission system.

#### External Events

Registering external events is no longer available.

#### Trip Control

**Starting and Stopping Trips**

`startTrip()` and `stopTrip()` methods now require a `success` and `failure(sdkStatus)` block parameters respectively.

**Trip Details**

The SDK no longer returns a `Trip` object when a trip is stopped. The `setTripTimeOutListener()` callback of the SDK no longer returns a `Trip` object.

**Checking Ongoing Trips**

The `isTripOngoing()` method now expects a parameter of type `TripType`.

#### Other

`getWiFiLastSeenTimestamp()` is no longer available.


# React Native

### Migrating from 4.x to 6.x

This is a guide for migrating from the v4.x to v6.x of the Sentiance React Native SDKs. Aside from the new features that this version brings, one noticeable change is the variety of modules that have been introduced to replace the monolithic `react-native-sentiance` module.

These are smaller, independent modules that are distributed as their own packages. This has a number of advantages, such as smaller app bundle sizes (since you only install the modules you need).

This section describes all the changes that were made since version 4.7.0, and provides two code migration paths: a minimal one that utilizes existing (but deprecated) APIs, and a full one that utilizes only the new APIs.

#### General Changes

**Introducing new SDK modules**

Version 6.x breaks down the monolithic `react-native-sentiance` module into several new modules, such as `core` , `crash-detection` and others. These new modules are now part of the **@sentiance-react-native** scope on NPM.

Among the new modules introduced, we also included a `legacy` module.

This module provides the same APIs and functionality that the deprecated `react-native-sentiance` does. Using this module would allow you to migrate from v4 to v6 of the React Native SDKs with minimal effort.

{% hint style="info" %}
Please note that new SDK features and functionality will be supported via the new SDK modules that have been introduced. As such, using the **legacy** module is discouraged. Instead, you are encouraged to do a full migration, in order to benefit from upcoming features and improvements.
{% endhint %}

The `@sentiance-react-native/core` module contains functionality that is needed by all other modules, and hence must be installed always.

**Java 8 Compatibility on Android**

Version 6.x makes use of Java 8 language features. Make sure your app is configured to support Java 8. See [this](https://developer.android.com/studio/write/java8-support#supported_features) guide.

**iOS Pod Update**

The iOS pod has changed from `RNSentiance` to `RNSentianceCore`, along with a new podspec file located under `../node_modules/react-native-sentiance/ios`.

#### Code Changes & Deprecations

Below is the list of deprecations and code changes that were made in 6.x.

**SDK Native Event Names**

The following SDK events have been renamed, to help make the names even more unique and avoid any potential clashes with other components or libraries transmitting events with the same name.

<table><thead><tr><th width="150" align="center">Old Name</th><th align="center">New Name</th></tr></thead><tbody><tr><td align="center">SDKStatusUpdate</td><td align="center">SENTIANCE_STATUS_UPDATE_EVENT</td></tr><tr><td align="center">SDKUserLink</td><td align="center">SENTIANCE_USER_LINK_EVENT</td></tr><tr><td align="center">SDKUserActivityUpdate</td><td align="center">SENTIANCE_USER_ACTIVITY_UPDATE_EVENT</td></tr><tr><td align="center">SDKTripTimeout</td><td align="center">SENTIANCE_ON_TRIP_TIMED_OUT_EVENT</td></tr><tr><td align="center">VehicleCrashEvent</td><td align="center">SENTIANCE_VEHICLE_CRASH_EVENT</td></tr></tbody></table>

In addition, the **SDKCrashEvent** and **SDKTripProfile** events have been removed.

**New SDK Modules**

v6 introduces several new modules that provide Sentiance functionality:

* **legacy**: this module was introduced to make it easier for apps to integrate v6 with very minimal changes required. It provides all of the functionality previously provided by v4, but several APIs have been deprecated, and it no longer supports the old SDK event names. Please refer to the table above for a complete list of the changes in SDK event names.
* **core**: this module provides core Sentiance functionality, such as user creation and SDK detections. It serves as a base for all other modules, and hence must be installed always.
* **crash-detection**: this module provides crash detection related functionality.
* **user-context**: this module provides functionality to add user context awareness to your apps.

{% hint style="info" %}
Note that all deprecated functionality is now provided by the **legacy** module, and all of the new functionality introduced here is provided by the **core** module (except for crash detection related functionality, which is provided by the **crash-detection** module).
{% endhint %}

**SDK Initialization & User Creation**

The `init` and `initWithUserLinkingEnabled` functions have been deprecated.

User creation is no longer a part of the SDK initialization process, **hence we expect apps to initialize the SDK natively during app startup, without postponing or delaying it.**

This initializer can safely be called when a Sentiance user does not exist yet, as it will only do minimal work to prepare the SDK for user creation - **It will not create a new user.** Instead, if a Sentiance user already exists, it initializes the SDK internally and resumes detections (if detections were enabled in the past).

{% hint style="warning" %}
As long as a Sentiance user exists on the device, you are expected to initialize the SDK during app startup, without delay or postponement. If you intend to not run detections for a brief period, simply call `disableDetections` instead. If you intend to not run detections for a longer duration, simply reset the SDK to remove the user from the device.
{% endhint %}

* To create a Sentiance user, we have added a new function called `createUser`. You can use this method to create a Sentiance user anywhere in your app. Note that you can only create one user at any time. To create a different user, you must first reset the SDK.
* We have introduced an easier and a cleaner way to create users, using an authentication code instead of Sentiance credentials (app ID & secret). This is now the preferred way to create Sentiance users. See [this page](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md) for more info.
* The `isNativeInitializationEnabled`, `enableNativeInitialization`, `disableNativeInitialization` functions have been deprecated.

**Checking User Existence & Linking Status**

You can use the new `userExists()` function to find out whether a Sentiance user already exists on the device, and to check whether the user has been successfully linked (i.e. user linking), you can call `isUserLinked()`.

These two functions can be called without having to initialize the SDK beforehand.

**User Linking**

* After creating an unlinked Sentiance user, you can link it at a later time by calling the new `linkUser` or `linkUserWithAuthCode` functions.
* The `isThirdPartyLinked` function has been deprecated. Use `isUserLinked` instead.
* The `userLinkCallback` function has been deprecated. You would call this function originally to let the Sentiance SDK know if user linking on your backend was successful or not. Instead, with the new user creation approach, the linker function you supply must return a boolean indicating the result of the user linking.

**Enabling/Disabling detections**

* The `start` function was deprecated in favour of `enableDetections`
* The `startWithStopDate` function was deprecated in favour of `enableDetectionWithExpiryDate`
* The `stop` function was deprecated in favour of `disableDetections`

**SDK reset**

The existing `reset` function that returns a `Promise<boolean>` has been deprecated. A new `reset` function that has been added returns a `Promise<ResetResult>` instead.

**SDK status**

* The `startStatus` field has been deprecated. The new `detectionStatus` field has been added as its successor.
* A `backgroundRefreshStatus` field has been added. (iOS only)
* A `addSdkUserActivityUpdateListener` function has been added. It registers a listener internally to the `SENTIANCE_STATUS_UPDATE_EVENT` event and notifies the caller of any SDK status updates.

**User access token**

The `getUserAccessToken` function has been deprecated in favour of `requestUserAccessToken`.

**User metadata fields**

The existing `addUserMetadataField`, `addUserMetadataFields`, `removeUserMetadataField` functions return a `Promise<boolean>`. This boolean did not provide any additional information regarding whether the function call succeeded or not, and so we deprecated these functions in favour of new ones that return a `Promise<void>`.

**Battery optimization**

The existing `disableBatteryOptimization` function returns a `Promise<boolean>`. This boolean did not provide any additional information regarding whether the function call succeeded or not, and so we deprecated this function in favour of a new one that returns a `Promise<void>`.

**User activity updates**

In order to get user activity updates, you would call `listenUserActivityUpdates` originally to express your interest in getting user activity updates, before manually registering a listener to the now-deprecated `SDKUserActivityUpdate` event to process the actual updates.

We added a new function, `addSdkUserActivityUpdateListener` that combines the two aforementioned actions into one.

**Trips**

* In order to be notified of a trip timeout, you would call `listenTripTimeout` originally to express your interest in being notified of trip timeout events, before manually registering a listener to the now-deprecated `SDKTripTimeout` event to process the actual timeout event. We added a new function, `addTripTimeoutListener` that combines the two aforementioned actions into one.
* The `startTrip` function that returns a `Promise<boolean>` was deprecated in favour of a new one that returns a `Promise<void>`.
* The `stopTrip` function that returns a `Promise<boolean>` was deprecated in favour of a new one that returns a `Promise<void>`.

**Crash detection**

* We moved the following, crash detection related functionality to the **crash-detection** module (but it's still available through the **legacy** module as well):
  * `listenVehicleCrashEvents`
  * `invokeDummyVehicleCrash`
  * `isVehicleCrashDetectionSupported`
* In order to be notified of new crash events, you would call `listenVehicleCrashEvents`

  originally to express your interest in being notified of crash events, before manually registering a listener to the now-deprecated `VehicleCrashEvent` event to process the event. We added a new function, `addVehicleCrashEventListener` that combines the two aforementioned actions into one.

**SDK notification**

When running in the background, the Sentiance SDK needs to start a foreground service and supply a notification to Android, which gets shown to the user when the service is running.

On Android, you could customize this notification via the **AndroidManifest.xml** file by specifying custom meta-data entries. The prefix to the names of these entries has changed from `com.sentiance.react.bridge` to `com.sentiance.react.bridge.core` .

In addition, we added a new field called **notification\_id** that allows you to configure the identifier of the SDK's notification.

**Deprecated functions**

Below is a summary of all the functions that have been deprecated (**v4.7.0** and below), and their new counterparts from the `core` module:

<table><thead><tr><th width="333.9697205294237" align="center">Deprecated function</th><th width="341.165699005692" align="center">Replacement function</th></tr></thead><tbody><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">start(): Promise&#x3C;SdkStatus>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">enableDetections(): Promise&#x3C;EnableDisableDetectionsResult>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">startWithStopDate(stopEpochTimeMs: number | null): Promise&#x3C;SdkStatus>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">enableDetectionsWithExpiryDate(expiryEpochTimeMs: number | null): Promise&#x3C;EnableDisableDetectionsResult>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">stop(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">disableDetections(): Promise&#x3C;EnableDisableDetectionsResult>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">reset(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">reset(): Promise&#x3C;ResetResult>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">getUserAccessToken(): Promise&#x3C;UserAccessToken>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">requestUserAccessToken(): Promise&#x3C;UserAccessToken>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">addUserMetadataField(label: string, value: string): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">addUserMetadataField(label: string, value: string): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">addUserMetadataFields(metadata: MetadataObject): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">addUserMetadataFields(label: MetadataObject): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">removeUserMetadataField(label: string): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">removeUserMetadataField(label: string): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">disableBatteryOptimization(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">disableBatteryOptimization(): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">listenUserActivityUpdates(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">listenUserActivityUpdates(): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">startTrip(metadata: MetadataObject | null,  hint: TransportMode): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">startTrip(metadata: MetadataObject | null, hint: TransportMode): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">stopTrip(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">stopTrip(): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">submitDetections(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">submitDetections(): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">updateSdkNotification(title: string, message: string): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">updateSdkNotification(title: string, message: string): Promise&#x3C;void>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">isThirdPartyLinked(): Promise&#x3C;boolean>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">isUserLinked(): Promise&#x3C;boolean>
</code></pre></td></tr></tbody></table>

Most of the changes to the functions in the table above concern mainly their return values. Several functions returned a `Promise` that resolves to a `boolean` that does not add any useful information regarding whether the function call was successful or not. Their new counterparts return a `Promise` that resolves to `void` instead. To catch any errors, use a try/catch mechanism.

The following the functions have also been deprecated:

<table><thead><tr><th width="333.9697205294237" align="center">Deleted functions</th></tr></thead><tbody><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">getValueForKey(key: string, defaultValue: string): Promise&#x3C;string>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">setValueForKey(key: string, value: string): void
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">isNativeInitializationEnabled(): Promise&#x3C;boolean>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">enableNativeInitialization(): Promise&#x3C;boolean>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">disableNativeInitialization(): Promise&#x3C;boolean>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">init(
    appId: string,  
    secret: string,  
    baseURL: string | null,  
    shouldStart: boolean): Promise&#x3C;boolean | SdkStatus>;
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">initWithUserLinkingEnabled(  
    appId: string,  
    secret: string,  
    baseURL: string | null,  
    shouldStart: boolean): Promise&#x3C;boolean | SdkStatus>;
</code></pre></td></tr></tbody></table>

In addition to the functions listed above, the following functions (provided by the **v4.7.1** and above) were also deprecated:

<table><thead><tr><th width="333.9697205294237" align="center">Deprecated function</th><th width="341.165699005692" align="center">Replacement function</th></tr></thead><tbody><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">createUserExperimental(configuration: CreateUserConfiguration)
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">createUser(options: UserCreationOptions): Promise&#x3C;CreateUserResult>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">resetExperimental(): void
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">reset(): Promise&#x3C;ResetResult>
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">disableExperimental(): Promise&#x3C;void>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">disableDetections(): Promise&#x3C;EnableDisableDetectionsResult>In addition, the following the functions have also been deprecated:
</code></pre></td></tr><tr><td align="center"><pre class="language-javascript"><code class="lang-javascript">enableExperimental() : Promise&#x3C;void>
</code></pre></td><td align="center"><pre class="language-javascript"><code class="lang-javascript">enableDetections(): Promise&#x3C;EnableDisableDetectionsResult>
</code></pre></td></tr></tbody></table>

#### **Minimal Migration Steps (only for v4.7.0 and below)**

The following describes the minimal changes that you have to do, in order to compile and run your app with v6.x of the Sentiance SDK. Depending on your development environment and integration method, you might need to make additional changes that are not mentioned here. If you haven't already done so, please go through the General Changes section in order to get a complete list of the changes that were made in version 6.x.

Please note that it is only possible to do a minimal migration if you are using v4.7.0 or below of the Sentiance React Native SDK. If you are using a higher version, then you need to perform a full migration.

**1. Remove v4 from your code**

Start by removing the `react-native-sentiance` dependency from your project's **package.json** file. You can do so by running the following command on your project's root folder:

```bash
npm uninstall react-native-sentiance
```

Or you can remove the dependency manually by editing your **package.json**:

{% code title="package.json" %}

```json
{
  ...,
  "dependencies": {
    ...,
    "react-native-sentiance": "^4.x.x" <-- Remove this line
  }
}
```

{% endcode %}

To remove the package from your local environment, delete the `yarn.lock`/`package-lock.json` files then reinstall the project's dependencies.

Next, you need to remove all references to the v4 SDK from your Android code. To do that, follow these steps:

* Open up your project's **android/settings.gradle** file. There are 2 lines in there that need to be removed:

{% code title="android/settings.gradle" %}

```groovy
rootProject.name = 'YourProjectName'

// Remove the 2 lines below
include ':react-native-sentiance'
project(':react-native-sentiance').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentiance/android')

include ':app'
```

{% endcode %}

* Open your project's **android/app/build.gradle** file, and remove the `react-native-sentiance` dependency:

{% code title="android/app/build.gradle" %}

```groovy
dependencies {
    ...
    implementation project(':react-native-sentiance') // Remove this line
}
```

{% endcode %}

**2. Add v6 dependency**

Run:

```bash
npm install @sentiance-react-native/core @sentiance-react-native/legacy
```

**3. Update your iOS Podfile**

Replace the following line:

```
pod 'RNSentiance', :path => '../node_modules/react-native-sentiance/ios/RNSentiance.podspec'
```

with:

```
pod 'RNSentianceCore', :path => '../node_modules/@sentiance-react-native/core/ios'
```

and then run `pod install` from within your project's **ios/** directory.

**4. Update your Javascript imports**

Replace all occurrences of this import in your code:

```javascript
import RNSentiance from 'react-native-sentiance';
```

with the following:

```javascript
import RNSentiance from '@sentiance-react-native/legacy';
```

**5. Update the Sentiance SDK event names**

Refer to this section for more details on the changes to the SDK event names. Make sure to replace every occurrence of the old event names with their corresponding new ones.

**6. Update the AndroidManifest.xml file**

When running in the background, the Sentiance SDK needs to start a foreground service and supply a notification to Android, which gets shown to the user when the service is running.

On Android, you could customize this notification via the **AndroidManifest.xml** file by specifying custom meta-data entries. The prefix to the names of these entries has changed from `com.sentiance.react.bridge` to `com.sentiance.react.bridge.core`. Make sure to update your code accordingly.

#### Full Migration Steps

The following steps describe all the changes that you have to do, in order to replace deprecated Sentiance API usage with non-deprecated counterparts, and successfully compile and run your app after updating to v6.x. Depending on your development environment and integration method, you might need to make additional changes that are not mentioned here. If you haven't already done so, please go through the General Changes section in order to get a complete list of the changes that were made in version 6.x.

**1. Remove v4 from your code**

Start by removing the `react-native-sentiance` dependency from your project's **package.json** file. You can do so by running the following command on your project's root folder:

```shell
npm uninstall react-native-sentiance
```

Or you can remove the dependency manually by editing your **package.json**:

{% code title="package.json" %}

```json
{
  ...,
  "dependencies": {
    ...,
    "react-native-sentiance": "^4.x.x" <-- Remove this line
  }
}
```

{% endcode %}

To remove the package from your local environment, delete the `yarn.lock`/`package-lock.json` files then reinstall the project's dependencies.

Next, you need to remove all references to the v4 SDK from your Android code. To do that, follow these steps:

* Open up your project's **android/settings.gradle** file. There are 2 lines in there that need to be removed:

{% code title="android/settings.gradle" %}

```groovy
rootProject.name = 'YourProjectName'

// Remove the 2 lines below
include ':react-native-sentiance'
project(':react-native-sentiance').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentiance/android')

include ':app'
```

{% endcode %}

* Open your project's **android/app/build.gradle** file, and remove the `react-native-sentiance` dependency:

{% code title="android/app/build.gradle" %}

```groovy
dependencies {
    ...
    implementation project(':react-native-sentiance') // Remove this line
}
```

{% endcode %}

**2. Add v6 dependency**

Run:

```bash
npm install @sentiance-react-native/core @sentiance-react-native/crash-detection
```

**3. Update your iOS Podfile**

Replace the following line:

```
pod 'RNSentiance', :path => '../node_modules/react-native-sentiance/ios/RNSentiance.podspec'
```

with:

```
pod 'RNSentianceCore', :path => '../node_modules/@sentiance-react-native/core/ios'
```

and then run `pod install` from within your project's **ios/** directory.

**4. Update your Javascript imports**

The previous version of the Sentiance React Native SDK provided the following crash-detection related functionality:

* `listenVehicleCrashEvents`
* `invokeDummyVehicleCrash`
* `isVehicleCrashDetectionSupported`

These functions have been moved into a separate module, called `crash-detection`.

Make sure to add the proper import everywhere you use these 3 functions; you can proceed in one of two ways:

```javascript
// 1. Import default
import SentianceCrashDetection from '@sentiance-react-native/crash-detection';
await SentianceCrashDetection.listenVehicleCrashEvents();
await SentianceCrashDetection.invokeDummyVehicleCrash();
const isCrashDetectionSupported = 
    await SentianceCrashDetection.isVehicleCrashDetectionSupported();

// 2. or use named imports
import {
    listenVehicleCrashEvents,
    invokeDummyVehicleCrash,
    isVehicleCrashDetectionSupported
} from '@sentiance-react-native/crash-detection';
```

The rest of the Sentiance SDK functionality that your integration uses is now provided by the `core` module. Make sure to replace all of this import's occurrences in your code:

```javascript
import RNSentiance from 'react-native-sentiance';
```

with the following:

```javascript
import RNSentiance from '@sentiance-react-native/core';
```

**5. Update the SDK initialization**

In your existing integration, you may be initializing the Sentiance SDK in different ways:

1. By calling `init` in your Javascript code to initialize the SDK and to create a user without linking it.
2. By calling `initWithUserLinkingEnabled` in your Javascript code to initialize the SDK and to create a linked user.
3. By calling a helper function from the now-deprecated `RNSentianceHelper` class in your Android code

The Sentiance SDK must be initialized natively, as Javascript initialization is no longer supported. Remove any calls to `init` or `initWithUserLinkingEnabled` from your Javascript code.

For Android, replace any calls through the `RNSentianceHelper` class with a call to the new `initializeSDK` method available in the new `SentianceHelper` class:

{% code title="MainApplication.java" %}

```java
import com.sentiance.react.bridge.core.SentianceHelper;

// Inside the onCreate method
SentianceHelper.getInstance(getApplicationContext())
    .initializeSDK();
```

{% endcode %}

For iOS, replace the call to `initSDK:secret:baseURL:shouldStart:resolver:rejecter:` with `initializeSDKWithLaunchOptions:` that is now available via the `RNSentianceHelper` class. If you previously had `shouldStart` set to `YES`, then call `enableDetectionsIfUserExists` after a successful initialization to enable detections. Note that this last step is not required for completely new integrations, as enabling SDK detections [is now persistent](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md).

{% code title="AppDelegate.m" %}

```objectivec
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...
    BOOL shouldStart = YES; // this is normally YES, but check your existing implementation

    RNSentianceHelper *helper = [[RNSentianceHelper alloc] init];
    SENTInitializationResult *result = [helper initializeSDKWithLaunchOptions:launchOptions];
    if (result.isSuccessful && shouldStart) {
        [helper enableDetectionsIfUserExists];
    }
    
    return YES;
}
```

{% endcode %}

{% hint style="info" %}
If you are using the experimental methods for integrating the SDK, the ones introduced in v4.7.1, alongside `initializeWithSuccess:failure:`, then replace it with the initializer method that is mentioned above. In this case, you should consider `shouldStart` to be `YES`.
{% endhint %}

Finally, enabling/disabling the native initialization is no longer supported. Make sure to remove any calls to the old `enableNativeInitialization`, `disableNativeInitialization`, `isNativeInitializationEnabled` functions from your code.

**6. Update the user creation process**

In your existing integration, you will likely have some conditional logic that calls `init` or `initWithUserLinkingEnabled` in order to create a Sentiance user.

* If you are using `init`, then replace:

{% code title="app.js" %}

```javascript
await RNSentiance.init(
    APP_ID, 
    APP_SECRET, 
    SENTIANCE_PLATFORM_URL, 
    ...);
```

{% endcode %}

with the following:

```javascript
import { createUser } from '@sentiance-react-native/core';

await createUser(
    {
        appId: APP_ID,
        appSecret: APP_SECRET,
        platformUrl: SENTIANCE_PLATFORM_URL,
    }
);
```

* If you are using `initWithUserLinkingEnabled` to create a user, then replace:

{% code title="app.js" %}

```javascript
const emitter = new NativeEventEmitter(RNSentiance);

emitter.addListener('SDKUserLink',
      async data => {
        const { installId } = data;
        const success = await linkUserToYourBackend(installId);
        RNSentiance.userLinkCallback(success);
      }
    );

await RNSentiance.initWithUserLinkingEnabled(
    APP_ID, 
    APP_SECRET, 
    SENTIANCE_PLATFORM_URL, 
    ...);
```

{% endcode %}

with the following:

```javascript
import { createUser } from '@sentiance-react-native/core';

await createUser(
    {
        appId: APP_ID,
        appSecret: APP_SECRET,
        platformUrl: SENTIANCE_PLATFORM_URL,
        linker: async (installId) => {
            // This linker function must return a boolean, indicating if user 
            // linking to your backend was successful or not.
            return await linkUserToYourBackend(installId);
        }
    }
);
```

You no longer need to register an event listener onto **SDKUserLink** events or call `RNSentiance.userLinkCallback()`.

{% hint style="info" %}
**User Creation Without the Need for App Credentials**

In addition to the user-linker based approach of creating users, we have introduced an improved linked-user creation mechanism, which replaces the Sentiance app credentials with a temporary authentication code, obtained from Sentiance at the moment of user creation. This is our recommended approach for creating users in all new integrations. You can read more about it [here](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md).
{% endhint %}

For more information on user creation, check out [this](https://github.com/sentiance/v4-docs/blob/main/important-topics/sdk/appendix/migration-guide/broken-reference/README.md) page.

**7. Update the AndroidManifest.xml file**

When running in the background, the Sentiance SDK needs to start a foreground service and supply a notification to Android, which gets shown to the user when the service is running.

On Android, you could customize this notification via the **AndroidManifest.xml** file by specifying custom meta-data entries. The prefix to the names of these entries has changed from `com.sentiance.react.bridge` to `com.sentiance.react.bridge.core`. Make sure to update your code accordingly.

**8. Update the Sentiance SDK event names**

Refer to this section for more details on the changes to the SDK event names. Make sure to replace every occurrence of the old event names with their corresponding new ones.

**9. Update other deprecated function usages**

Refer to this section to view the full list of functions that have been deprecated, and make sure to replace the existing usage of the deprecated functions on that list with their new counterparts.

**10. Additional required changes (only if you are migrating from v4.7.1+)**

* The `createUserExperimental` function has been deprecated. Use `createUser` instead:

```javascript
// Replace this code
import { createUserExperimental } from 'react-native-sentiance/sentiance';

await createUserExperimental(
    {
        appId: APP_ID,
        appSecret: APP_SECRET,
        baseUrl: SENTIANCE_PLATFORM_URL,
        linker: (data, isUserLinkingSuccessful) => {
            const { installId } = data;
            const success = await linkUserToYourBackend(installId);
            isUserLinkingSuccessful(success);
        }
    }
);

// With this code
import { createUser } from '@sentiance-react-native/core';

await createUser(
    {
        appId: APP_ID,
        appSecret: APP_SECRET,
        platformUrl: SENTIANCE_PLATFORM_URL,
        linker: installId => {
            // This linker function must return a boolean, indicating if user 
            // linking to your backend was successful or not.
            return await linkUserToYourBackend(installId);
        }
    }
);
```

* The `resetExperimental` function has been deprecated. Use `reset` instead:

```javascript
// Replace this code
import { resetExperimental } from 'react-native-sentiance/sentiance';
resetExperimental();

// With this code
import { reset } from '@sentiance-react-native/core';
const result = await reset();
```

* The `disableExperimental` function has been deprecated. Use `disableDetections` instead:

```javascript
// Replace this code
import { disableExperimental } from 'react-native-sentiance/sentiance';
await disableExperimental();

// With this code
import { disableDetections } from '@sentiance-react-native/core';
const result = await disableDetections();
```

* The `enableExperimental` function has been deprecated. Use `enableDetections` instead:

```javascript
// Replace this code
import { enableExperimental } from 'react-native-sentiance/sentiance';
await enableExperimental();

// With this code
import { enableDetections } from '@sentiance-react-native/core';
const result = await enableDetections();
```


# Flutter

### Migrating from `v0.0.22` to `v6.9.x` of the Sentiance Flutter SDKs <a href="#migrating-from-v0022-to-v690-of-the-sentiance-flutter-sdks" id="migrating-from-v0022-to-v690-of-the-sentiance-flutter-sdks"></a>

#### Changes regarding library imports <a href="#changes-regarding-library-imports" id="changes-regarding-library-imports"></a>

In version `0.0.22`, the imports you needed to specify to interact with the Sentiance SDKs were a little too much. That's why we're now offering the entirety of a package's functionality under 1 library file, ready for import.

For the example, in order to create a new Sentiance user, you needed to do something like this:

```dart
// import #1, brings in CreateUserResult and CreateUserOptions types among other types
import 'package:sentiance_core/api.g.dart'; 
// import #2, brings in SentianceCore and more
import 'package:sentiance_core/sentiance_core.dart';

void createNewSentianceUser() async {
    final sentianceCore = SentianceCore();
    CreateUserResult result = await sentianceCore.createUser(
        CreateUserOptions(...)
    );
}
```

Change your code to import everything it needs from the `sentiance_core` package's single public library instead:

```dart
// This brings in everything you previously needed in addition to all other public declarations of the package
import 'package:sentiance_core/sentiance_core.dart';
                                                     
void createNewSentianceUser() async {
    final sentianceCore = SentianceCore();
    CreateUserResult result = await sentianceCore.createUser(
        CreateUserOptions(...)
    );
}
```

If you don't want to pollute the global namespace, you can be specific about what you want to import:

```dart
import 'package:sentiance_core/sentiance_core.dart' 
    show SentianceCore, CreateUserResult, CreateUserOptions; // Import only these 3 types

void createNewSentianceUser() async {
    final sentianceCore = SentianceCore();
    CreateUserResult result = await sentianceCore.createUser(
        CreateUserOptions(...)
    );
}
```

This applies for every single Sentiance package out there. At the time of writing this, the following are the only public imports for all of our SDKs:

```dart
import 'package:sentiance_core/sentiance_core.dart';
import 'package:sentiance_crash_detection/sentiance_crash_detection.dart';
import 'package:sentiance_driving_insights/sentiance_driving_insights.dart';
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';
import 'package:sentiance_smart_geofences/sentiance_smart_geofences.dart';
import 'package:sentiance_user_context/sentiance_user_context.dart';
```

For more information on what types and functionality our packages provide, check out the API reference on pub.dev.

#### Changes regarding duplicate type definitions across packages <a href="#changes-regarding-duplicate-type-definitions-across-packages" id="changes-regarding-duplicate-type-definitions-across-packages"></a>

In `v0.0.22`, several packages were re-defining the same type. For the example, `sentiance_crash_detection` was defining its own `Location` type, as well as `sentiance_smart_geofences`. These types shared the same fields as well, so it only made sense to get rid of the duplication here and to centralize the definition of commonly used types in one package and one package alone.

For more information on where to find what, check out the documentation on [pub.dev](https://pub.dev/packages?q=sentiance).

#### Changes regarding the public signature of certain APIs <a href="#changes-regarding-the-public-signature-of-certain-apis" id="changes-regarding-the-public-signature-of-certain-apis"></a>

The signatures of several Sentiance APIs have changed. Make sure to update your code accordingly:

* `reset()` now returns `void` instead of `bool`. If the operation succeeds, execution terminates normally. If an error occurs, a `ResetError` will be thrown.
* `submitDetections()` now returns `void` instead of `bool`. If the operation succeeds, execution terminates normally. If an error occurs, a `SubmitDetectionsError` will be thrown.
* `enableDetections()` now returns `void` instead of `bool`. If the operation succeeds, execution terminates normally. If an error occurs, an `EnableDetectionsError` will be thrown.
* `enableDetectionsWithExpiryDate()` now returns `void` instead of `bool`. If the operation succeeds, execution terminates normally. If an error occurs, an `EnableDetectionsError` will be thrown.
* `disableDetections()` now returns `void` instead of `bool`. If the operation succeeds, execution terminates normally. If an error occurs, a `DisableDetectionsError` will be thrown.
* `createUser()` now throws a `UserCreationError` if the operation fails.
* `requestAccessToken()` now throws a `UserAccessTokenError` if the operation fails.
* `refreshGeofences()` now throws a `SmartGeofencesRefreshError` if the operation fails.
* `requestUserContext()` now throws a `RequestUserContextError` if the operation fails.
* `invokeDummyVehicleCrash()` now returns `void` instead of `bool`.
* The `getPhoneUsageEvents`, `getHarshDrivingEvents`, `getCallWhileMovingEvents`, `getSpeedingEvents`, `getTimelineUpdates` and `getTimelineEvents` APIs now return a list of non-nullable events instead of nullable ones. This change could be breaking for you if you're using a linter as part of your build process. If not, you may want to do something about those "unnecessary null-aware operator use" warnings that will pop.

#### APIs that were removed <a href="#apis-that-were-removed" id="apis-that-were-removed"></a>

* `getLanguageName()` has been removed and is no longer supported.

#### Changes concerning overall usage of background listeners <a href="#changes-concerning-overall-usage-of-background-listeners" id="changes-concerning-overall-usage-of-background-listeners"></a>

**Setting a background listener**

In order to set a listener to get notified of SDK events in the background - for the example, smart geofence events - you had to create a top level Dart handler function like this:

```dart
import 'package:sentiance_smart_geofences/api.g.dart';

@pragma('vm:entry-point')
void handleSmartGeofenceEvents() async {
  // Initialize required Flutter bindings
  WidgetsFlutterBinding.ensureInitialized();
  
  SentianceSmartGeofencesListenerApi.setUp(SmartGeofenceEventHandler());
}

class SmartGeofenceEventHandler extends SentianceSmartGeofencesListenerApi {
  @override
  void onSmartGeofenceEvent(SmartGeofenceEvent event) {
    // Do something with the event
  }
}
```

The `SentianceSmartGeofencesListenerApi` class is no longer directly accessible. Change your code to use the static `SentianceSmartGeofences.registerSmartGeofenceEventListener` function instead:

```dart
import 'package:sentiance_smart_geofences/sentiance_smart_geofences.dart';

@pragma('vm:entry-point')
void handleSmartGeofenceEvents() async {
  // Initialize required Flutter bindings
  WidgetsFlutterBinding.ensureInitialized();

  SentianceSmartGeofences.registerSmartGeofenceEventListener((smartGeofenceEvent) {
    // Do something with the event
  });
}
```

On that same note, here is a summary with all the changes that you need to make for any background listeners that you may be setting:

<table><thead><tr><th width="375">Before migrating</th><th>After migrating</th></tr></thead><tbody><tr><td><code>SentianceUserContextListenerApi.setUp(...)</code></td><td><code>SentianceUserContext.registerUserContextUpdateListener()</code></td></tr><tr><td><code>SentianceEventTimelineListenerApi.setUp(...)</code></td><td><code>SentianceEventTimeline.registerEventTimelineUpdateListener()</code></td></tr><tr><td><code>SentianceSmartGeofencesListenerApi.setUp(...)</code></td><td><code>SentianceSmartGeofences.registerSmartGeofenceEventListener()</code></td></tr><tr><td><code>SentianceCrashDetectionListenerApi.setUp(...)</code></td><td><code>SentianceCrashDetection.registerCrashListener()</code><br><code>SentianceCrashDetection.registerCrashDiagnosticListener()</code></td></tr><tr><td><code>SentianceDrivingInsightsListenerApi.setUp(...)</code></td><td><code>SentianceDrivingInsights.registerDrivingInsightsListener()</code></td></tr></tbody></table>

**Setting crash detection background listeners**

As you may have noticed in the table above, you now have the option to set a listener to vehicle crash events independently of setting another listener for crash diagnostic updates; make sure to update your code accordingly based on your needs and requirements.

**Setting a user context updates' background listener**

In version `0.0.22`, you set a background listener to get the most recent user context updates as follows:

```dart
import 'package:sentiance_user_context/api.g.dart';

@pragma('vm:entry-point')
void handleUserContextUpdates() async {
  // Initialize required Flutter bindings
  WidgetsFlutterBinding.ensureInitialized();

  SentianceUserContextListenerApi.setUp(UserContextUpdatesHandler());
}

class UserContextUpdatesHandler extends SentianceUserContextListenerApi {
  @override
  void didUpdate(UserContext userContext) {
    // Do something with the user context
  }
}
```

Change your code to take into account an additional list of user context update criteria that now gets delivered as well:

```dart
import 'package:sentiance_user_context/sentiance_user_context.dart' show SentianceUserContext;

@pragma('vm:entry-point')
void handleUserContextUpdates() async {
  // Initialize required Flutter bindings
  WidgetsFlutterBinding.ensureInitialized();

  SentianceUserContext.registerUserContextUpdateListener((criteria, userContext) {
    // Do something with the user context
    // Do something with the update criteria
  });
}
```

#### Changes impacting users of event timeline features <a href="#changes-impacting-users-of-event-timeline-features" id="changes-impacting-users-of-event-timeline-features"></a>

All the event timeline APIs return objects of type `TimelineEvent` that had multiple nullable fields that only carry a value if the event is of a certain `type`.

The `type` field has been removed and the `TimelineEvent` class is now abstract. To process the timeline events based on their type, use inheritance checks as follows:

```dart
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart';

final eventTimeline = SentianceEventTimeline();

/// A function that processes all events that occurred since the dawn of times.
void processTimelineEvents() async {
  final startEpochTimeMs = 0;
  final endEpochTimeMs = DateTime.now().millisecondsSinceEpoch;
  final events = await eventTimeline.getTimelineEvents(startEpochTimeMs, endEpochTimeMs);

  for (final timelineEvent in events) {
    if (timelineEvent is StationaryEvent) {
      // Process stationary
    } else if (timelineEvent is TransportEvent) {
      // Process transport
    } else if (timelineEvent is OffTheGridEvent) {
      // Process off the grid
    } else if (timelineEvent is UnknownEvent) {
      // Process unknown event
    }
  }
}
```

#### Changes impacting users of user context features <a href="#changes-impacting-users-of-user-context-features" id="changes-impacting-users-of-user-context-features"></a>

**Segment attributes**

As of `v6.9.0`, segment attribute values are `double` instead of `int`. Please update your code accordingly.

#### Changes impacting users of driving insights features <a href="#changes-impacting-users-of-driving-insights-features" id="changes-impacting-users-of-driving-insights-features"></a>

**Call-while-moving events**

* The `minTravelledSpeedInMps` and `maxTravelledSpeedInMps` fields have been renamed to `minTraveledSpeedInMps` and `maxTraveledSpeedInMps` respectively.

**Transport events associated with a driving insights payload**

Transport events previously had a `String type` field, which is no longer the case.

```dart
final sentianceDrivingInsights = SentianceDrivingInsights();
final drivingInsights = await sentianceDrivingInsights.getDrivingInsights("transport_id_here");

String type = drivingInsights.transportEvent.type; // no longer compiles
```

Moreover, with `v6.9.0`, the transport event associated with driving insights is now a type that is provided by the `sentiance_event_timeline` package:

```dart
import 'package:sentiance_driving_insights/sentiance_driving_insights.dart' show SentianceDrivingInsights;
import 'package:sentiance_event_timeline/sentiance_event_timeline.dart' show TransportEvent;

final sentianceDrivingInsights = SentianceDrivingInsights();
final drivingInsights = await sentianceDrivingInsights.getDrivingInsights("transport_id_here");

TransportEvent transportEvent = drivingInsights.transportEvent;
```

Make sure to change your code accordingly.


# Feature Production Readiness

The Sentiance SDK includes two types of features:

* **Early Access**: these are early releases of features that Sentiance is working on, and that have been tested for stability and acceptable quality. Early Access features are intended for trials, PoCs and MVPs while we finalize them for production use. The APIs are not final, and may change until the feature reaches production. We welcome your feedback to help us improve the features further.
* **Production Ready**: these are features that have undergone rigorous quality and stability tests and checks, and are ready to be used for building production features in your app. The APIs are final, and remain backwards compatible across minor and patch version updates.

{% hint style="info" %}
We generally release certain features as Early Access first, then update them to Production Ready in subsequent major or minor SDK releases.
{% endhint %}

#### Production Ready State of Current Features

The following table lists each SDK feature, with the version of the SDK it was first released in as Early Access, and the version in which it became Production Ready.

<table data-header-hidden><thead><tr><th width="225.8574565245455"></th><th></th><th></th></tr></thead><tbody><tr><td><strong>Feature</strong></td><td><strong>Early Access Version</strong></td><td><strong>Production Ready Version</strong></td></tr><tr><td>General Purpose Sensor Data Collection</td><td></td><td><p>Android: 1.0.0</p><p>iOS: 1.0.0</p></td></tr><tr><td>Automatic Stationary and Trip Detection</td><td></td><td><p>Android: 1.0.0</p><p>iOS: 1.0.0</p></td></tr><tr><td>User Linking</td><td></td><td><p>Android: 4.1.22</p><p>iOS: 5.1.0</p></td></tr><tr><td>User Activity Information</td><td></td><td><p>Android:4.7.0</p><p>iOS: 5.3.0</p></td></tr><tr><td>SDK Reset</td><td></td><td><p>Android: 4.14.0</p><p>iOS: 5.6.0</p></td></tr><tr><td>Vehicle Crash Detection</td><td><p>Android: 4.1.21</p><p>iOS: 5.1.4</p></td><td><p>Android: 4.20.0</p><p>iOS: 5.9.0</p></td></tr><tr><td>Transport Classification</td><td><p>Android: 6.0.0</p><p>iOS: 6.0.0</p></td><td><p>Android: 6.4.0</p><p>iOS: 6.4.0</p></td></tr><tr><td>Home &#x26; Work Detection</td><td><p>Android: 6.0.0</p><p>iOS: 6.0.0</p></td><td><p>Android: 6.4.0</p><p>iOS: 6.4.0</p></td></tr><tr><td>Event Timeline</td><td></td><td>Android 6.6.0<br>iOS 6.6.0</td></tr><tr><td>User Segment Detection</td><td><p>Android: 6.0.0</p><p>iOS: 6.0.0</p></td><td><p>Android: 6.4.0</p><p>iOS: 6.4.0</p></td></tr><tr><td>User Current Context Information</td><td><p>Android: 6.0.0</p><p>iOS: 6.0.0</p></td><td><p>Android: 6.4.0</p><p>iOS: 6.4.0</p></td></tr><tr><td>Venue Type Mapping Information</td><td><p>Android: 6.0.0</p><p>iOS: 6.0.0</p></td><td></td></tr><tr><td>Semantic Time</td><td><p>Android: 6.2.0</p><p>iOS: 6.2.0</p></td><td></td></tr><tr><td>Driving Insights</td><td><p>Android: 6.3.0</p><p>iOS: 6.3.0</p></td><td>Android: 6.8.0<br>iOS: 6.8.0</td></tr><tr><td>Smart Geofences</td><td></td><td>Android: 6.8.0<br>iOS: 6.8.0</td></tr></tbody></table>


# Data Dictionary

This data dictionary provides a comprehensive overview of all properties and data elements available across the different (sub)modules of our SDK.

For each field and property, it includes a concise explanation of:

* What the field represents
* The type and structure of the data

This ensures clarity and consistency when working with SDK-generated data across teams and integrations.

{% hint style="info" %}
For a more detailed exploration of each module including available methods, functions, and technical implementation details please refer to the Platform Reference documentation.
{% endhint %}

<details>

<summary>Core SDK</summary>

</details>

<details>

<summary>Event Timeline API</summary>

</details>

<details>

<summary>Driving Insights API</summary>

</details>

<details>

<summary>User Context API</summary>

</details>

<details>

<summary>Smart Geofences API</summary>

</details>




---

[Next Page](/llms-full.txt/1)

