> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nemu.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter SDK

> Nemu attribution and Smart Links SDK for Flutter applications.

## 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

| Requirement | Minimum version       |
| ----------- | --------------------- |
| Flutter     | >= 3.10.0             |
| Dart SDK    | >= 3.0.0 \< 4.0.0     |
| iOS         | 12.0+                 |
| Android     | API 21+ (Android 5.0) |

<Warning>
  **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.
</Warning>

***

## 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:

* **Email:** [contato@nemu.com.br](mailto:contato@nemu.com.br)

### 2. Package installation

Add the SDK to your `pubspec.yaml` file:

#### Via Git repository (recommended)

```yaml theme={null}
dependencies:
  nemu_tracking_flutter:
    git:
      url: https://github.com/usenemu/sdk-app-tracking-flutter.git
      ref: main
```

#### Via local path (for development)

```yaml theme={null}
dependencies:
  nemu_tracking_flutter:
    path: ../sdk-app-tracking-flutter
```

Then run:

```bash theme={null}
flutter pub get
```

<Info>
  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.
</Info>

***

## Native configuration

### iOS

#### 1. Configure Universal Links (required for direct deep links)

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
```

<Info>
  The exact domain will be provided by the Nemu team along with the credentials.
</Info>

#### 2. Configure the URI Scheme (required)

In the `ios/Runner/Info.plist` file, add your app's URI scheme:

```xml theme={null}
<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.

<Note>
  The URI scheme configured here must match the value passed in the `uriScheme` parameter during SDK initialization.
</Note>

***

### Android

#### 1. Configure Deep Links in AndroidManifest.xml

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):**

```xml theme={null}
<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:**

```xml theme={null}
<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:

```xml theme={null}
<uses-permission android:name="android.permission.INTERNET" />
```

<Info>
  In most Flutter projects, this permission is already included by default.
</Info>

***

## 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

| Parameter     | Type      | Required | Description                                                                           |
| ------------- | --------- | -------- | ------------------------------------------------------------------------------------- |
| `apiKey`      | `String`  | Yes      | API key obtained from the Nemu dashboard                                              |
| `uriScheme`   | `String`  | Yes      | App URI scheme (e.g.: `"yourapp"`). Must match the value configured in the Smart Link |
| `trackingId`  | `String`  | Yes      | Tracking ID associated with the app in the Nemu dashboard                             |
| `isDebugMode` | `bool`    | No       | Enables detailed logs in the console. Default: `false`                                |
| `baseUrl`     | `String?` | No       | Overrides the API base URL (only when `isDebugMode` is `true`)                        |

### Full initialization example

```dart theme={null}
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`:

#### Using `--dart-define` (recommended)

```dart theme={null}
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:

```bash theme={null}
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:

```env theme={null}
NEMU_API_KEY=your-api-key
NEMU_URI_SCHEME=yourapp
NEMU_TRACKING_ID=your-tracking-id
```

```dart theme={null}
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']!,
  ),
);
```

<Warning>
  Add the `.env` file to your `.gitignore` to avoid committing credentials to the repository.
</Warning>

***

## 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

```dart theme={null}
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

```dart theme={null}
void onLogout() {
  NemuTracking.instance.clearUserId();
}
```

<Note>
  `clearUserId()` only removes the local association. The attribution history on the backend is preserved.
</Note>

***

## Deep Links

The SDK automatically processes two types of deep links:

### Direct 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

### Deferred deep links (Deferred Deep Links)

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`.

### Reactive listener (onDeepLink)

Register a callback to receive deep link data as soon as it becomes available:

```dart theme={null}
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(),
    );
  }
}
```

<Info>
  **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.
</Info>

### Imperative query (getDeepLinkData)

If you prefer to query the data imperatively instead of using the listener:

```dart theme={null}
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

```dart theme={null}
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?"**

```dart theme={null}
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

```dart theme={null}
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:

```dart theme={null}
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

<Warning>
  Disable debug mode in production. Logs may contain sensitive information.
</Warning>

### Base URL override

In development or staging environments, you can point the SDK to a different API:

```dart theme={null}
NemuTracking.instance.init(
  const NemuInitParams(
    apiKey: 'your-api-key',
    uriScheme: 'yourapp',
    trackingId: 'your-tracking-id',
    isDebugMode: true,
    baseUrl: 'https://staging-trackings.nemu.com.br',
  ),
);
```

<Info>
  The `baseUrl` is only used when `isDebugMode` is `true`. In production mode, the SDK always uses the default URL.
</Info>

***

## Exported types

The SDK exports the following Dart types for use in your application:

```dart theme={null}
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

```dart theme={null}
class NemuInitParams {
  final String apiKey;
  final String uriScheme;
  final String trackingId;
  final bool isDebugMode;    // Default: false
  final String? baseUrl;
}
```

### DeepLinkData

```dart theme={null}
class DeepLinkData {
  final String? deepLinkValue;
  final bool isDeferred;
  final UtmData? utms;
}
```

### UtmData

```dart theme={null}
class UtmData {
  final String? source;
  final String? medium;
  final String? campaign;
  final String? content;
  final String? term;
}
```

### SessionHistory

```dart theme={null}
class SessionHistory {
  final String trackingSessionId;
  final String utmTerm;
  final UtmData utms;
  final String? origin;
  final String createdAt;
}
```

### SessionInfo

```dart theme={null}
class SessionInfo {
  final String trackingSessionId;
  final String utmTerm;
  final String trackingId;
}
```

### DeepLinkCallback

```dart theme={null}
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.

***

### Deep links not working on iOS

**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`?

***

### Deep links not working on Android

**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](https://pub.dev/packages/flutter_secure_storage)

***
