Pular para o conteúdo principal

Introduction

The Nemu Flutter SDK (nemu_tracking_flutter) allows you to integrate Nemu’s attribution and Smart Links system directly into your Flutter application. With the SDK you can:
  • Capture the install source — know which campaign, source, or media the user came from (UTMs)
  • Process direct deep links — when the user clicks a Smart Link and the app is already installed
  • Process deferred deep links — when the user clicks a Smart Link, installs the app from the store, and opens it for the first time
  • Identify users — associate an ID from your system with the device to cross-reference attribution data
  • Query session history — retrieve the UTMs from the user’s last interaction
The SDK works automatically: once initialized, it intercepts deep links, detects whether it’s the app’s first launch, and performs attribution with the Nemu backend, without the need for manual configuration of each event.

Prerequisites

RequirementMinimum version
Flutter>= 3.10.0
Dart SDK>= 3.0.0 < 4.0.0
iOS12.0+
AndroidAPI 21+ (Android 5.0)
Private package: nemu_tracking_flutter is a restricted-access package. To install it, you need access to the Git repository provided by the Nemu support team. See the installation section below.

Installation

1. Access token configuration

The nemu_tracking_flutter package is private and hosted in a restricted-access Git repository. Before installing, you need to configure authentication.

Get your access

Contact the Nemu support team to receive access to the SDK repository:

2. Package installation

Add the SDK to your pubspec.yaml file:
dependencies:
  nemu_tracking_flutter:
    git:
      url: https://github.com/usenemu/sdk-app-tracking-flutter.git
      ref: main

Via local path (for development)

dependencies:
  nemu_tracking_flutter:
    path: ../sdk-app-tracking-flutter
Then run:
flutter pub get
All required dependencies (shared_preferences, flutter_secure_storage, app_links, etc.) are automatically installed as transitive dependencies of the SDK. You don’t need to add them manually to your project.

Native configuration

iOS

For iOS to forward Smart Links directly to the app (without opening the browser), you need to configure Associated Domains. In Xcode:
  1. Open the .xcworkspace project (at ios/Runner.xcworkspace)
  2. Select the Runner target
  3. Go to Signing & Capabilities
  4. Click + Capability and add Associated Domains
  5. Add the domain in the format:
applinks:your-domain.nemu.com.br
The exact domain will be provided by the Nemu team along with the credentials.

2. Configure the URI Scheme (required)

In the ios/Runner/Info.plist file, add your app’s URI scheme:
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourapp</string>
    </array>
  </dict>
</array>
Replace yourapp with the scheme defined in the Nemu dashboard for your Smart Link.
The URI scheme configured here must match the value passed in the uriScheme parameter during SDK initialization.

Android

For Smart Links to open the app directly, add intent filters to your main Activity. In the android/app/src/main/AndroidManifest.xml file, inside the main <activity> tag (the one containing android:name=".MainActivity"): App Links (Android Universal Links):
<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data
    android:scheme="https"
    android:host="your-domain.nemu.com.br" />
</intent-filter>
URI Scheme:
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="yourapp" />
</intent-filter>
Replace your-domain.nemu.com.br and yourapp with the values corresponding to your project.

2. Internet permission (usually already present)

Check that AndroidManifest.xml has the internet permission:
<uses-permission android:name="android.permission.INTERNET" />
In most Flutter projects, this permission is already included by default.

Initialization

SDK initialization should be done only once, as early as possible in the app lifecycle — ideally in the initState of your root widget or in the main function.

Configuration parameters

ParameterTypeRequiredDescription
apiKeyStringYesAPI key obtained from the Nemu dashboard
uriSchemeStringYesApp URI scheme (e.g.: "yourapp"). Must match the value configured in the Smart Link
trackingIdStringYesTracking ID associated with the app in the Nemu dashboard
isDebugModeboolNoEnables detailed logs in the console. Default: false
baseUrlString?NoOverrides the API base URL (only when isDebugMode is true)

Full initialization example

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

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    NemuTracking.instance.init(
      const NemuInitParams(
        apiKey: 'your-api-key',
        uriScheme: 'yourapp',
        trackingId: 'your-tracking-id',
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const HomeScreen(),
    );
  }
}

Using environment variables

To avoid exposing credentials directly in your code, use the flutter_dotenv package or define variables via --dart-define:
NemuTracking.instance.init(
  const NemuInitParams(
    apiKey: String.fromEnvironment('NEMU_API_KEY'),
    uriScheme: String.fromEnvironment('NEMU_URI_SCHEME'),
    trackingId: String.fromEnvironment('NEMU_TRACKING_ID'),
  ),
);
Run the app with:
flutter run \
  --dart-define=NEMU_API_KEY=your-api-key \
  --dart-define=NEMU_URI_SCHEME=yourapp \
  --dart-define=NEMU_TRACKING_ID=your-tracking-id

