nemu_tracking_flutter 1.4.0 copy "nemu_tracking_flutter: ^1.4.0" to clipboard
nemu_tracking_flutter: ^1.4.0 copied to clipboard

PlatformAndroid

Nemu Smart Links attribution SDK for Flutter - UTM tracking, deep linking, and install attribution.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nemu SDK Example',
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _status = 'Initializing...';
  final _clickIdController = TextEditingController();
  String? _activeClickId;
  bool _loadingHistory = false;

  @override
  void initState() {
    super.initState();
    _activeClickId = getInstallReferrerClickIdOverride();
    _initSdk();
  }

  void _initSdk() {
    try {
      NemuTracking.instance.init(
        const NemuInitParams(
          apiKey: 'deb2b453-8e0c-4edc-9980-45f599397e4d',
          // `uriScheme` é opcional; omita para apps que usam só Universal Links.
          // Deve ser o mesmo scheme declarado no AndroidManifest.xml
          // (`<data android:scheme="nemuexample"/>`) — o valor anterior
          // (`nemu.fluttersdk.example://`) não correspondia a nenhum intent
          // filter, então o app nunca recebia deep links por URI scheme.
          uriScheme: 'nemuexample',
          trackingId: 'VaCItBH8b4',
          isDebugMode: true
        ),
      );
      setState(() => _status = 'SDK loaded successfully');

      NemuTracking.instance.onDeepLink((data) {
        debugPrint('Deep link data: $data');
      });
    } catch (error) {
      setState(() => _status = 'Error: $error');
    }
  }

  void _handleSetClickId() {
    final trimmed = _clickIdController.text.trim();
    if (trimmed.isEmpty) return;
    setInstallReferrerClickIdOverride(trimmed);
    setState(() => _activeClickId = trimmed);
  }

  void _handleClearClickId() {
    setInstallReferrerClickIdOverride(null);
    setState(() {
      _activeClickId = null;
      _clickIdController.clear();
    });
  }

  /// A chamada espera o rastreamento do launch antes de decidir que não há
  /// histórico (ver `_awaitLaunchTracking`), então numa primeira abertura pode
  /// levar alguns segundos — daí o estado de carregamento.
  ///
  /// `ensureInitialized()` roda de forma síncrona dentro de
  /// `getLastSessionHistory()`, então um `init()` que falhou chega aqui como
  /// `StateError` e não como future rejeitado; o `try` cobre os dois casos.
  Future<void> _handleShowLastSessionHistory() async {
    setState(() => _loadingHistory = true);

    TrackingSessionHistory? history;
    Object? error;
    try {
      history = await NemuTracking.instance.getLastSessionHistory();
    } catch (e) {
      error = e;
    }

    if (!mounted) return;
    setState(() => _loadingHistory = false);

    await showDialog<void>(
      context: context,
      builder: (_) => _LastSessionHistoryDialog(history: history, error: error),
    );
  }

  Future<void> _handleReset() async {
    final prefs = await SharedPreferences.getInstance();
    final keys = prefs.getKeys().where((k) => k.startsWith('@nemu_sdk:'));
    for (final key in keys) {
      await prefs.remove(key);
    }
    setState(() => _status = 'Storage cleared! Reinitializing...');
    _initSdk();
  }

  @override
  void dispose() {
    _clickIdController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'Nemu SDK Example',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 12),
                Text(
                  _status,
                  style: const TextStyle(fontSize: 14, color: Colors.grey),
                ),
                const SizedBox(height: 24),
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton.icon(
                    onPressed:
                        _loadingHistory ? null : _handleShowLastSessionHistory,
                    icon: _loadingHistory
                        ? const SizedBox(
                            width: 16,
                            height: 16,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          )
                        : const Icon(Icons.history),
                    label: Text(
                      _loadingHistory
                          ? 'Loading...'
                          : 'Show last session history',
                    ),
                  ),
                ),
                const SizedBox(height: 24),
                if (kDebugMode) _buildDebugSection(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildDebugSection() {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[100],
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Install Referrer Override',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
          ),
          const SizedBox(height: 12),
          TextField(
            controller: _clickIdController,
            decoration: const InputDecoration(
              hintText: 'Enter click_id (e.g. uuid)',
              border: OutlineInputBorder(),
              contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
            ),
            autocorrect: false,
          ),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: ElevatedButton(
                  onPressed: _handleSetClickId,
                  child: const Text('Set click_id'),
                ),
              ),
              const SizedBox(width: 8),
              ElevatedButton(
                onPressed: _handleClearClickId,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.grey,
                  foregroundColor: Colors.white,
                ),
                child: const Text('Clear'),
              ),
            ],
          ),
          if (_activeClickId != null) ...[
            const SizedBox(height: 8),
            Text(
              'Active: $_activeClickId',
              style: const TextStyle(
                fontSize: 13,
                color: Colors.blue,
                fontWeight: FontWeight.w500,
              ),
            ),
          ],
          const SizedBox(height: 4),
          const Text(
            'Set the click_id before resetting the SDK to simulate an install referrer on the next deferred deep link check.',
            style: TextStyle(fontSize: 12, color: Colors.grey),
          ),
          const SizedBox(height: 12),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: _handleReset,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
              ),
              child: const Text('Reset SDK (test deferred deep link)'),
            ),
          ),
        ],
      ),
    );
  }
}

/// Lista o que `getLastSessionHistory()` devolveu.
///
/// `TrackingSessionHistory` não tem `toJson`, então os campos são enumerados à
/// mão. Os nulos aparecem como `null` em vez de serem omitidos: numa tela de
/// diagnóstico, "o backend não mandou este campo" é justamente a informação
/// que se está procurando.
class _LastSessionHistoryDialog extends StatelessWidget {
  const _LastSessionHistoryDialog({this.history, this.error});

  final TrackingSessionHistory? history;
  final Object? error;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('getLastSessionHistory()'),
      content: SizedBox(width: double.maxFinite, child: _buildContent()),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('Close'),
        ),
      ],
    );
  }

  Widget _buildContent() {
    if (error != null) {
      return Text(
        '${error.runtimeType}: $error',
        style: const TextStyle(fontSize: 13, color: Colors.red),
      );
    }

    final h = history;
    if (h == null) {
      return const Text(
        'Returned null — no session history for this device yet. Track an '
        'event (open a deep link) and try again.',
        style: TextStyle(fontSize: 13, color: Colors.grey),
      );
    }

    final tracking = h.tracking;
    final entries = <(String, Object?)>[
      ('id', h.id),
      ('trackingSessionId', h.trackingSessionId),
      ('trackingId', h.trackingId),
      ('provider', h.provider),
      ('referrer', h.referrer),
      ('origin', h.origin),
      ('utmSource', h.utmSource),
      ('utmMedium', h.utmMedium),
      ('utmCampaign', h.utmCampaign),
      ('utmCampaignName', h.utmCampaignName),
      ('utmContent', h.utmContent),
      ('utmTerm', h.utmTerm),
      ('utmCampaignId', h.utmCampaignId),
      ('utmAdsetId', h.utmAdsetId),
      ('utmAdsetName', h.utmAdsetName),
      ('utmAdId', h.utmAdId),
      ('utmAdName', h.utmAdName),
      ('googleKeyword', h.googleKeyword),
      ('traySessionId', h.traySessionId),
      ('vtexSessionId', h.vtexSessionId),
      ('nuvemShopId', h.nuvemShopId),
      ('fbp', h.fbp),
      ('fbc', h.fbc),
      ('fbclid', h.fbclid),
      ('fbcUnix', h.fbcUnix),
      ('fbpUnix', h.fbpUnix),
      ('clientHash', h.clientHash),
      ('dashboardId', h.dashboardId),
      ('accountId', h.accountId),
      ('createdAt', h.createdAt),
      ('updatedAt', h.updatedAt),
      if (tracking == null)
        ('tracking', null)
      else ...[
        ('tracking.id', tracking.id),
        ('tracking.name', tracking.name),
        ('tracking.origin', tracking.origin),
        ('tracking.dashboardId', tracking.dashboardId),
        ('tracking.accountId', tracking.accountId),
        ('tracking.productId', tracking.productId),
      ],
    ];

    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          for (final (key, value) in entries) _buildRow(key, value),
        ],
      ),
    );
  }

  Widget _buildRow(String key, Object? value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 3),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            // Cabe a maior chave (`tracking.dashboardId`) sem quebrar no meio
            // do nome; valores longos quebram, e quebrar valor lê melhor que
            // quebrar identificador.
            width: 158,
            child: Text(
              key,
              style: const TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.w600,
                fontFamily: 'monospace',
              ),
            ),
          ),
          Expanded(
            child: Text(
              value?.toString() ?? 'null',
              style: TextStyle(
                fontSize: 12,
                fontFamily: 'monospace',
                color: value == null ? Colors.grey : Colors.black87,
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
140
points
576
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Nemu Smart Links attribution SDK for Flutter - UTM tracking, deep linking, and install attribution.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

android_id, android_play_install_referrer, app_links, flutter, flutter_secure_storage, http, shared_preferences, uuid

More

Packages that depend on nemu_tracking_flutter