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

> SDK de atribuição e Smart Links da Nemu para aplicativos Flutter.

## Introdução

O **Nemu Flutter SDK** (`nemu_tracking_flutter`) permite integrar o sistema de atribuição e Smart Links da Nemu diretamente no seu aplicativo Flutter.

Com o SDK você pode:

* **Capturar a origem da instalação** — saber de qual campanha, fonte ou mídia o usuário veio (UTMs)
* **Processar deep links diretos** — quando o usuário clica em um Smart Link e o app já está instalado
* **Processar deep links diferidos** — quando o usuário clica em um Smart Link, instala o app pela loja e abre pela primeira vez
* **Identificar usuários** — associar um ID do seu sistema ao dispositivo para cruzar dados de atribuição
* **Consultar histórico de sessões** — recuperar os UTMs da última interação do usuário

O SDK funciona de forma automática: ao ser inicializado, ele intercepta deep links, detecta se é a primeira abertura do app e realiza a atribuição com o backend da Nemu, sem necessidade de configuração manual de cada evento.

***

## Pré-requisitos

| Requisito | Versão mínima         |
| --------- | --------------------- |
| Flutter   | >= 3.10.0             |
| Dart SDK  | >= 3.0.0 \< 4.0.0     |
| iOS       | 12.0+                 |
| Android   | API 21+ (Android 5.0) |

<Warning>
  **Pacote privado:** o `nemu_tracking_flutter` é um pacote de acesso restrito. Para instalá-lo, você precisa de acesso ao repositório Git fornecido pela equipe de suporte da Nemu. Consulte a seção de instalação abaixo.
</Warning>

***

## Instalação

### 1. Instalação do pacote

Adicione o SDK ao seu arquivo `pubspec.yaml`:

```yaml theme={null}
dependencies:
  nemu_tracking_flutter: ^1.0.0
```

Em seguida, execute:

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

<Info>
  Todas as dependências necessárias (`shared_preferences`, `flutter_secure_storage`, `app_links`, etc.) são instaladas automaticamente como dependências transitivas do SDK. Não é necessário adicioná-las manualmente ao seu projeto.
</Info>

***

## Configuração nativa

### iOS

#### 1. Configure Universal Links (obrigatório para deep links diretos)

Para que o iOS encaminhe os Smart Links diretamente para o app (sem abrir o navegador), você precisa configurar **Associated Domains**.

No Xcode:

1. Abra o projeto `.xcworkspace` (em `ios/Runner.xcworkspace`)
2. Selecione o target **Runner**
3. Vá em **Signing & Capabilities**
4. Clique em **+ Capability** e adicione **Associated Domains**
5. Adicione o domínio no formato:

```
applinks:seu-dominio.nemu.com.br
```

<Info>
  O domínio exato será fornecido pela equipe da Nemu junto com as credenciais.
</Info>

#### 2. Configure o URI Scheme (obrigatório)

No arquivo `ios/Runner/Info.plist`, adicione o URI scheme do seu app:

```xml theme={null}
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>seuapp</string>
    </array>
  </dict>
</array>
```

Substitua `seuapp` pelo scheme definido no painel da Nemu para o seu Smart Link.

<Note>
  O URI scheme configurado aqui deve corresponder ao valor passado no parâmetro `uriScheme` na inicialização do SDK.
</Note>

***

### Android

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

Para que os Smart Links abram o app diretamente, adicione intent filters na sua `Activity` principal.

No arquivo `android/app/src/main/AndroidManifest.xml`, dentro da tag `<activity>` principal (a que contém `android:name=".MainActivity"`):

**App Links (Universal Links do Android):**

```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="seu-dominio.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="seuapp" />
</intent-filter>
```

Substitua `seu-dominio.nemu.com.br` e `seuapp` pelos valores correspondentes ao seu projeto.

#### 2. Permissão de Internet (geralmente já presente)

Verifique se o `AndroidManifest.xml` possui a permissão de internet:

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

<Info>
  Na maioria dos projetos Flutter essa permissão já está incluída por padrão.
</Info>

***

## Inicialização

A inicialização do SDK deve ser feita **uma única vez**, o mais cedo possível no ciclo de vida do app — idealmente no `initState` do seu widget raiz ou na função `main`.

### Parâmetros de configuração

| Parâmetro     | Tipo      | Obrigatório | Descrição                                                                                |
| ------------- | --------- | ----------- | ---------------------------------------------------------------------------------------- |
| `apiKey`      | `String`  | Sim         | Chave de API obtida no painel da Nemu                                                    |
| `uriScheme`   | `String`  | Sim         | URI scheme do app (ex: `"seuapp"`). Deve corresponder ao valor configurado no Smart Link |
| `trackingId`  | `String`  | Sim         | ID de rastreamento associado ao app no painel da Nemu                                    |
| `isDebugMode` | `bool`    | Não         | Ativa logs detalhados no console. Padrão: `false`                                        |
| `baseUrl`     | `String?` | Não         | Sobrescreve a URL base da API (somente quando `isDebugMode` é `true`)                    |

### Exemplo completo de inicialização

```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: 'sua-api-key',
        uriScheme: 'seuapp',
        trackingId: 'seu-tracking-id',
      ),
    );
  }

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

### Uso com variáveis de ambiente

Para não expor credenciais diretamente no código, utilize o pacote `flutter_dotenv` ou defina as variáveis via `--dart-define`:

#### Usando `--dart-define` (recomendado)

```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'),
  ),
);
```

Execute o app com:

```bash theme={null}
flutter run \
  --dart-define=NEMU_API_KEY=sua-api-key \
  --dart-define=NEMU_URI_SCHEME=seuapp \
  --dart-define=NEMU_TRACKING_ID=seu-tracking-id
```

#### Usando `flutter_dotenv`

Crie um arquivo `.env` na raiz do projeto:

```env theme={null}
NEMU_API_KEY=sua-api-key
NEMU_URI_SCHEME=seuapp
NEMU_TRACKING_ID=seu-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>
  Adicione o `.env` ao seu `.gitignore` para não versionar as credenciais no repositório.
</Warning>

***

## Identificação de usuários

Após o login do usuário no seu app, associe o ID dele ao dispositivo. Isso permite que a Nemu cruze dados de atribuição com o seu sistema de usuários.

### Registrar usuário

```dart theme={null}
import 'package:nemu_tracking_flutter/nemu_tracking_flutter.dart';

// Após o login bem-sucedido
void onLoginSuccess(String userId) {
  NemuTracking.instance.setUserId(userId);
}
```

O SDK salva o ID localmente e envia a associação ao backend automaticamente.

### Limpar usuário no logout

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

<Note>
  `clearUserId()` remove apenas a associação local. O histórico de atribuição no backend é preservado.
</Note>

***

## Deep Links

O SDK processa dois tipos de deep links automaticamente:

### Deep links diretos

Ocorrem quando o **app já está instalado** e o usuário clica em um Smart Link. O sistema operacional abre o app diretamente (via Universal Link / App Link ou URI scheme).

O fluxo é transparente:

1. O usuário clica no Smart Link
2. O app abre com a URL
3. O SDK processa a URL e registra a sessão
4. Os listeners registrados via `onDeepLink` são notificados com os dados

### Deep links diferidos (Deferred Deep Links)

Ocorrem quando o **app não está instalado**. O usuário clica no Smart Link, é redirecionado para a loja, instala o app e abre pela primeira vez.

O SDK detecta automaticamente esse cenário na primeira abertura e entrega os dados de atribuição e deep link via `onDeepLink` com `isDeferred: true`.

### Listener reativo (onDeepLink)

Registre um callback para receber os dados do deep link assim que estiverem disponíveis:

```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: 'sua-api-key',
        uriScheme: 'seuapp',
        trackingId: 'seu-tracking-id',
      ),
    );

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

      if (data.deepLinkValue != null) {
        // Navegar para a tela correspondente
        // Ex: data.deepLinkValue = "product/456"
        navigateTo(data.deepLinkValue!);
      }

      if (data.utms != null) {
        debugPrint('Fonte: ${data.utm_source}');
        debugPrint('Campanha: ${data.utm_campaign}');
        debugPrint('Mídia: ${data.utm_medium}');
        debugPrint('Conteúdo: ${data.utm_content}');
        debugPrint('Termo: ${data.utm_term}');
      }

      debugPrint('Foi diferido? ${data.isDeferred}');
    });
  }

  @override
  void dispose() {
    // Limpar listener ao desmontar
    _unsubscribe?.call();
    super.dispose();
  }

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

<Info>
  **Replay automático:** se um deep link já foi processado antes do listener ser registrado, o callback é disparado imediatamente com os dados mais recentes. Isso evita que o app perca o deep link inicial.
</Info>

### Consulta imperativa (getDeepLinkData)

Se preferir consultar os dados de forma imperativa em vez de usar o listener:

```dart theme={null}
Future<void> checkDeepLink() async {
  final data = await NemuTracking.instance.getDeepLinkData();

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

### Estrutura do DeepLinkData

```dart theme={null}
class DeepLinkData {
  /// Valor do deep link (ex: "product/456", "category/shoes")
  final String? deepLinkValue;

  /// true = deep link diferido (usuário instalou após clicar)
  final bool isDeferred;

  /// Parâmetros UTM do Smart Link
  final UtmData? utms;
}

class UtmData {
  final String? utm_source;
  final String? utm_medium;
  final String? utm_campaign;
  final String? utm_content;
  final String? utm_term;
}
```

***

## Histórico de sessão

### Histórico da última sessão (last touch)

Consulte os UTMs da interação mais recente do usuário. Responde à pergunta: **"quais foram os UTMs da última visita?"**

```dart theme={null}
Future<void> checkLastSession() async {
  final session = await NemuTracking.instance.getLastSessionHistory();

  if (session != null) {
    debugPrint('Fonte: ${session.utm_source}');
    debugPrint('Mídia: ${session.utm_medium}');
    debugPrint('Campanha: ${session.utm_campaign}');
    debugPrint('Conteúdo: ${session.utm_content}');
    debugPrint('Termo: ${session.utm_term}');
  } else {
    debugPrint('Nenhuma sessão encontrada');
  }
}
```

### Exemplo: anexando UTMs a uma compra

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

  final purchasePayload = {
    'orderId': orderId,
    'amount': amount,
    // Última interação (last touch)
    'lastSource': lastSession?.utm_source,
    'lastCampaign': lastSession?.utm_campaign,
    'lastMedium': lastSession?.utm_medium,
    'lastContent': lastSession?.utm_content,
    'lastTerm': lastSession?.utm_term,
  };

  // Enviar para o seu backend
  // await http.post(Uri.parse('https://sua-api.com/purchases'), body: jsonEncode(purchasePayload));
}
```

***

## Uso avançado

### Modo debug

Ative o modo debug para ver logs detalhados de todas as operações do SDK no console:

```dart theme={null}
NemuTracking.instance.init(
  const NemuInitParams(
    apiKey: 'sua-api-key',
    uriScheme: 'seuapp',
    trackingId: 'seu-tracking-id',
    isDebugMode: true,
  ),
);
```

Os logs aparecerão prefixados com `[NemuSDK]` e incluem informações sobre:

* Processamento de deep links
* Fluxo de inicialização
* Erros e falhas de rede

<Warning>
  Desative o modo debug em produção. Os logs podem conter informações sensíveis.
</Warning>

***

## Tipos exportados

O SDK exporta os seguintes tipos Dart para uso na sua aplicação:

```dart theme={null}
import 'package:nemu_tracking_flutter/nemu_tracking_flutter.dart';

// Classes e tipos disponíveis:
// - NemuTracking        (classe principal — singleton)
// - NemuInitParams      (parâmetros de inicialização)
// - DeepLinkData        (dados do deep link)
// - DeepLinkCallback    (typedef do callback)
// - UtmData             (parâmetros UTM)
// - SessionInfo         (informações da sessão)
// - SessionHistory      (histórico da sessão)
```

### NemuInitParams

```dart theme={null}
class NemuInitParams {
  final String apiKey;
  final String uriScheme;
  final String trackingId;
  final bool isDebugMode;    // Padrão: 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? utm_source;
  final String? utm_medium;
  final String? utm_campaign;
  final String? utm_content;
  final String? utm_term;
}
```

### SessionHistory

```dart theme={null}
class SessionHistory {
  final String trackingSessionId;
  final String utm_source;
  final String utm_medium;
  final String utm_campaign;
  final String utm_content;
  final String utm_term;
  final String? origin;
  final String createdAt;
}
```

### SessionInfo

```dart theme={null}
class SessionInfo {
  final String trackingSessionId;
  final String utm_source;
  final String utm_medium;
  final String utm_campaign;
  final String utm_content;
  final String utm_term;
  final String trackingId;
}
```

### DeepLinkCallback

```dart theme={null}
typedef DeepLinkCallback = void Function(DeepLinkData data);
```

***

## Solução de problemas

### Erro ao inicializar: "NemuTracking must be initialized before use"

**Causa:** um método do SDK foi chamado antes da inicialização.

**Solução:** certifique-se de que `NemuTracking.instance.init()` é chamado no `initState` do widget raiz **antes** de qualquer outro método do SDK.

***

### Deep links não funcionam no iOS

**Verificações:**

* O Associated Domain está configurado corretamente no Xcode? (formato: `applinks:seu-dominio`)
* O arquivo `apple-app-site-association` está publicado e acessível no domínio? (configurado pela equipe da Nemu)
* O URI scheme está declarado no `Info.plist`?
* O `WidgetsFlutterBinding.ensureInitialized()` está sendo chamado antes do `runApp`?

***

### Deep links não funcionam no Android

**Verificações:**

* Os intent filters estão corretos no `AndroidManifest.xml`?
* O domínio está com verificação automática ativa (`android:autoVerify="true"`)?
* O arquivo `assetlinks.json` está publicado no domínio? (configurado pela equipe da Nemu)
* A `MainActivity` está configurada com `launchMode="singleTop"` ou `singleTask`?

***

### Nenhum dado de atribuição retornado

**Verificações:**

* O `apiKey` e o `trackingId` estão corretos?
* O Smart Link está configurado e ativo no painel da Nemu?
* Teste com `isDebugMode: true` e verifique os logs `[NemuSDK]` no console

***

### flutter\_secure\_storage falha no Android

**Erro:**

```
PlatformException(Exception encountered, KeyStoreException, ...)
```

**Solução:**

* Verifique se o `minSdkVersion` no `android/app/build.gradle` é pelo menos 21
* Em dispositivos com Android \< 6.0, pode ser necessário configurar o `flutter_secure_storage` com opções específicas. Consulte a [documentação do pacote](https://pub.dev/packages/flutter_secure_storage)

***
