dub_flutter 0.0.2 copy "dub_flutter: ^0.0.2" to clipboard
dub_flutter: ^0.0.2 copied to clipboard

An unofficial Flutter SDK for Dub - track clicks, leads, and sales with the open-source link management platform.

example/lib/main.dart

import 'dart:async';

import 'package:app_links/app_links.dart';
import 'package:dub_flutter/dub_flutter.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dub Flutter Example',
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      home: const DubDebugHome(),
    );
  }
}

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

  @override
  State<DubDebugHome> createState() => _DubDebugHomeState();
}

class _DubDebugHomeState extends State<DubDebugHome> {
  final _publishableKeyController = TextEditingController(text: 'dub_xxxxx');
  final _domainController = TextEditingController(text: 'dub.sh');

  final _customerExternalIdController = TextEditingController(text: 'user_123');
  final _leadEventNameController = TextEditingController(text: 'Sign up');
  final _saleEventNameController = TextEditingController(text: 'subscription');
  final _leadEventNameForSaleController = TextEditingController(
    text: 'Sign up',
  );
  final _amountController = TextEditingController(text: '9.99');
  final _currencyController = TextEditingController(text: 'usd');

  PaymentProcessor _paymentProcessor = PaymentProcessor.custom;

  final _appLinks = AppLinks();
  StreamSubscription<Uri>? _sub;

  bool _initialized = false;
  Uri? _lastIncomingUri;

  TrackOpenResponse? _lastOpen;
  TrackLeadResponse? _lastLead;
  TrackSaleResponse? _lastSale;
  String? _lastError;

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

  Future<void> _startListeningForLinks() async {
    try {
      final initial = await _appLinks.getInitialLink();
      if (initial != null) {
        setState(() {
          _lastIncomingUri = initial;
        });
      }

      _sub = _appLinks.uriLinkStream.listen(
        (uri) {
          setState(() {
            _lastIncomingUri = uri;
          });
        },
        onError: (Object error) {
          setState(() {
            _lastError = 'Deep link stream error: $error';
          });
        },
      );
    } catch (e) {
      setState(() {
        _lastError = 'Failed to initialize deep links: $e';
      });
    }
  }

  @override
  void dispose() {
    _sub?.cancel();
    _publishableKeyController.dispose();
    _domainController.dispose();
    _customerExternalIdController.dispose();
    _leadEventNameController.dispose();
    _saleEventNameController.dispose();
    _leadEventNameForSaleController.dispose();
    _amountController.dispose();
    _currencyController.dispose();
    super.dispose();
  }

  Future<void> _initSdk() async {
    setState(() {
      _lastError = null;
    });

    try {
      await Dub.init(
        publishableKey: _publishableKeyController.text.trim(),
        domain: _domainController.text.trim(),
      );
      setState(() {
        _initialized = true;
      });
    } catch (e) {
      setState(() {
        _lastError = 'Dub.init failed: $e';
      });
    }
  }

  Future<void> _trackOpen({String? deepLink}) async {
    setState(() {
      _lastError = null;
      _lastOpen = null;
    });

    try {
      final res = await Dub.instance.trackOpen(deepLink: deepLink);
      setState(() {
        _lastOpen = res;
      });
    } catch (e) {
      setState(() {
        _lastError = 'trackOpen failed: $e';
      });
    }
  }

  Future<void> _trackLead() async {
    setState(() {
      _lastError = null;
      _lastLead = null;
    });

    try {
      final res = await Dub.instance.trackLead(
        eventName: _leadEventNameController.text.trim(),
        customerExternalId: _customerExternalIdController.text.trim(),
        metadata: const {'example': 'true'},
      );
      setState(() {
        _lastLead = res;
      });
    } catch (e) {
      setState(() {
        _lastError = 'trackLead failed: $e';
      });
    }
  }

  Future<void> _trackSale() async {
    setState(() {
      _lastError = null;
      _lastSale = null;
    });

    final amount = double.tryParse(_amountController.text.trim());
    if (amount == null) {
      setState(() {
        _lastError = 'Invalid amount: ${_amountController.text}';
      });
      return;
    }

    try {
      final res = await Dub.instance.trackSale(
        customerExternalId: _customerExternalIdController.text.trim(),
        amount: amount,
        paymentProcessor: _paymentProcessor,
        eventName: _saleEventNameController.text.trim(),
        currency: _currencyController.text.trim(),
        leadEventName: _leadEventNameForSaleController.text.trim().isEmpty
            ? null
            : _leadEventNameForSaleController.text.trim(),
        metadata: const {'example': 'true'},
      );
      setState(() {
        _lastSale = res;
      });
    } catch (e) {
      setState(() {
        _lastError = 'trackSale failed: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final incoming = _lastIncomingUri?.toString();

    return Scaffold(
      appBar: AppBar(title: const Text('Dub Deep Link Debugger')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _Section(
            title: '1) Initialize SDK',
            child: Column(
              children: [
                TextField(
                  controller: _publishableKeyController,
                  decoration: const InputDecoration(
                    labelText: 'Publishable key',
                    hintText: 'dub_…',
                  ),
                ),
                const SizedBox(height: 12),
                TextField(
                  controller: _domainController,
                  decoration: const InputDecoration(
                    labelText: 'Dub domain',
                    hintText: 'dub.sh',
                  ),
                ),
                const SizedBox(height: 12),
                FilledButton(
                  onPressed: _initSdk,
                  child: Text(_initialized ? 'Re-initialize' : 'Initialize'),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _Section(
            title: '2) Deep link reception',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Last incoming URI: ${incoming ?? '(none yet)'}',
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: (!_initialized || _lastIncomingUri == null)
                      ? null
                      : () => _trackOpen(deepLink: incoming),
                  child: const Text('Track Open from last incoming URI'),
                ),
                const SizedBox(height: 8),
                FilledButton.tonal(
                  onPressed: _initialized ? () => _trackOpen() : null,
                  child: const Text(
                    'Track Open (no deepLink; clipboard/referrer)',
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _Section(
            title: '3) Lead / Sale',
            child: Column(
              children: [
                TextField(
                  controller: _customerExternalIdController,
                  decoration: const InputDecoration(
                    labelText: 'customerExternalId',
                  ),
                ),
                const SizedBox(height: 12),
                TextField(
                  controller: _leadEventNameController,
                  decoration: const InputDecoration(
                    labelText: 'Lead eventName',
                  ),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Expanded(
                      child: FilledButton(
                        onPressed: _initialized ? _trackLead : null,
                        child: const Text('Track Lead'),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                TextField(
                  controller: _saleEventNameController,
                  decoration: const InputDecoration(
                    labelText: 'Sale eventName',
                  ),
                ),
                const SizedBox(height: 12),
                TextField(
                  controller: _leadEventNameForSaleController,
                  decoration: const InputDecoration(
                    labelText: 'leadEventName (optional)',
                  ),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _amountController,
                        keyboardType: TextInputType.number,
                        decoration: const InputDecoration(labelText: 'amount'),
                      ),
                    ),
                    const SizedBox(width: 12),
                    SizedBox(
                      width: 100,
                      child: TextField(
                        controller: _currencyController,
                        decoration: const InputDecoration(
                          labelText: 'currency',
                        ),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 12),
                DropdownButtonFormField<PaymentProcessor>(
                  value: _paymentProcessor,
                  decoration: const InputDecoration(
                    labelText: 'paymentProcessor',
                  ),
                  items: PaymentProcessor.values
                      .map(
                        (p) => DropdownMenuItem(value: p, child: Text(p.name)),
                      )
                      .toList(),
                  onChanged: (v) {
                    if (v == null) return;
                    setState(() => _paymentProcessor = v);
                  },
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Expanded(
                      child: FilledButton(
                        onPressed: _initialized ? _trackSale : null,
                        child: const Text('Track Sale'),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _Section(
            title: '4) Results',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (_lastError != null)
                  Text(
                    _lastError!,
                    style: TextStyle(
                      color: Theme.of(context).colorScheme.error,
                    ),
                  ),
                Text('trackOpen.clickId: ${_lastOpen?.clickId ?? '(none)'}'),
                Text('trackOpen.link.key: ${_lastOpen?.link?.key ?? '(none)'}'),
                Text('trackLead.click.id: ${_lastLead?.click.id ?? '(none)'}'),
                Text(
                  'trackSale.eventName: ${_lastSale?.eventName ?? '(none)'}',
                ),
                const SizedBox(height: 8),
                FutureBuilder<String?>(
                  future: _initialized
                      ? Dub.instance.clickId
                      : Future.value(null),
                  builder: (context, snapshot) {
                    final clickId = snapshot.data;
                    return Text('Stored clickId: ${clickId ?? '(none)'}');
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _Section extends StatelessWidget {
  final String title;
  final Widget child;

  const _Section({required this.title, required this.child});

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 12),
            child,
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
210
downloads

Publisher

unverified uploader

Weekly Downloads

An unofficial Flutter SDK for Dub - track clicks, leads, and sales with the open-source link management platform.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

clipboard, flutter, http, meta, play_install_referrer, plugin_platform_interface, shared_preferences

More

Packages that depend on dub_flutter

Packages that implement dub_flutter