ferry_link_flutter 0.1.0 copy "ferry_link_flutter: ^0.1.0" to clipboard
ferry_link_flutter: ^0.1.0 copied to clipboard

Flutter SDK for Ferry deep linking, deferred deep links, and link-level attribution on iOS and Android.

example/lib/main.dart

import 'package:ferry_link_flutter/ferry_link_flutter.dart';
import 'package:ferry_platform_interface/ferry_platform_interface.dart';
import 'package:flutter/material.dart';

import 'demo_ferry_platform.dart';

/// Minimal example app for the ferry Flutter plugin. Demonstrates the
/// three steps from the README's 5-minute integration guide:
///
/// 1. Ferry.configure, called once at app start (see below).
/// 2. A direct universal/app link open, via the "Simulate direct open"
///    button, which calls Ferry.handle with a sample link URL. This is a
///    real call into the native SDK's local URL parsing; no network call
///    is made for a link that already carries its data in the query
///    string.
/// 3. A simulated deferred first-install match, via the "Simulate
///    deferred match" button. See demo_ferry_platform.dart: this exists
///    to make the onLink shape visible in a running app without a real
///    install and without calling the real Ferry API. The real deferred
///    match flow (first-launch detection, signal gathering order,
///    single-shot POST /v1/match) is ferry-android's and ferry-ios's own
///    responsibility, covered by their test suites, not reimplemented
///    here.
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Swap in the demo decorator before anything else runs, so every call
  // this app makes to Ferry.* (through the real FerryPlatform.instance
  // singleton) still reaches the real native side for configure/handle/
  // track, with only onLink augmented. See demo_ferry_platform.dart.
  final demoPlatform = DemoFerryPlatform(FerryPlatform.instance);
  FerryPlatform.instance = demoPlatform;

  // Step 1: configure. Call once, as early as possible. This reads
  // nothing and sends nothing; pk_test_xxx is a placeholder public key
  // for this example and for tests, never a real project's key.
  await Ferry.configure(
    publicKey: 'pk_test_xxx',
    options: const FerryOptions(debugLogging: true),
  );

  runApp(FerryExampleApp(demoPlatform: demoPlatform));
}

class FerryExampleApp extends StatelessWidget {
  const FerryExampleApp({super.key, required this.demoPlatform});

  final DemoFerryPlatform demoPlatform;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ferry example',
      home: FerryHomePage(demoPlatform: demoPlatform),
    );
  }
}

class FerryHomePage extends StatefulWidget {
  const FerryHomePage({super.key, required this.demoPlatform});

  final DemoFerryPlatform demoPlatform;

  @override
  State<FerryHomePage> createState() => _FerryHomePageState();
}

class _FerryHomePageState extends State<FerryHomePage> {
  FerryLink? _lastLink;

  @override
  void initState() {
    super.initState();
    // Step 2/3: one router for both a direct open and a deferred match.
    // Discriminate on link.isDeferred, never on link.method.
    Ferry.onLink.listen((link) {
      setState(() => _lastLink = link);
    });
  }

  /// The one Ferry call that reports its failure to the caller, because
  /// a share sheet with no link in it is worse than an error.
  Future<void> _createLink() async {
    String message;
    try {
      final created = await Ferry.createLink(
        domain: 'go.acme.com',
        data: const {'screen': 'profile', 'user': 'u_42'},
      );
      message = 'Created ${created.url}';
    } on FerryException catch (e) {
      message = 'createLink failed: ${e.error}';
    }
    if (!mounted) return;
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    final link = _lastLink;
    return Scaffold(
      appBar: AppBar(title: const Text('Ferry example')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: () => Ferry.handle(
                'https://go.acme.com/welcome?ref=footer&campaign=fall_sale',
              ),
              child: const Text('Simulate direct open'),
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: widget.demoPlatform.simulateDeferredMatch,
              child: const Text('Simulate deferred match (demo)'),
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: () => Ferry.track(FerryEvents.signup),
              child: const Text('Track a signup event'),
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: _createLink,
              child: const Text('Create a link'),
            ),
            const SizedBox(height: 24),
            Text(
              link == null
                  ? 'No link received yet.'
                  : 'Last link:\n'
                      'data: ${link.data}\n'
                      'url: ${link.url}\n'
                      'method: ${link.method.rawValue}\n'
                      'isDeterministic: ${link.confidence.isDeterministic}\n'
                      'score: ${link.confidence.score}\n'
                      'isDeferred: ${link.isDeferred}\n'
                      'clickId: ${link.clickId}',
            ),
          ],
        ),
      ),
    );
  }
}