Docs › Get started
Measure your app
Count the screens and events of your own iOS and Android app with Nvivio's SDK, without cookies or device identifiers, in the same dashboard as your website.
Nvivio measures your own iOS and Android app the way it measures your website: which screens people use, what they do there, which version of your app and of iOS or Android they have, on which phones, and from which country. Without cookies, without device identifiers and with your data in Frankfurt. Your app is a part of your site, so you see your website and your apps in one dashboard, together or each on its own.
Do you need the SDK?
| Your app | What measures it |
|---|---|
| A website, web app or installable web app (PWA) | The script: no SDK. |
| Pages of your own site shown inside your app (a web view) | The script on those pages: they count like any other visit. |
| A native iOS or iPadOS app (Swift, SwiftUI, UIKit) | The Swift package. |
| A native Android app (Kotlin, Java, Jetpack Compose) | The Android library. |
| React Native, Flutter, Unity or another framework | Not as a package yet: the iOS and Android SDK through a small native module, or the requests yourself. |
The SDKs are small, open source (Apache 2.0) and have no dependencies: nvivio-swift and nvivio-android.
What the SDK sends
| What | Example | Where it comes from |
|---|---|---|
| Screen views and the time on each screen | Checkout, 37 s |
Nvivio.screen("Checkout") in your app |
| Your own events, with up to 20 properties | Purchase, plan: pro |
Nvivio.event(…) in your app |
| Your app’s bundle ID and version | com.acme.shop, 2.3.1 |
the app itself |
| The platform and its version | iOS 26.1, Android 16 | the system |
| Phone or tablet, and the screen’s width | phone, 402 | the system |
| The device model | iPhone 13, Samsung SM-S921B | the system |
| The language | nl-BE |
the user’s settings |
| Country, region and city | Belgium, Flanders, Ghent | worked out from the IP address on our server, which then forgets the address |
The SDK never sends or reads the advertising identifier (IDFA, Google’s advertising ID), the identifier for vendors, Android’s ID, serial numbers, push tokens or anything that identifies a person. The device model is shared by many people: it is there to show which phones to test on, Nvivio never uses it to tell visitors apart, and a report only shows a model once five visitors used it. It stores nothing on the phone: no files, no settings, no cookies. Events wait in memory and are sent in small batches; when the app closes before it could send them, they are gone, and so is an event that could not be sent within an hour.
Add your app
- In the dashboard, open Settings › Parts of the site that belongs to your app, and choose + App.
- Give it a name and its bundle ID (iOS, as in App Store Connect) and/or package name (Android, as in Google Play), such as
com.acme.shop. One app can have both, and several apps are several parts. - Choose Save.
Below your parts, Add the SDK to your app then shows the steps below for iOS and Android, with your site’s code already filled in and a copy button for each piece of code. They are also on Settings › Installation, under By platform › iOS / Android › Show the code for your app.
Rather have your AI assistant do it? On the iOS or the Android tab, choose Copy the prompt for your AI and paste it into Claude Code, Cursor or the assistant in Xcode or Android Studio. That prompt has the steps for that one platform, your site’s code and the apps you registered; your assistant tells you what is left for you, such as registering a bundle ID it finds in your project (Install with your AI assistant).
Events from an app whose bundle ID the site does not list are refused, as events from a domain the site does not list are. Have an app but no website? Add a site with the domain of your app’s privacy policy or support page: the Website part then simply stays empty.
iOS
The Swift package works on iOS and iPadOS 15 and later. In Xcode, choose File › Add Package Dependencies… and enter:
https://github.com/montobleu/nvivio-swift
Start it once, when your app starts, with your site’s code (it is in Settings › Installation):
import Nvivio
@main
struct ShopApp: App {
init() {
Nvivio.start(site: "NV-XXXXXXX")
}
var body: some Scene { WindowGroup { ContentView() } }
}
Name your screens. In SwiftUI with the modifier, in UIKit from viewDidAppear:
CheckoutView()
.nvivioScreen("Checkout")
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Nvivio.screen("Checkout")
}
Send an event when something happens that matters:
Nvivio.event("Purchase", ["plan": "pro", "items": 3])
The package carries its own privacy manifest (PrivacyInfo.xcprivacy): no tracking, no tracking domains, and none of the APIs Apple asks a reason for. It reads the device model with uname, which is not one of them.
Android
The library works on Android 6 (API 23) and later and needs only the INTERNET permission. It comes from JitPack: add that repository once, in settings.gradle.kts,
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}
and the library to your app module’s build.gradle.kts:
dependencies {
implementation("com.github.montobleu:nvivio-android:1.0.0")
}
Start it once, in your Application:
class ShopApp : Application() {
override fun onCreate() {
super.onCreate()
Nvivio.start(this, "NV-XXXXXXX")
}
}
Name your screens and send events:
Nvivio.screen("Checkout") // in onResume, or a LaunchedEffect in Compose
Nvivio.event("Purchase", mapOf("plan" to "pro", "items" to 3))
With Nvivio.start(this, "NV-XXXXXXX", trackActivities = true) every activity counts as a screen by itself, named after its class (CheckoutActivity is Checkout).
Screens, events and links
- Screens are names you choose, like
CheckoutorSettings/Privacy. Use names, not data: an order number in a screen name makes every order a screen of its own. Showing the same screen again right away counts once. - Time on a screen is the time it was in front, without the time your app spent in the background.
- Events follow the rules of custom events: a name of up to 64 characters and up to 20 properties.
- Revenue: an in-app purchase sends its amount as the property
revenue, for example"4.99 EUR"; without a currency it is in your site’s (Revenue). - Deep links. Pass the link that opened your app to
Nvivio.open(url). Itsutm_source,utm_medium,utm_campaign,utm_termandutm_contentthen count for the visit, so a campaign that sends people into your app shows under Sources like any other. - Excluded screens. The rules under Settings › Exclude visits › Pages and screens hold for your app’s screens too: nothing on a screen they match counts (Exclude pages).
Your website and your apps together
One site can hold your website, your web app and your apps for iOS and Android, each as a part. The numbers of all of them are in one dashboard with the same definitions, so you can compare them or look at one: how many people use the app next to the website, which screens matter most, where campaigns lead.
What Nvivio does not do is recognise one person across devices or between your website and your app. That would take an identifier or a login, and Nvivio uses neither: it is why your app and your website need no consent prompt for Nvivio. Someone who visits your website on a laptop and opens your app on a phone counts as two visitors.
How people are counted
As on your website: the visitor is a code our server makes from the IP address and what the SDK sends about the phone (platform, its version, phone or tablet, your app’s version, but never the device model), with a salt that changes every day and is thrown away after 26 hours. It differs per site and per day, so nobody, us included, can follow a phone across apps or from one day to the next. The IP address itself is never stored.
Every launch of your app also gets a random code that lives only in the app’s memory. It keeps one launch together as one visit, also when the phone moves from Wi-Fi to mobile data on the way. Someone who uses your app in the morning and in the evening on the same network is one visitor with two visits that day; on another day, or on another network, a new one, as with the script without cookies.
In the dashboard
- The switch next to the period shows your app as a part: everything, your website or your app. On Live it shows who is in your app now, and on which screen.
- With only your app chosen, Pages is called Screens. Your app’s bundle ID is its domain.
- Device and browser gets three tabs, App versions, OS versions and Models, and Reports has a report for each. Your app shows as the browser App.
- Models names an iPhone by its model (iPhone 13), an iPad, Mac or Vision Pro by its kind, and an Android phone by its maker and model as Android names it (Samsung SM-S921B, Google Pixel 8). Models with fewer than five visitors in the period are together in one row, Other models, so a rare phone never points to one person.
- Filters work as everywhere:
part:app,app_version:3.3.0,os_version:iOS 26.1,device_model:iPhone 13,path:/Checkout. Goals and funnels can use screens (/Checkout) and events. - In the API and MCP the dimensions are
app_version,os_versionanddevice_model; there too, rare models are one row with the valuenull.
Privacy and the stores
Consent. Your app needs a consent prompt for Nvivio where your website needs a cookie banner for it, and not where it does not: the SDK stores nothing on the phone and sends only what a browser sends with every request, plus the screen’s width, as the script does, and the device model. Do I need a cookie banner? applies to your app as it does to your website.
App Tracking Transparency. Not needed: Nvivio does not track in Apple’s sense. Nothing is linked with data from other companies, used for advertising or shared with data brokers, and the SDK never touches the advertising identifier.
App Store privacy details. In App Store Connect, under App Privacy, Nvivio’s SDK collects:
| Data type | Linked to the user | Used for tracking | Purpose |
|---|---|---|---|
| Usage Data › Product Interaction | No | No | Analytics |
| Location › Coarse Location (country, region and city from the IP address) | No | No | Analytics |
| Diagnostics › Other Diagnostic Data (your app’s version, the OS version and the device model) | No | No | Analytics |
Send more in event properties (an order total, a plan)? Add those data types yourself.
Google Play data safety. In the Data safety form, Nvivio’s SDK collects App activity › App interactions, Location › Approximate location and App info and performance › Diagnostics (your app’s version, the Android version and the device model), for Analytics. The data is not shared (Nvivio processes it for you), it is encrypted in transit, and it is optional when your app lets people turn it off. The IP address is only processed for a moment and is not collected.
Your privacy policy. For example:
We count how our app is used with Nvivio, a European analytics service: which screens are opened, which actions are taken, the version of the app and of the operating system, the model of the device, the language and the country. Nvivio stores nothing on your device, uses no advertising or device identifiers, and keeps no IP addresses; the data stays in the EU (Frankfurt). Nvivio works for us as a processor. You can turn this off under Settings › Privacy.
Letting people turn it off. Nvivio.isEnabled = false stops it at once and drops what is waiting. The SDK does not remember it, because it stores nothing: keep the choice in your app’s own settings and set it at every start.
Nvivio.isEnabled = UserDefaults.standard.object(forKey: "analytics") as? Bool ?? true
Children’s apps. Apps in the Kids category of the App Store or in Google Play’s Families program cannot use the SDK yet.
Other platforms
React Native and Flutter packages are on the way. Until then, a React Native or Flutter app can use the iOS and Android SDK through a small native module, or send the same requests itself: POST https://nviv.io/a with up to 20 events, a user agent that starts with Nvivio-, and nothing stored on the phone. The format, with every field and the rules the SDKs follow, is in the protocol.
When nothing arrives
- Turn on
Nvivio.debug = true: the SDK logs every request and the answer (the Xcode console, or Logcat with the tagNvivio). - 403, app does not belong to this site: the bundle ID or package name is not in Settings › Parts, or is spelled differently. Debug builds often add a suffix such as
.debug: add that one as well, or leave it out of your debug builds. - 404: the site code is wrong.
- Nothing is counted from a simulator or emulator in a data centre, or from automated tests that run there, as on the web.