Skip to main content
Userpilot iOS SDK enables you to capture user insights and deliver personalized in-app experiences in real time. With just a one-time setup, you can immediately begin leveraging Userpilot’s analytics and engagement features to understand user behaviors and guide their journeys in-app. This document provides a step-by-step walkthrough of the installation and initialization process, as well as instructions on using the SDK’s public APIs.

Prerequisites

Before you begin, ensure your project meets the following requirements:
RequirementVersion
.NET9.0 or higher
iOS Deployment Target13.0 or higher
Android Min SDKAPI 21 or higher

Installation

Install the Userpilot MAUI SDK β€” a single package that automatically includes the correct platform bindings:
<PackageReference Include="Userpilot.Maui" Version="0.0.1-beta.1" />
Or via the .NET CLI:
dotnet add package Userpilot.Maui

Option B β€” Project Reference (For Local Development)

Add a project reference to the appropriate binding project in your .csproj file:
<!-- Android binding -->
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
  <ProjectReference Include="path/to/Userpilot.Android.Binding/Userpilot.Android.Binding.csproj" />
</ItemGroup>

<!-- iOS binding -->
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
  <ProjectReference Include="path/to/Userpilot.MaciOS.Binding/Userpilot.MaciOS.Binding.csproj" />
</ItemGroup>
  1. For Android, add the required NuGet dependencies:
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
  <!-- Firebase Messaging β€” provides Firebase infrastructure classes.
       The native Userpilot SDK handles the messaging service, push display, and
       permission requests. This NuGet provides the underlying Firebase classes. -->
  <PackageReference Include="Xamarin.Firebase.Messaging" Version="124.1.1" />

  <!-- Required by native SDK (material components) -->
  <PackageReference Include="Xamarin.Google.Android.Material" Version="1.12.0.5" />
  <PackageReference Include="Xamarin.Kotlin.StdLib" Version="2.2.0.1" />

  <!-- ViewBinding - required by Userpilot SDK for UI layout inflation -->
  <PackageReference Include="Xamarin.AndroidX.DataBinding.ViewBinding" Version="8.5.0.1" />
</ItemGroup>
Note: You may need to pin additional AndroidX packages to resolve duplicate class errors. See the sample .csproj for the full list of recommended package versions.

Initializing

To use Userpilot, initialize the SDK during your app’s launch sequence. Replace <APP_TOKEN> with your Application Token, which can be fetched from your Environments Page.

iOS Initialization

On iOS, you must enable automatic push notification configuration in your AppDelegate.cs before the SDK is set up. This enables method swizzling for push notification handling.
// Platforms/iOS/AppDelegate.cs

using Foundation;

[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

    public override bool FinishedLaunching(UIKit.UIApplication application, NSDictionary? launchOptions)
    {
        // Enable automatic push notification configuration (method swizzling).
        // MUST be called early in app launch, BEFORE Userpilot SDK setup.
        Userpilot.UserpilotSdk.EnableAutomaticPushConfig();

        return base.FinishedLaunching(application, launchOptions);
    }
}
Then initialize the SDK from your shared code (e.g., a button handler or app startup):
bool success = Userpilot.UserpilotSdk.Setup(
    token: "<APP_TOKEN>",
    isLoggingEnabled: true,
    isDisableRequestPushPermission: false
);

Android Initialization

On Android, initialize the SDK by passing the application context:
var context = Android.App.Application.Context;

bool success = Userpilot.UserpilotSdk.Setup(
    context,
    token: "<APP_TOKEN>",
    isLoggingEnabled: true,
    isUseInAppBrowserEnabled: false,
    isDisableRequestPushNotificationsPermissions: false
);

Cross-Platform Initialization Example

You can wrap initialization with preprocessor directives for a unified flow:
var token = "<APP_TOKEN>";

#if ANDROID
    var context = Android.App.Application.Context;
    bool success = Userpilot.UserpilotSdk.Setup(
        context, token,
        isLoggingEnabled: true,
        isUseInAppBrowserEnabled: false,
        isDisableRequestPushNotificationsPermissions: false
    );