Using flutter_dotenv

Create a .env file at the project root:
NEMU_API_KEY=your-api-key
NEMU_URI_SCHEME=yourapp
NEMU_TRACKING_ID=your-tracking-id
import 'package:flutter_dotenv/flutter_dotenv.dart';

await dotenv.load();

NemuTracking.instance.init(
  NemuInitParams(
    apiKey: dotenv.env['NEMU_API_KEY']!,
    uriScheme: dotenv.env['NEMU_URI_SCHEME']!,
    trackingId: dotenv.env['NEMU_TRACKING_ID']!,
  ),
);
Add the .env file to your .gitignore to avoid committing credentials to the repository.

User identification

After the user logs into your app, associate their ID with the device. This allows Nemu to cross-reference attribution data with your user system.

Register user

import 'package:nemu_tracking_flutter/nemu_tracking_flutter.dart';

// After successful login
void onLoginSuccess(String userId) {
  NemuTracking.instance.setUserId(userId);
}
The SDK saves the ID locally and sends the association to the backend automatically.

Clear user on logout

void onLogout() {
  NemuTracking.instance.clearUserId();
}
clearUserId() only removes the local association. The attribution history on the backend is preserved.

The SDK automatically processes two types of deep links: Occur when the app is already installed and the user clicks a Smart Link. The operating system opens the app directly (via Universal Link / App Link or URI scheme). The flow is transparent:
  1. The user clicks the Smart Link
  2. The app opens with the URL
  3. The SDK processes the URL and registers the session
  4. Listeners registered via onDeepLink are notified with the data
Occur when the app is not installed. The user clicks the Smart Link, is redirected to the store, installs the app, and opens it for the first time. The SDK automatically detects this scenario on the first launch and delivers the attribution and deep link data via onDeepLink with isDeferred: true. Register a callback to receive deep link data as soon as it becomes available:
import 'package:flutter/material.dart';
import 'package:nemu_tracking_flutter/nemu_tracking_flutter.dart';

class _MyAppState extends State<MyApp> {
  VoidCallback? _unsubscribe;

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

    NemuTracking.instance.init(
      const NemuInitParams(
        apiKey: 'your-api-key',
        uriScheme: 'yourapp',
        trackingId: 'your-tracking-id',
      ),
    );

    _unsubscribe = NemuTracking.instance.onDeepLink((DeepLinkData data) {
      debugPrint('Deep link received: $data');

      if (data.deepLinkValue != null) {
        // Navigate to the corresponding screen
        // E.g.: data.deepLinkValue = "product/456"
        navigateTo(data.deepLinkValue!);
      }

      if (data.utms != null) {
        debugPrint('Source: ${data.utms!.source}');
        debugPrint('Campaign: ${data.utms!.campaign}');
      }

      debugPrint('Was deferred? ${data.isDeferred}');
    });
  }

  @override
  void dispose() {
    // Clean up listener on dispose
    _unsubscribe?.call();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const HomeScreen(),
    );
  }
}
Automatic replay: if a deep link was already processed before the listener was registered, the callback is fired immediately with the most recent data. This prevents the app from missing the initial deep link.

Imperative query (getDeepLinkData)

If you prefer to query the data imperatively instead of using the listener:
Future<void> checkDeepLink() async {
  final data = await NemuTracking.instance.getDeepLinkData();

  if (data != null) {
    debugPrint('App opened via Smart Link: ${data.deepLinkValue}');
  } else {
    debugPrint('App opened organically');
  }
}

DeepLinkData structure

class DeepLinkData {
  /// Deep link value (e.g.: "product/456", "category/shoes")
  final String? deepLinkValue;

  /// true = deferred deep link (user installed after clicking)
  final bool isDeferred;

  /// UTM parameters from the Smart Link
  final UtmData? utms;
}

class UtmData {
  final String? source;
  final String? medium;
  final String? campaign;
  final String? content;
  final String? term;
}

Session history

Last session history (last touch)

Query the UTMs from the user’s most recent interaction. Answers the question: “what were the UTMs from the last visit?”
Future<void> checkLastSession() async {
  final session = await NemuTracking.instance.getLastSessionHistory();

  if (session != null) {
    debugPrint('Source: ${session.utms.source}');
    debugPrint('Medium: ${session.utms.medium}');
    debugPrint('Campaign: ${session.utms.campaign}');
    debugPrint('Origin: ${session.origin}');
    debugPrint('Created at: ${session.createdAt}');
  } else {
    debugPrint('No session found');
  }
}

