Flutter

El paquete unolink es una capa ligera sobre los SDK nativos de iOS y Android, que vienen dentro de él. No necesita ningún permiso aparte de internet ni entradas en Info.plist. Publicado en pub.dev por unolink.dev. Plan Pro y superiores.

Requisitos

  • Flutter 3.22+ (Dart 3.4+).
  • Android 6.0+ (minSdk 23) e iOS 13+.
  • Una clave del SDK: en el panel, abre tu app → Ajustes → Claves del SDK → Nueva clave. Solo se muestra una vez, así que cópiala en ese momento.

Instalación

flutter pub add unolink

Eso añade unolink: ^0.3.3 a pubspec.yaml y lo descarga. Después vuelve a compilar la app; un hot reload no basta después de añadir código nativo.

Si CocoaPods falla en la build de iOS con un error de codificación (Ruby 4, o una configuración regional que no es UTF-8), ejecútalo tú en UTF-8 y vuelve a compilar:

cd ios && LANG=en_US.UTF-8 pod install

Configuración

Llama a configure una vez en main(), después de WidgetsFlutterBinding.ensureInitialized() y antes de runApp:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:unolink/unolink.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Unolink.configure('ulk_…', debug: kDebugMode);
  runApp(const MyApp());
}

Opciones

OpciónPor defectoQué hace
debugfalseMarca esta instalación como dispositivo de prueba. Pasa kDebugMode. Los paneles ocultan los dispositivos de prueba salvo que Incluir dispositivos de prueba esté activado.
logLevelUnolinkLogLevel.warnCuánto se escribe en la consola de Xcode o en Logcat (etiqueta Unolink): none, error, warn, info, debug.
baseUrlhttps://api.unolink.devLa API del SDK. Déjala como está.

La clave es pública: va dentro de tu app y solo puede enviar datos de esa app. Consulta Claves del SDK.

Eventos del embudo

Añade una línea en cada momento importante. Se convierten en los pasos de Embudo y Recorridos.

Unolink.onboardingCompleted();                     // the last onboarding screen is done
Unolink.paywallViewed(placement: 'onboarding');    // a paywall appeared; placement says which one
Unolink.paywallSkipped(placement: 'onboarding');   // closed without buying
Unolink.signUp(method: 'apple');                   // an account was created
Unolink.signUpSkipped();                           // went on as a guest (0.3.2+)

Sin RevenueCat, informa tú de las pruebas y compras. El importe es una cadena, así que nunca se redondea:

Unolink.trialStarted(productId: 'pro_yearly');
Unolink.purchase('49.99', 'USD', productId: 'pro_yearly');
Unolink.purchaseCancelled(productId: 'pro_yearly');              // closed the purchase sheet
Unolink.purchaseFailed(productId: 'pro_yearly', reason: 'payment_failed');

Tus propios eventos aceptan un nombre y hasta 20 propiedades:

Unolink.track('level_up', {'level': 3});

La lista completa, con los nombres que llegan al panel, está en Eventos.

ID de usuario y RevenueCat

Si usas RevenueCat, conéctalo en tu app → Ajustes → RevenueCat. Después, no llames a purchase ni a trialStarted: RevenueCat envía él mismo las pruebas, compras, renovaciones, cancelaciones y reembolsos, y Unolink ignora las llamadas de compra de la app para que nada cuente dos veces.

Para unir esas compras a la instalación, informa del propio ID de usuario de app de RevenueCat. Con el SDK 0.3.2 o posterior, una línea lo mantiene al día. Llámala una vez al iniciar, antes o después de configure:

Unolink.connectRevenueCat(
  () => Purchases.appUserID,
  addListener: Purchases.addCustomerInfoUpdateListener,
  removeListener: Purchases.removeCustomerInfoUpdateListener,
);

Unolink lee el ID en cuanto está configurado, en cada actualización de la información del cliente (compras, logIn, logOut) y cada vez que la app se reanuda, y solo lo envía cuando ha cambiado. Si RevenueCat aún no está configurado, la lectura falla sin hacer ruido y se vuelve a intentar más tarde. Devuelve una función que desconecta. Por eso, Unolink.revenueCatUserChanged() normalmente no hace falta en Flutter.

Sin ella (o antes de la 0.3.2), llama a setUserId con el ID de RevenueCat después de Purchases.configure, después de cada Purchases.logIn y Purchases.logOut, y en cada inicio. Usa uno u otro, no los dos:

// after Purchases.configure, logIn and logOut, and on every launch
Unolink.setUserId(await Purchases.appUserID);

Para un invitado, eso es el ID anónimo de RevenueCat, así que una compra hecha antes de registrarse se une a la instalación de inmediato. Después de logIn, es el ID de la cuenta. Cuenta cada ID que haya informado una instalación, así que la compra del invitado se mantiene unida. Consulta Invitados y cuentas.

Unolink nunca guarda el ID en sí, solo un hash unidireccional distinto para cada una de tus apps.

En Android, cuando alguien instala desde un enlace de Unolink que tiene un deep link, el listener se ejecuta una vez, en la primera apertura. En iPhone no se llama: Apple no tiene install referrer, y Unolink nunca hace fingerprinting.

Unolink.setDeferredDeepLinkListener((link) {
  // link.deepLink: '/promo/3for2', link.params: {'offer': '3for2'}
  // link.linkSlug: 'tiktok' or null, link.clickId: 'clk_01JA…'
  if (link.deepLink != null) router.go(link.deepLink!);
  Unolink.consumeDeferredDeepLink();
});

El listener se llama una vez por instalación. Un enlace que llegó antes de que lo configuraras se entrega en cuanto lo configuras.

Ábrelo después del onboarding

Las primeras aperturas suelen empezar en el onboarding. Si la pantalla del enlace está detrás del onboarding o del inicio de sesión, no lo abras desde el listener. Cuando termine el onboarding (o el inicio de sesión), lee el enlace pendiente, ábrelo y luego márcalo como consumido:

// when onboarding (or sign-in) is done, and on every launch after that until it's consumed
final link = await Unolink.getPendingDeferredDeepLink();
if (link?.deepLink != null) {
  router.go(link!.deepLink!);
  await Unolink.consumeDeferredDeepLink();
}

El enlace espera hasta que lo consumas, aunque la app se cierre entretanto (hasta 30 días después de la instalación; 0.3.3+). Un usuario nuevo que abandona a mitad del onboarding lo recibe igualmente en el siguiente inicio, así que no necesitas guardarlo tú. Llama a consumeDeferredDeepLink() en cuanto lo hayas abierto: hasta entonces, getPendingDeferredDeepLink() lo devuelve en cada inicio. Es null antes de configure y hasta que Google Play responde, y siempre null en iPhone.

Configura el Enlace profundo y los Parámetros del enlace en el panel (Enlaces → ⋯ → Editar). Cómo funciona está en Deep links diferidos en Android.

Otras llamadas

LlamadaQué hace
await Unolink.getInstallId()El ID aleatorio de esta instalación, o null antes de configure. No identifica a ninguna persona ni dispositivo.
Unolink.flush()Envía ya los eventos en cola. Normalmente no hace falta.
Unolink.setEnabled(false)Desactiva la medición y elimina los eventos en cola, para tu ajuste de exclusión voluntaria. true la vuelve a activar.

Dispositivos de prueba

Con debug: kDebugMode, las builds de depuración son dispositivos de prueba. Sus datos son reales, pero están ocultos hasta que activas Incluir dispositivos de prueba en Embudo y Recorridos. Más en Pruebas.

Privacidad

  • Sin IDFA, sin Google Advertising ID, sin aviso de App Tracking Transparency, sin fingerprinting.
  • Un ID de instalación aleatorio, versión de la app, versión del sistema operativo, versión del SDK, teléfono o tablet, idioma. El país viene del borde de la red. Las direcciones IP nunca se guardan.
  • En iOS, el manifiesto de privacidad del SDK nativo viene con el paquete: analíticas, no vinculadas a la identidad.
  • Nunca pongas nombres, emails ni otros datos personales en las propiedades de los eventos.

Solución de problemas

  • ¿MissingPluginException? Detén la app y vuelve a ejecutarla. Hot reload y hot restart no cargan código nativo nuevo.
  • ¿No aparece nada en el panel? Activa Incluir dispositivos de prueba: las builds con kDebugMode están ocultas por defecto.
  • ¿Sigue sin aparecer nada? Configura con logLevel: UnolinkLogLevel.debug y busca Unolink en la consola de Xcode o en Logcat. Una clave incorrecta o revocada se registra como error.

Más en Solución de problemas.