Using the Flutter SDK

This guide will help you implement Linkrunner functionality in your Flutter application.

Initializing the SDK

Initialize the Linkrunner SDK when your app starts:

You can find your project token here.

SDK Signing Parameters (Optional)

For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization:

  • secretKey: A unique secret key used for request signing and authentication
  • keyId: A unique identifier for the key pair used in the signing process

You can find your project token, secret key, and key ID here.

Note: The initialization method doesn’t return any value. To get attribution data and deeplink information, use the getAttributionData method.

import 'package:linkrunner/linkrunner.dart';

Future<void> initLinkrunner() async {
  try {
    // Initialize with your project token and SDK signing parameters
    await Linkrunner.init(
      'YOUR_PROJECT_TOKEN',
      'YOUR_SECRET_KEY', // Optional: Required for SDK signing
      'YOUR_KEY_ID', // Optional: Required for SDK signing
    );
    print('Linkrunner initialized');
  } catch (e) {
    print('Error initializing Linkrunner: $e');
  }
}

// Call this in your app's initialization
@override
void initState() {
  super.initState();
  initLinkrunner();
}

User Registration

Call the signup method once after the user has completed your app’s onboarding process:

It is strongly recommended to use the integrated platform’s identify function to set a persistent user_id once it becomes available (typically after signup or login).

If the platform’s identifier function is not called, you must provide a user identifier for Mixpanel, PostHog, and Amplitude integration.

  • mixpanelDistinctId for Mixpanel
  • amplitudeDeviceId for Amplitude
  • posthogDistinctId for PostHog
Future<void> onSignup() async {
  try {
    await Linkrunner.signup(
      userData: LRUserData(
        id: '123', // Required: User ID
        name: 'John Doe', // Optional
        phone: '9876543210', // Optional
        email: 'user@example.com', // Optional
        // These properties are used to track reinstalls
        userCreatedAt: '2024-01-01T00:00:00Z', // Optional
        isFirstTimeUser: true, // Optional
        mixpanelDistinctId: 'mixpanelDistinctId', // Optional - Mixpanel Distinct ID
        amplitudeDeviceId: 'amplitudeDeviceId', // Optional - Amplitude User ID
        posthogDistinctId: 'posthogDistinctId', // Optional - PostHog Distinct ID
      ),
      data: {}, // Optional: Any additional data
    );
    print('Signup successful');
  } catch (e) {
    print('Error during signup: $e');
  }
}

Getting Attribution Data

To get attribution data and deeplink information for the current installation, use the getAttributionData function:

Future<void> getAttributionInfo() async {
  try {
    final attributionData = await Linkrunner.getAttributionData();
    print('Attribution data: $attributionData');
  } catch (e) {
    print('Error getting attribution data: $e');
  }
}

The getAttributionData function returns an AttributionData object with the following structure:

class AttributionData {
  final String? deeplink;            // Optional: The deep link URL that led to app installation
  final CampaignData campaignData;   // Required: Campaign information
}

class CampaignData {
  final String id;                   // Required: Campaign ID
  final String name;                 // Required: Campaign name
  final String? adNetwork;           // Optional: "META" | "GOOGLE" | null
  final String? groupName;           // Optional: Campaign group name
  final String? assetGroupName;      // Optional: Asset group name
  final String? assetName;           // Optional: Asset name
  final String type;                 // Required: Campaign type ("ORGANIC" | "INORGANIC")
  final String installedAt;          // Required: Installation timestamp
  final String storeClickAt;         // Required: Store click timestamp
}

Example response:

{
  "deeplink": "myapp://product/123",
  "campaign_data": {
    "id": "camp_123",
    "name": "Summer Sale 2024",
    "ad_network": "META",
    "group_name": "iOS Campaign",
    "asset_group_name": "Product Catalog",
    "asset_name": "Banner Ad 1",
    "type": "INORGANIC",
    "installed_at": "2024-03-20T10:30:00Z",
    "store_click_at": "2024-03-20T10:29:45Z"
  }
}

Setting User Data

Call setUserData each time the app opens and the user is logged in:

Future<void> setUserData() async {
  try {
    await Linkrunner.setUserData(
      userData: LRUserData(
        id: '123', // Required: User ID
        name: 'John Doe', // Optional
        phone: '9876543210', // Optional
        email: 'user@example.com', // Optional
        mixpanelDistinctId: 'mixpanelDistinctId', // Optional - Mixpanel Distinct ID
        amplitudeDeviceId: 'amplitudeDeviceId', // Optional - Amplitude User ID
        posthogDistinctId: 'posthogDistinctId', // Optional - PostHog Distinct ID
      ),
    );
    print('User data set successfully');
  } catch (e) {
    print('Error setting user data: $e');
  }
}

Set Additional Data

Use the setAdditionalData method to set CleverTap ID:

Future<void> setIntegrationData() async {
  try {
    await Linkrunner.setAdditionalData(
      integrationData: {
        'clevertapId': 'YOUR_CLEVERTAP_USER_ID', // CleverTap user identifier
      },
    );
    print('CleverTap ID set successfully');
  } catch (e) {
    print('Error setting CleverTap ID: $e');
  }
}