Example: attaching UTMs to a purchase

Future<void> trackPurchase(String orderId, double amount) async {
  final lastSession = await NemuTracking.instance.getLastSessionHistory();

  final purchasePayload = {
    'orderId': orderId,
    'amount': amount,
    // Last interaction (last touch)
    'lastSource': lastSession?.utms.source,
    'lastCampaign': lastSession?.utms.campaign,
    'lastMedium': lastSession?.utms.medium,
    'origin': lastSession?.origin,
  };

  // Send to your backend
  // await http.post(Uri.parse('https://your-api.com/purchases'), body: jsonEncode(purchasePayload));
}

Advanced usage

Debug mode

Enable debug mode to see detailed logs of all SDK operations in the console:
NemuTracking.instance.init(
  const NemuInitParams(
    apiKey: 'your-api-key',
    uriScheme: 'yourapp',
    trackingId: 'your-tracking-id',
    isDebugMode: true,
  ),
);
Logs will appear prefixed with [NemuSDK] and include information about:
  • Deep link processing
  • Initialization flow
  • Network errors and failures
Disable debug mode in production. Logs may contain sensitive information.

Base URL override

In development or staging environments, you can point the SDK to a different API:
NemuTracking.instance.init(
  const NemuInitParams(
    apiKey: 'your-api-key',
    uriScheme: 'yourapp',
    trackingId: 'your-tracking-id',
    isDebugMode: true,
    baseUrl: 'https://staging-trackings.nemu.com.br',
  ),
);
The baseUrl is only used when isDebugMode is true. In production mode, the SDK always uses the default URL.

Exported types

The SDK exports the following Dart types for use in your application:
import 'package:nemu_tracking_flutter/nemu_tracking_flutter.dart';

// Available classes and types:
// - NemuTracking        (main class — singleton)
// - NemuInitParams      (initialization parameters)
// - DeepLinkData        (deep link data)
// - DeepLinkCallback    (callback typedef)
// - UtmData             (UTM parameters)
// - SessionInfo         (session information)
// - SessionHistory      (session history)

NemuInitParams

class NemuInitParams {
  final String apiKey;
  final String uriScheme;
  final String trackingId;
  final bool isDebugMode;    // Default: false
  final String? baseUrl;
}

DeepLinkData

class DeepLinkData {
  final String? deepLinkValue;
  final bool isDeferred;
  final UtmData? utms;
}

UtmData

class UtmData {
  final String? source;
  final String? medium;
  final String? campaign;
  final String? content;
  final String? term;
}

SessionHistory

class SessionHistory {
  final String trackingSessionId;
  final String utmTerm;
  final UtmData utms;
  final String? origin;
  final String createdAt;
}

SessionInfo

class SessionInfo {
  final String trackingSessionId;
  final String utmTerm;
  final String trackingId;
}

DeepLinkCallback

typedef DeepLinkCallback = void Function(DeepLinkData data);

Troubleshooting

”flutter pub get” fails to resolve the package

Error:
Could not resolve URL "https://github.com/usenemu/sdk-app-tracking-flutter.git"
Solution:
  • Check that you have access to the SDK Git repository
  • Confirm that your Git credentials (SSH or HTTPS) are properly configured
  • Contact Nemu support to validate your access

Initialization error: “NemuTracking must be initialized before use”

Cause: an SDK method was called before initialization. Solution: make sure NemuTracking.instance.init() is called in the root widget’s initState before any other SDK method.
Checks:
  • Is the Associated Domain correctly configured in Xcode? (format: applinks:your-domain)
  • Is the apple-app-site-association file published and accessible on the domain? (configured by the Nemu team)
  • Is the URI scheme declared in Info.plist?
  • Is WidgetsFlutterBinding.ensureInitialized() being called before runApp?

Checks:
  • Are the intent filters correct in AndroidManifest.xml?
  • Is the domain with automatic verification active (android:autoVerify="true")?
  • Is the assetlinks.json file published on the domain? (configured by the Nemu team)
  • Is MainActivity configured with launchMode="singleTop" or singleTask?

No attribution data returned

Checks:
  • Are the apiKey and trackingId correct?
  • Is the Smart Link configured and active in the Nemu dashboard?
  • Test with isDebugMode: true and check the [NemuSDK] logs in the console

flutter_secure_storage fails on Android

Error:
PlatformException(Exception encountered, KeyStoreException, ...)
Solution:
  • Check that minSdkVersion in android/app/build.gradle is at least 21
  • On devices with Android < 6.0, you may need to configure flutter_secure_storage with specific options. See the package documentation

Need help? Contact the Nemu support team: