Añadir soporte para enlaces profundos
Losenlaces profundos dirigen a los usuarios a contenidos específicos dentro de su aplicación. Cuando los usuarios tocan un enlace profundo en un dispositivo con su aplicación instalada, la aplicación se abre directamente al contenido deseado, como una página de producto o una experiencia específica.
Los enlaces de seguimiento singulares admiten tanto enlaces profundos estándar (para aplicaciones instaladas) como enlaces profundos diferidos (para nuevas instalaciones). Para más información, consulte las preguntas frecuentes sobre deep linking y las preguntas frecuentes sobre Singular Links.
Requisitos
Requisitos previos
Complete los requisitos previos de Singular Links para habilitar la vinculación profunda para su aplicación.
Notas:
- Este artículo asume que su organización está utilizando Singular Links - la tecnología de enlaces de seguimiento de Singular. Los clientes más antiguos pueden estar utilizando enlaces de seguimiento heredados.
- Los destinos de enlaces profundos de su aplicación deben configurarse en la página Aplicaciones de Singular (consulte Configuración de su aplicación para el seguimiento de atribución).
Parámetros disponibles
El controlador de SingularLink proporciona acceso a los parámetros de enlace profundo, enlace profundo diferido y passthrough de los enlaces de seguimiento de Singular cuando se abre la aplicación.
- Enlace profundo (_dl): La URL de destino dentro de su aplicación para los usuarios que hagan clic en el enlace.
- Enlace profundo diferido (_ddl): La URL de destino para los usuarios que instalan la aplicación después de hacer clic en el enlace.
- Passthrough (_p): Datos personalizados pasados a través del enlace de seguimiento para un contexto adicional
Configuración de la plataforma
Configuración de la plataforma iOS
Actualización de iOS AppDelegate
Habilite el SDK de Singular para procesar datos relacionados con el lanzamiento y gestionar enlaces profundos pasando los objetos launchOptions y userActivity al SDK de Singular en su archivo AppDelegate.
Estos objetos contienen información crítica sobre cómo y por qué se lanzó su aplicación, que Singular utiliza para el seguimiento de atribución y la navegación de enlaces profundos.
Implementación Objective-C
// Top of AppDelegate.m
#import "SingularAppDelegate.h"
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
[SingularAppDelegate shared].launchOptions = launchOptions;
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>
*restorableObjects))restorationHandler {
[[SingularAppDelegate shared] continueUserActivity:userActivity
restorationHandler:restorationHandler];
return [super application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
[[SingularAppDelegate shared] handleOpenUrl:url options:options];
return [super application:app openURL:url options:options];
}
Implementación Swift
import singular_flutter_sdk
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self)
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.launchOptions = launchOptions
}
return super.application(application,
didFinishLaunchingWithOptions:launchOptions)
}
override func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.continueUserActivity(userActivity,
restorationHandler: nil)
}
return super.application(application,
continue: userActivity,
restorationHandler: restorationHandler)
}
override func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.handleOpen(url, options: options)
}
return super.application(app, open: url, options: options)
}
Configuración de la plataforma Android
Actualizar Android MainActivity
Habilite Singular SDK para procesar los datos relacionados con el lanzamiento y gestionar los enlaces profundos modificando el archivo MainActivity para pasar el objeto Intent a Singular SDK.
El objeto Intent contiene información sobre cómo y por qué se lanzó su aplicación, que Singular utiliza para el seguimiento de la atribución y la navegación de enlaces profundos.
Implementación Java
// Add as part of the imports at the top of the class
import android.content.Intent;
import com.singular.flutter_sdk.SingularBridge;
// Add to the MainActivity class
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridge.onNewIntent(intent);
}
}
Implementación de Kotlin
// Add as part of the imports at the top of the class
import android.content.Intent
import com.singular.flutter_sdk.SingularBridge
// Add to the MainActivity class
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridge.onNewIntent(intent)
}
}
Configuración del SDK
Implementación de Singular Links Handler
Configure la devolución de llamada singularLinksHandler durante la inicialización del SDK para gestionar los datos de enlaces profundos entrantes y diferidos.
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_link_params.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
initializeSDK();
}
void initializeSDK() {
// Create SDK configuration
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
// Configure deep link handler
config.singularLinksHandler = (SingularLinkParams params) {
print('=== Singular Link Resolved ===');
print('Deep link: ${params.deeplink}');
print('Passthrough: ${params.passthrough}');
print('Is deferred: ${params.isDeferred}');
// Handle deep link navigation
if (params.deeplink != null) {
handleDeepLink(params.deeplink!, params.isDeferred);
}
// Handle passthrough data
if (params.passthrough != null) {
handlePassthroughData(params.passthrough!);
}
};
// Initialize SDK
Singular.start(config);
}
void handleDeepLink(String url, bool isDeferred) {
print('Routing to: $url (Deferred: $isDeferred)');
// Parse URL and navigate to appropriate screen
// Example: myapp://product/123
if (url.contains('product')) {
final productId = url.split('/').last;
navigateToProduct(productId);
} else if (url.contains('promo')) {
navigateToPromo(url);
}
}
void handlePassthroughData(String passthrough) {
print('Processing passthrough data: $passthrough');
// Parse and use passthrough data as needed
}
void navigateToProduct(String productId) {
// Your navigation logic
print('Navigating to product: $productId');
}
void navigateToPromo(String url) {
// Your navigation logic
print('Navigating to promo: $url');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
Propiedad de configuración:
void Function(SingularLinkParams)? singularLinksHandler
Nota: La devolución de llamada singularLinksHandler sólo se activa cuando la aplicación se abre a través de un Singular Link. Para obtener más información, consulte las preguntas frecuentes sobre Singular Links.
Para obtener la documentación completa, consulte la referencia singularLinksHandler.
Comportamiento del controlador
Comprensión de la resolución de enlaces
singularLinksHandler se comporta de forma diferente en función de si la aplicación está recién instalada o ya instalada.
Nueva instalación (enlace profundo diferido)
En una instalación nueva, no existe ninguna URL abierta cuando se inicia la aplicación. Singular completa la atribución para determinar si el enlace de seguimiento contenía un valor de enlace profundo o de enlace profundo diferido.
Flujo de enlace profundo diferido:
- El usuario hace clic en un enlace de seguimiento de Singular configurado con un valor de enlace profundo.
- El usuario instala y abre la aplicación por primera vez
- Singular SDK envía la primera sesión a los servidores de Singular
- La atribución se completa e identifica el enlace profundo desde el enlace de seguimiento
- El valor del enlace profundo vuelve al callback
singularLinksHandleren el parámetrodeeplinkconisDeferred = true
Prueba de enlaces profundos diferidos:
- Desinstala la app del dispositivo de prueba (si está instalada actualmente)
- iOS: Reinicie su IDFA. Android: Restablece tu ID de publicidad de Google (GAID).
- Haz clic en el enlace de seguimiento Singular desde el dispositivo (asegúrate de que está configurado con un valor de enlace profundo)
- Instale y abra la aplicación
La atribución debería completarse correctamente, y el valor de enlace profundo diferido se pasará al gestor de singularLinksHandler.
Consejo profesional: Cuando pruebe enlaces profundos con una compilación de desarrollo que utilice un nombre de paquete diferente (por ejemplo, com.example.dev en lugar de com.example.prod), configure el enlace de seguimiento específicamente para el nombre de paquete de la aplicación de desarrollo. Después de hacer clic en el enlace de prueba, instala la compilación de desarrollo directamente en el dispositivo (a través de Android Studio o Xcode) en lugar de descargar la aplicación de producción de la tienda de aplicaciones.
Ya instalada (enlace profundo inmediato)
Cuando la aplicación ya está instalada, al hacer clic en un enlace singular se abre la aplicación inmediatamente mediante la tecnología Universal Links (iOS) o Android App Links.
Flujo de enlace profundo inmediato:
- El usuario hace clic en un enlace de seguimiento Singular
- El sistema operativo proporciona una URL abierta que contiene el enlace de seguimiento Singular completo
- Durante la inicialización del SDK, Singular analiza la URL
- Singular extrae los valores
deeplinkypassthrough - Los valores vuelven a través del manejador
singularLinksHandlerconisDeferred = false
Características avanzadas
Parámetros Passthrough
Capture datos adicionales del clic en el enlace de seguimiento utilizando parámetros passthrough.
Si se incluye un parámetro passthrough (_p) en el enlace de seguimiento, el parámetro passthrough del controlador singularLinksHandler contiene los datos correspondientes. Utilícelo para capturar metadatos de campaña, datos de segmentación de usuarios o cualquier información personalizada que necesite en la aplicación.
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_link_params.dart';
import 'dart:convert';
SingularConfig config = SingularConfig('API_KEY', 'SECRET');
config.singularLinksHandler = (SingularLinkParams params) {
// Extract passthrough data
final passthroughData = params.passthrough;
if (passthroughData != null) {
try {
// Parse JSON passthrough data
final jsonData = jsonDecode(passthroughData);
print('Campaign ID: ${jsonData['campaign_id']}');
print('User Segment: ${jsonData['segment']}');
print('Promo Code: ${jsonData['promo_code']}');
// Apply campaign-specific settings
applyCampaignSettings(jsonData);
} catch (error) {
print('Error parsing passthrough data: $error');
}
}
};
void applyCampaignSettings(Map<String, dynamic> data) {
// Your campaign logic here
print('Applying campaign settings: $data');
}
Reenviar todos los parámetros de consulta
Capture todos los parámetros de consulta de la URL del enlace de seguimiento añadiendo el parámetro _forward_params=2 a su enlace de seguimiento.
Cuando se añade _forward_params=2 al enlace de seguimiento, todos los parámetros de consulta se incluyen en el parámetro deeplink del manejador singularLinksHandler, dándole acceso a la URL completa con todos sus parámetros.
Ejemplo de enlace de seguimiento:https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_forward_params=2&utm_source=facebook&promo=SALE2024
El manejador singularLinksHandler recibirá:deeplink = "myapp://product/123?utm_source=facebook&promo=SALE2024"
Ejemplo de implementación completa
Implementación completa de deep linking con navegación, manejo de passthrough y reenvío de parámetros para apps Flutter.
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_link_params.dart';
import 'dart:convert';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
void initState() {
super.initState();
initializeSDK();
}
void initializeSDK() {
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
config.singularLinksHandler = (SingularLinkParams params) {
print('=== Singular Link Resolved ===');
print('Deep link: ${params.deeplink}');
print('Passthrough: ${params.passthrough}');
print('Is deferred: ${params.isDeferred}');
// Handle passthrough data
if (params.passthrough != null) {
handlePassthroughData(params.passthrough!);
}
// Handle deep link navigation
if (params.deeplink != null) {
handleDeepLinkNavigation(params.deeplink!, params.isDeferred);
}
};
Singular.start(config);
}
void handlePassthroughData(String passthroughString) {
try {
final data = jsonDecode(passthroughString);
// Apply promo code if present
if (data.containsKey('promo_code')) {
applyPromoCode(data['promo_code']);
}
// Set user segment
if (data.containsKey('segment')) {
setUserSegment(data['segment']);
}
// Track campaign
if (data.containsKey('campaign_id')) {
trackCampaign(data['campaign_id']);
}
} catch (error) {
print('Error parsing passthrough: $error');
}
}
void handleDeepLinkNavigation(String url, bool isDeferred) {
// Parse URL to extract route and parameters
final urlObj = parseDeepLink(url);
print('Navigating to: ${urlObj['route']}');
print('Parameters: ${urlObj['params']}');
print('Deferred install: $isDeferred');
// Route based on deep link structure
switch (urlObj['route']) {
case 'product':
navigateToProduct(urlObj['params']['id']);
break;
case 'promo':
navigateToPromo(urlObj['params']['code']);
break;
case 'category':
navigateToCategory(urlObj['params']['name']);
break;
default:
navigateToHome();
}
}
Map<String, dynamic> parseDeepLink(String url) {
// Parse myapp://product/123?variant=blue
final parts = url.split('?');
final path = parts[0];
final queryString = parts.length > 1 ? parts[1] : null;
// Extract path components
final pathParts = path.replaceFirst(RegExp(r'^[^:]+://'), '').split('/');
final route = pathParts[0];
// Parse parameters
final params = <String, String>{};
// Add path parameters
if (pathParts.length > 1) {
params['id'] = pathParts[1];
}
// Add query parameters
if (queryString != null) {
queryString.split('&').forEach((pair) {
final keyValue = pair.split('=');
if (keyValue.length == 2) {
params[keyValue[0]] = Uri.decodeComponent(keyValue[1]);
}
});
}
return {
'route': route,
'params': params
};
}
// Navigation functions
void navigateToProduct(String? productId) {
if (productId != null) {
print('Navigating to product: $productId');
// Use your navigation framework (Navigator, GoRouter, etc.)
navigatorKey.currentState?.pushNamed('/product/$productId');
}
}
void navigateToPromo(String? promoCode) {
if (promoCode != null) {
print('Navigating to promo: $promoCode');
navigatorKey.currentState?.pushNamed('/promo/$promoCode');
}
}
void navigateToCategory(String? categoryName) {
if (categoryName != null) {
print('Navigating to category: $categoryName');
navigatorKey.currentState?.pushNamed('/category/$categoryName');
}
}
void navigateToHome() {
print('Navigating to home');
navigatorKey.currentState?.pushNamed('/');
}
// Utility functions
void applyPromoCode(String code) {
print('Applying promo code: $code');
// Your promo code logic
}
void setUserSegment(String segment) {
print('Setting user segment: $segment');
// Your user segmentation logic
}
void trackCampaign(String campaignId) {
print('Tracking campaign: $campaignId');
// Your campaign tracking logic
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
navigatorKey: navigatorKey,
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/product': (context) => ProductScreen(),
'/promo': (context) => PromoScreen(),
'/category': (context) => CategoryScreen(),
},
);
}
}
// Placeholder screen classes
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(child: Text('Home Screen')),
);
}
}
class ProductScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product')),
body: Center(child: Text('Product Screen')),
);
}
}
class PromoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Promo')),
body: Center(child: Text('Promo Screen')),
);
}
}
class CategoryScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Category')),
body: Center(child: Text('Category Screen')),
);
}
}
Mejores prácticas:
- Analiza las URL de forma segura: Valida y desinfecta siempre las URL de enlaces profundos antes de la navegación para evitar vulnerabilidades de seguridad.
- Manejar el estado de navegación: Utiliza GlobalKey para NavigatorState para habilitar la navegación antes de que MaterialApp se inicialice por completo.
- Pruebe ambos escenarios: Pruebe tanto los enlaces profundos inmediatos (aplicación instalada) como los enlaces profundos diferidos (nueva instalación) durante el desarrollo.
- Registro para depuración: Habilite un registro exhaustivo durante el desarrollo para rastrear la resolución de enlaces profundos y el flujo de navegación.
- Gestión de errores: Implemente bloques try-catch para el análisis de JSON y las operaciones de navegación para gestionar correctamente los datos malformados.