#elif IOS
    bool success = Userpilot.UserpilotSdk.Setup(
        token,
        isLoggingEnabled: true,
        isDisableRequestPushPermission: false
    );
#endif

Using the SDK

Once initialized, the SDK provides straightforward APIs for identifying users, tracking events, and screen views.

Identifying Users (Required)

This API identifies unique users and sets their properties. Once identified, all subsequent tracked events and screens will be attributed to that user. Recommended Usage:
  • On user authentication (login): Immediately call Identify when a user signs in.
  • On app launch for authenticated users: If the user has a valid session, call Identify at app launch.
  • Upon property updates: Whenever user or company properties change.
// Build user properties as a JSON string
var userProperties = System.Text.Json.JsonSerializer.Serialize(new
{
    name = "John Doe",
    email = "user@example.com",
    created_at = "2019-10-17",
    role = "Admin"
});

// Build company properties as a JSON string
var companyProperties = System.Text.Json.JsonSerializer.Serialize(new
{
    id = "<COMPANY_ID>",
    name = "Acme Labs",
    created_at = "2019-10-17",
    plan = "Free"
});

bool success = Userpilot.UserpilotSdk.Identify("<USER_ID>", userProperties, companyProperties);
Properties Guidelines:
  • Key id is required in company properties to identify a unique company.
  • Userpilot supports String, Numeric, and Date types.
  • Send date values in ISO 8601 format.
  • If you plan to use Userpilot’s localization features, pass user property locale_code with a value in ISO 639-1 format.
  • Use reserved property keys:
    • email for the user’s email.
    • name for the user’s or company’s name.
    • created_at for the user’s or company’s signup date.
Note: Ensure the User ID source is consistent across Web, Android, and iOS. While properties are optional, setting them enhances Userpilot’s segmentation capabilities.

Tracking Screens (Required)

Calling Screen is crucial for unlocking Userpilot’s core engagement and analytics capabilities. When a user navigates to a particular screen, invoking Screen records that view and triggers any eligible in-app experiences.
Userpilot.UserpilotSdk.Screen("Profile");

Tracking Events

Log any meaningful action the user performs. Events can be button clicks, form submissions, or any custom activity you want to analyze. Optionally, pass metadata as a JSON string.
var properties = System.Text.Json.JsonSerializer.Serialize(new
{
    itemId = "sku_456",
    price = 29.99
});

Userpilot.UserpilotSdk.Track("Added to Cart", properties);

Anonymous Users

If a user is not authenticated, call Anonymous to track events without a user ID. This is useful for pre-signup flows or guest sessions.
Userpilot.UserpilotSdk.Anonymous();
Note: Anonymous users count towards your Monthly Active Users usage. Consider your MAU limits before using this API.

Trigger Experiences

Triggers a specific experience programmatically using its unique ID. This API allows you to manually initiate an experience within your application.
Userpilot.UserpilotSdk.TriggerExperience("<EXPERIENCE_ID>");

End Experience

Ends the currently active experience (if any).
Userpilot.UserpilotSdk.EndExperience();

Logging Out

When a user logs out, call Logout to clear the current user context. This ensures subsequent events are no longer associated with the previous user.
Userpilot.UserpilotSdk.Logout();

Configurations (Optional)

The following parameters can be passed during SDK initialization via the Setup method:
ParameterTypeDescription
loggingBooleanEnable or Disable logs for SDK

Default: false
disableRequestPushNotificationsPermissionBooleanDisable request push notifications permission by SDK.

Default: false
useInAppBrowserBooleanConfiguration to indicate when to open the URL inside CustomTabsIntent or not. (Android only)

Default: false

Callbacks

If you have additional configuration needs, you can register callback delegates to receive navigation events, analytics events, and experience lifecycle events.
Important (iOS): On iOS, callback instances must be stored in static fields to prevent the .NET garbage collector from finalizing them. The native Swift SDK holds weak Objective-C references to these objects, which the .NET GC cannot see.
Handles deep link routes triggered by Userpilot experiences (e.g., when a user taps a CTA link inside an experience).

iOS

// Define the callback class
public class AppNavigationCallbackiOS : Userpilot.UserpilotNavigationCallback
{
    public override void OnNavigate(string url)
    {
        Console.WriteLine($"[Userpilot] Navigate to: {url}");
        // Handle navigation to the URL
    }
}

// Store in a static field to prevent GC
private static AppNavigationCallbackiOS? _navCallback;

// Register the callback (after SDK setup)
_navCallback = new AppNavigationCallbackiOS();
Userpilot.UserpilotSdk.SetNavigationCallback(_navCallback);

Android

// Define the callback class
public class AppNavigationCallback : Java.Lang.Object, Userpilot.UserpilotSdk.Companion.INavigationCallback
{
    public void OnNavigate(string url)
    {
        Android.Util.Log.Info("Userpilot", $"Navigate to: {url}");
        // Handle navigation to the URL
    }
}

// Register the callback (BEFORE SDK setup to capture initialization events)
Userpilot.UserpilotSdk.SetNavigationCallback(new AppNavigationCallback());

Analytics Callback

Receives callbacks whenever the SDK tracks an event, screen, or identifies a user. Useful for integrating with another analytics tool or logging events for debugging.

iOS

public class AppAnalyticsCallbackiOS : Userpilot.UserpilotAnalyticsCallback
{
    public override void OnAnalyticTracked(string analytic, string value, NSDictionary? properties)
    {
        Console.WriteLine($"[Userpilot] {analytic}: {value}");
    }
}

private static AppAnalyticsCallbackiOS? _analyticsCallback;

_analyticsCallback = new AppAnalyticsCallbackiOS();
Userpilot.UserpilotSdk.SetAnalyticsCallback(_analyticsCallback);

Android

public class AppAnalyticsCallback : Java.Lang.Object, Userpilot.UserpilotSdk.Companion.IAnalyticsCallback
{
    public void OnAnalyticTracked(string analytic, string value, string properties)
    {
        Android.Util.Log.Info("Userpilot", $"{analytic}: {value} | {properties}");
    }
}

Userpilot.UserpilotSdk.SetAnalyticsCallback(new AppAnalyticsCallback());

Experience Callback

Receives callbacks when Userpilot experiences (flows, surveys, NPS) start, complete, are dismissed, or progress through steps. Experience Types: Flow, Survey, NPS Experience States: Started, Completed, Dismissed, Skipped, Submitted

iOS

public class AppExperienceCallbackiOS : Userpilot.UserpilotExperienceCallback
{
    public override void OnExperienceStateChanged(
        string experienceType, int experienceId, string experienceState)
    {
        Console.WriteLine($"[Userpilot] {experienceType} #{experienceId} -> {experienceState}");
    }

    public override void OnExperienceStepStateChanged(
        string experienceType, NSNumber experienceId, int stepId,
        string stepState, int step, int totalSteps)
    {
        Console.WriteLine($"[Userpilot] {experienceType} #{experienceId} step {step}/{totalSteps} -> {stepState}");
    }
}

private static AppExperienceCallbackiOS? _experienceCallback;

_experienceCallback = new AppExperienceCallbackiOS();
Userpilot.UserpilotSdk.SetExperienceCallback(_experienceCallback);

Android

public class AppExperienceCallback : Java.Lang.Object, Userpilot.UserpilotSdk.Companion.IExperienceCallback
{
    public void OnExperienceStateChanged(
        string experienceType, int experienceId, string experienceState)
    {
        Android.Util.Log.Info("Userpilot", $"{experienceType} #{experienceId} -> {experienceState}");
    }

    public void OnExperienceStepStateChanged(
        string experienceType, int experienceId, int stepId,
        string stepState, int step, int totalSteps)
    {
        Android.Util.Log.Info("Userpilot", $"{experienceType} #{experienceId} step {step}/{totalSteps} -> {stepState}");
    }
}

Userpilot.UserpilotSdk.SetExperienceCallback(new AppExperienceCallback());
Note (Android): Register callbacks before calling Setup() so that events fired during initialization are not lost.
Note (iOS): Register callbacks after calling Setup(). Store callback instances in static fields to prevent GC from collecting them.

Push Notifications

Userpilot SDK supports push notifications on both iOS and Android. This section covers the full setup required for each platform.

iOS Push Notification Setup

It is recommended to configure your Android push settings in the Userpilot Settings Studio before setting up push notifications in your app. To obtain the required keys and configuration details, please refer to the iOS Push Notification Guide.

1. Enable Push Notifications Capability

Add the aps-environment entitlement to your Entitlements.plist file at Platforms/iOS/Entitlements.plist:
<?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>
        <!-- Push Notifications capability -->
        <key>aps-environment</key>
        <string>development</string>
    </dict>
</plist>
Note: Change development to production when publishing to the App Store.

2. Enable Background Remote Notifications

Add the remote-notification background mode to your Info.plist at Platforms/iOS/Info.plist:
<!-- Background Modes: enable remote (push) notifications delivery in background -->
<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

3. Configure Code Signing in the .csproj

Add the signing configuration to your .csproj file to use the provisioning profile you created:
<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
    <!-- Team and signing identity -->
    <CodesignKey>Apple Development: Your Name (XXXXXXXXXX)</CodesignKey>
    <CodesignProvision>Your Provisioning Profile Name</CodesignProvision>
    <TeamId>YOUR_TEAM_ID</TeamId>
    <!-- Entitlements with aps-environment for push notifications -->
    <CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
</PropertyGroup>

4. Enable Automatic Push Configuration

In your AppDelegate.cs, call EnableAutomaticPushConfig() before the SDK is set up. This enables method swizzling so the Userpilot SDK automatically handles:
  • Registering for remote notifications
  • Receiving the device push token
  • Handling notification responses
// Platforms/iOS/AppDelegate.cs

using Foundation;

[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

    public override bool FinishedLaunching(UIKit.UIApplication application, NSDictionary? launchOptions)
    {
        // MUST be called BEFORE Userpilot SDK setup
        Userpilot.UserpilotSdk.EnableAutomaticPushConfig();

        return base.FinishedLaunching(application, launchOptions);
    }
}

Android Push Notification Setup

It is recommended to configure your Android push settings in the Userpilot Settings Studio before setting up push notifications in your app. To obtain the required keys and configuration details, please refer to the Android Push Notification Guide.

1. Firebase Project Configuration

Follow the steps in the official Google documentation on How to add Firebase to your project.

2. Register google-services.json in the .csproj

Add the GoogleServicesJson build action in your .csproj so .NET MAUI processes it and generates the required Android string resources (google_app_id, gcm_defaultSenderId, etc.):
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
  <GoogleServicesJson Include="Platforms\Android\google-services.json" />
</ItemGroup>

3. Add Add the Userpilot Firebase Messaging Service and Push Notification Permissions

Add the required permissions and the Userpilot Firebase Messaging Service to your AndroidManifest.xml at Platforms/Android/AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:supportsRtl="true">
        <!-- Userpilot push notification service (handles Firebase messages) -->
        <service
            android:name="com.userpilot.pushNotifications.UserpilotFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    </application>

    <!-- Required for push notifications on Android 13+ (API 33) -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>

4. Configure Notification Appearance

Create a resource file at Platforms/Android/Resources/values/userpilot.xml to customize the notification appearance:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Small icon displayed in the notification bar -->
    <drawable name="userpilot_notification_small_icon">@drawable/userpilot_ic_notification</drawable>

    <!-- Accent color applied to notification UI elements -->
    <color name="userpilot_notification_color">#6765E8</color>

    <!-- Lock screen visibility (true = visible) -->
    <bool name="userpilot_notification_channel_lock_screen_visibility">true</bool>

    <!-- Enable notification lights -->
    <bool name="userpilot_notification_channel_enable_light">true</bool>

    <!-- Enable vibration -->
    <bool name="userpilot_notification_channel_enable_vibration">true</bool>

    <!-- Notification channel configuration -->
    <string name="userpilot_notification_channel_id">com.userpilot.general.channel</string>
    <string name="userpilot_notification_channel_name">General</string>
    <string name="userpilot_notification_channel_description">Default channel used for app messages</string>

    <!-- Importance level (0 = NONE, 5 = MAX) -->
    <integer name="userpilot_notification_channel_importance">3</integer>

    <!-- Push notification deep link key -->
    <string name="userpilot_push_notification">userpilot_push_notification</string>