Parameters for Linkrunner.setAdditionalData

  • clevertapId: String (optional) - CleverTap user identifier

This method allows you to connect user identities across different analytics and marketing platforms.

Tracking Custom Events

Track custom events in your app:

Future<void> trackEvent() async {
  try {
    await Linkrunner.trackEvent(
      eventName: 'purchase_initiated', // Event name
      eventData: { // Optional: Event data
        'product_id': '12345',
        'category': 'electronics',
      },
    );
    print('Event tracked successfully');
  } catch (e) {
    print('Error tracking event: $e');
  }
}

Revenue Tracking

Capturing Payments

Track payment information:

Future<void> capturePayment() async {
  try {
    await Linkrunner.capturePayment(
      capturePayment: LRCapturePayment(
        amount: 99.99, // Required: Payment amount
        userId: 'user123', // Required: User identifier
        paymentId: 'payment456', // Optional: Unique payment identifier
        type: PaymentType.FIRST_PAYMENT, // Optional: Payment type
        status: PaymentStatus.PAYMENT_COMPLETED, // Optional: Payment status
      ),
    );
    print('Payment captured successfully');
  } catch (e) {
    print('Error capturing payment: $e');
  }
}

Future<void> removePayment() async {
  try {
    await Linkrunner.removePayment(
      removePayment: LRRemovePayment(
        userId: 'user123', // Either userId or paymentId must be provided
        paymentId: 'payment456', // Optional: Unique payment identifier
      ),
    );
    print('Payment removed successfully');
  } catch (e) {
    print('Error removing payment: $e');
  }
}

Parameters for LRCapturePayment

  • amount: double (required) - The payment amount
  • userId: String (required) - Identifier for the user making the payment
  • paymentId: String (optional) - Unique identifier for the payment
  • type: PaymentType (optional) - Type of payment. Available options:
    • FIRST_PAYMENT - First payment made by the user
    • WALLET_TOPUP - Adding funds to a wallet
    • FUNDS_WITHDRAWAL - Withdrawing funds
    • SUBSCRIPTION_CREATED - New subscription created
    • SUBSCRIPTION_RENEWED - Subscription renewal
    • ONE_TIME - One-time payment
    • RECURRING - Recurring payment
    • DEFAULT_PAYMENT - Default type (used if not specified)
  • status: PaymentStatus (optional) - Status of the payment. Available options:
    • PAYMENT_INITIATED - Payment has been initiated
    • PAYMENT_COMPLETED - Payment completed successfully (default if not specified)
    • PAYMENT_FAILED - Payment attempt failed
    • PAYMENT_CANCELLED - Payment was cancelled

Enhanced Privacy Controls

The SDK offers options to enhance user privacy:

// Enable PII (Personally Identifiable Information) hashing
Linkrunner.enablePIIHashing(true);

When PII hashing is enabled, sensitive user data like name, email, and phone number are hashed using SHA-256 before being sent to Linkrunner servers.

Function Placement Guide

FunctionWhere to PlaceWhen to Call
Linkrunner.initApp initializationOnce when app starts
Linkrunner.getAttributionDataAttribution data handling flowWhenever the attribution data is needed
Linkrunner.setAdditionalDataIntegration codeWhen third-party integration IDs are available
Linkrunner.signupOnboarding flowOnce after user completes onboarding
Linkrunner.setUserDataAuthentication logicEvery time app opens with logged-in user
Linkrunner.triggerDeeplinkAfter navigation initOnce after navigation is ready
Linkrunner.trackEventThroughout appWhen specific user actions occur
Linkrunner.capturePaymentPayment processingWhen user makes a payment
Linkrunner.removePaymentRefund flowWhen payment needs to be removed

Complete Example

Here’s a simplified example showing how to integrate Linkrunner in a Flutter app:

You can find your project token here.

import 'package:flutter/material.dart';
import 'package:linkrunner/linkrunner.dart';

final linkrunner = LinkRunner();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Linkrunner Demo',
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _initialized = false;

  @override
  void initState() {
    super.initState();
    _initializeLinkrunner();
  }

  Future<void> _initializeLinkrunner() async {
    try {
      final initData = await Linkrunner.init('YOUR_PROJECT_TOKEN');
      setState(() {
        _initialized = true;
      });
      // Handle any deeplinks after navigation is set up
      WidgetsBinding.instance.addPostFrameCallback((_) {
        triggerDeeplink();
      });
    } catch (e) {
      print('Error initializing Linkrunner: $e');
    }
  }

  Future<void> triggerDeeplink() async {
    try {
      await Linkrunner.triggerDeeplink();
    } catch (e) {
      print('Error triggering deeplink: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Linkrunner Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Linkrunner ${_initialized ? 'Initialized' : 'Initializing...'}'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                await Linkrunner.trackEvent('button_clicked');
              },
              child: Text('Track Custom Event'),
            ),
          ],
        ),
      ),
    );
  }
}

Support

If you encounter issues during integration, contact us at darshil@linkrunner.io.