</resources>

5. Handle Push Notification Intents in MainActivity

Configure your MainActivity.cs to forward push notification intents to the Userpilot SDK. Use LaunchMode.SingleTop to ensure deep links are routed to the existing activity instance.
// Platforms/Android/MainActivity.cs

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;

[Activity(
    Theme = "@style/Maui.SplashTheme",
    MainLauncher = true,
    LaunchMode = LaunchMode.SingleTop,
    ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
                           ConfigChanges.UiMode | ConfigChanges.ScreenLayout |
                           ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
[IntentFilter(
    new[] { Intent.ActionView },
    Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
    DataScheme = "userpilot_push_notification",
    DataHost = "sdk")]
public class MainActivity : MauiAppCompatActivity
{
    protected override void OnCreate(Bundle? savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        // Handle launch intent (cold start from push notification)
        if (Intent != null)
        {
            Userpilot.UserpilotSdk.HandleIntent(Intent);
        }
    }

    protected override void OnNewIntent(Intent? intent)
    {
        base.OnNewIntent(intent);

        // Handle intent when app is already running (warm start from push notification)
        if (intent != null)
        {
            Userpilot.UserpilotSdk.HandleIntent(intent);
        }
    }
}

Using ProGuard / R8 (Android)

If you are using R8 or ProGuard for code shrinking, add the following rules to your ProGuard configuration file (e.g., Platforms/Android/proguard.cfg):
# Keep Userpilot SDK classes
-keep class com.userpilot.** { *; }
-keep class com.microsoft.mauiuserpilot.** { *; }

# Keep Kotlin classes needed by Userpilot
-keep class kotlin.** { *; }
-keep class kotlinx.** { *; }

# Keep MAUI classes
-keep class crc*.** { *; }
-keep class mono.** { *; }
-keep class android.runtime.** { *; }

# Keep callbacks and interfaces
-keepclassmembers class * implements com.microsoft.mauiuserpilot.UserpilotSdk$Companion$NavigationCallback {
    <methods>;
}
-keepclassmembers class * implements com.microsoft.mauiuserpilot.UserpilotSdk$Companion$AnalyticsCallback {
    <methods>;
}
-keepclassmembers class * implements com.microsoft.mauiuserpilot.UserpilotSdk$Companion$ExperienceCallback {
    <methods>;
}

# Don't warn about missing classes in libraries
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn org.conscrypt.**
-dontwarn com.squareup.moshi.**
-dontwarn androidx.**

# Keep OkHttp and Moshi for network calls
-keep class com.squareup.moshi.** { *; }
-keep class okhttp3.** { *; }
-keep class okio.** { *; }

# Keep Coil image loading library (used by Userpilot SDK for experience images)
-keep class coil.** { *; }
-keep class coil.base.** { *; }
-keep class coil.util.** { *; }
-keep class coil.request.** { *; }
-keep class coil.decode.** { *; }
-keep class coil.fetch.** { *; }
-keep class coil.map.** { *; }
-keep class coil.memory.** { *; }
-keep class coil.network.** { *; }
-keep class coil.size.** { *; }
-keep class coil.transform.** { *; }
-keep class coil.transition.** { *; }
-keep class coil.disk.** { *; }
-dontwarn coil.**

# Keep AndroidSVG (used by Coil for SVG support)
-keep class com.caverock.androidsvg.** { *; }
-dontwarn com.caverock.androidsvg.**

# Firebase
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**

# Allow duplicate definitions - R8 will use the first one it finds
-ignorewarnings
Then reference it in your .csproj:
<ProguardConfiguration Include="Platforms\Android\proguard.cfg" />

🎬 Sample App

The sample/ directory contains a full .NET MAUI sample app that demonstrates all Userpilot SDK APIs from a single page. The sample includes:

πŸ“ Documentation

Full documentation is available at Userpilot Documentation.

πŸ“„ License

This project is licensed under the MIT License. See LICENSE for more information.