Adicionando suporte a links diretos
Os links diretos direcionam os usuários para um conteúdo específico dentro do seu aplicativo. Quando os usuários tocam em um link direto em um dispositivo com o seu aplicativo instalado, o aplicativo abre diretamente no conteúdo pretendido, como uma página de produto ou uma experiência específica.
Os links de rastreamento da Singular oferecem suporte tanto a links diretos padrão (para aplicativos instalados) quanto a links diretos diferidos (para novas instalações). Para obter informações completas, consulte as Perguntas frequentes sobre links diretos e as Perguntas frequentes sobre links da Singular.
Requisitos
Pré-requisitos
Conclua os pré-requisitos dos links Singular para habilitar o deep linking para seu aplicativo.
Observações:
- Este artigo pressupõe que sua organização está usando o Singular Links, a tecnologia de links de rastreamento da Singular. Clientes mais antigos podem estar usando links de rastreamento legados.
- Os destinos dos links diretos do seu aplicativo precisam ser configurados na página Aplicativos do Singular (consulte Configurando seu aplicativo para rastreamento de atribuição).
Parâmetros disponíveis
O manipulador SingularLink fornece acesso a parâmetros de links diretos, links diretos diferidos e passagem de parâmetros dos links de rastreamento da Singular quando o aplicativo é aberto.
- Deep Link (_dl): o URL de destino dentro do seu aplicativo para usuários que clicam no link
- Deep link diferido (_ddl): o URL de destino para usuários que instalam o aplicativo após clicar no link
- Passthrough (_p): dados personalizados passados pelo link de rastreamento para contexto adicional
Compatibilidade com a versão do Flutter
Alteração significativa no Flutter 3.35
O Flutter 3.35 introduziu uma alteração significativa na forma como os links profundos são processados.
A função interna didPushRouteInformation() agora intercepta
e decodifica links profundos antes que SDKs de terceiros possam processá-los. Isso cria
um conflito com o manipulador de links profundos do Singular, fazendo com que os links do Singular sejam
tratados como rotas desconhecidas e direcionando os usuários para a tela inicial em vez de
para o destino pretendido.
Isso afeta tanto o Android quanto o iOS. Se o seu aplicativo tem como alvo o Flutter 3.35 ou posterior, conclua a configuração adicional específica da plataforma abaixo antes de prosseguir com a implementação padrão de links diretos.
Observação: esta é uma alteração no nível da estrutura do Flutter, não um bug do SDK da Singular. Não é necessária nenhuma atualização do SDK. As configurações abaixo restauram o comportamento correto do roteamento de links profundos para todos os estados do aplicativo (primeiro plano, segundo plano e inicialização a frio).
Configuração do Android (Flutter 3.35+)
Duas alterações são necessárias no Android: desativar o manipulador de links profundos integrado do Flutter e encaminhar manualmente as intenções para o mecanismo Flutter e o SDK Singular.
Etapa 1: desativar o manipulador de links profundos do Flutter
Em seu AndroidManifest.xml, adicione a seguinte
entrada <meta-data> dentro da tag <activity>:
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="false" />
Passo 2: Encaminhe manualmente as intenções
Atualize seu MainActivity para encaminhar intenções tanto para o
mecanismo Flutter quanto para o SDK Singular.
// 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)
setIntent(intent)
// Forward to Flutter engine
flutterEngine?.activityControlSurface?.onNewIntent(intent)
// Forward to Singular SDK
if (intent.data != null) {
SingularBridge.onNewIntent(intent)
}
}
// 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) {
super.onNewIntent(intent);
setIntent(intent);
// Forward to Flutter engine
if (getFlutterEngine() != null) {
getFlutterEngine().getActivityControlSurface().onNewIntent(intent);
}
// Forward to Singular SDK
if (intent.getData() != null) {
SingularBridge.onNewIntent(intent);
}
}
Observação: isso substitui a implementação padrão do
onNewIntent mostrada na seção
Configuração da plataforma Android abaixo para
projetos Flutter 3.35+. Se você estiver usando o Flutter 3.10.x ou anterior, use
a implementação padrão.
Configuração do iOS (Flutter 3.35+)
No Info.plist do seu aplicativo, defina o
sinalizador FlutterDeepLinkingEnabled como false para
desativar a interceptação de links profundos nativos do Flutter:
<key>FlutterDeepLinkingEnabled</key>
<false/>
Observação: não são necessárias alterações adicionais no
AppDelegate além desse sinalizador. Continue com a
configuração padrão do AppDelegate do iOS mostrada na seção
Configuração da plataforma iOS abaixo.
Configuração da plataforma
Configuração da plataforma iOS
Atualize o iOS AppDelegate
Habilite o SDK Singular para processar dados relacionados ao lançamento e lidar com links profundos
passando os objetos launchOptions e
userActivity para o SDK Singular em seu
arquivo AppDelegate.
Esses objetos contêm informações críticas sobre como e por que seu aplicativo foi iniciado, que o Singular usa para rastreamento de atribuição e navegação de links diretos .
Implementação em 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];
}
Implementação 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)
}
Configuração da plataforma Android
Atualize o Android MainActivity
Habilite o SDK Singular para processar dados relacionados ao lançamento e lidar com links profundos
modificando o arquivo MainActivity para passar o
objeto Intent para o SDK Singular.
O objeto Intent contém informações sobre como e por que
seu aplicativo foi iniciado, que o Singular usa para rastreamento de atribuição e
navegação de links profundos.
Implementação 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);
}
}
Implementação 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)
}
}
Configuração do SDK
Implementar o manipulador de links Singular
Configure o retorno de chamada singularLinksHandler durante a inicialização do SDK
para lidar com dados de links diretos recebidos e links diretos 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(),
);
}
}
Propriedade de configuração:
void Function(SingularLinkParams)? singularLinksHandler
Observação: a chamada de retorno singularLinksHandler é acionada apenas quando o aplicativo é aberto por meio de um Singular Link. Para
obter mais informações, consulte as
Perguntas frequentes sobre Singular Links.
Para obter a documentação completa, consulte a referência singularLinksHandler.
Comportamento do manipulador
Entendendo a resolução de links
O singularLinksHandler se comporta de maneira diferente dependendo se o aplicativo foi instalado recentemente ou já estava instalado.
Instalação nova (deep link diferido)
Em uma instalação nova, não existe um URL aberto quando o aplicativo é iniciado. O Singular conclui a atribuição para determinar se o link de rastreamento continha um valor de link direto ou link direto diferido.
Fluxo de link direto diferido:
- O usuário clica em um link de rastreamento Singular configurado com um valor de deep link
- O usuário instala e abre o aplicativo pela primeira vez
- O SDK do Singular envia a primeira sessão para os servidores do Singular
- A atribuição é concluída e identifica o deep link a partir do link de rastreamento
- O valor do deep link retorna para a chamada de retorno
singularLinksHandlerno parâmetrodeeplinkcomisDeferred = true
Testando links profundos diferidos:
- Desinstale o aplicativo do dispositivo de teste (se estiver instalado)
- iOS: redefina seu IDFA. Android: redefina seu Google Advertising ID (GAID)
- Clique no link de rastreamento Singular no dispositivo (certifique-se de que ele esteja configurado com um valor de link direto)
- Instale e abra o aplicativo
A atribuição deve ser concluída com sucesso, e o valor do link direto diferido será passado para o manipulador singularLinksHandler.
Dica profissional: ao testar links diretos com uma versão de desenvolvimento usando um nome de pacote diferente (por exemplo, com.example.dev em vez de com.example.prod), configure o link de rastreamento especificamente para o nome do pacote do aplicativo de desenvolvimento. Depois de clicar no link de teste, instale a versão de desenvolvimento diretamente no dispositivo (via Android Studio ou Xcode) em vez de baixar o aplicativo de produção da loja de aplicativos.
Já instalado (link direto imediato)
Quando o aplicativo já está instalado, clicar em um Singular Link abre o aplicativo imediatamente usando a tecnologia Universal Links (iOS) ou Android App Links.
Fluxo de deep link imediato:
- O usuário clica em um link de rastreamento Singular
- O sistema operacional fornece um Open URL contendo o link de rastreamento Singular completo
- Durante a inicialização do SDK, o Singular analisa a URL
- O Singular extrai os valores
deeplinkepassthrough - Os valores são retornados através do manipulador
singularLinksHandlercomisDeferred = false
Recursos avançados
Parâmetros de passagem
Capture dados adicionais do clique no link de rastreamento usando parâmetros de passagem .
Se um parâmetro passthrough (_p) estiver incluído no link de rastreamento
, o parâmetro passthrough do manipulador singularLinksHandler contém os dados correspondentes. Use
isso para capturar metadados da campanha, dados de segmentação do usuário ou qualquer
informação personalizada necessária no aplicativo.
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');
}
Encaminhar todos os parâmetros de consulta
Capture todos os parâmetros de consulta da URL do link de rastreamento anexando o parâmetro _forward_params=2 ao seu link de rastreamento.
Quando _forward_params=2 é adicionado ao link de rastreamento, todos os parâmetros de consulta são incluídos no parâmetro deeplink do manipulador singularLinksHandler, dando a você acesso ao URL completo com todos os seus parâmetros.
Exemplo de link de rastreamento:https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_forward_params=2&utm_source=facebook&promo=SALE2024
O manipulador singularLinksHandler receberá:deeplink = "myapp://product/123?utm_source=facebook&promo=SALE2024"
Exemplo de implementação completa
Implementação abrangente de deep linking com navegação, tratamento de passagem e encaminhamento de parâmetros para aplicativos 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')),
);
}
}
Práticas recomendadas:
- Analise URLs com segurança: sempre valide e sanitize URLs de links diretos antes da navegação para evitar vulnerabilidades de segurança
- Lidar com o estado de navegação: use GlobalKey para NavigatorState para habilitar a navegação antes que o MaterialApp seja totalmente inicializado
- Teste os dois cenários: teste links diretos imediatos (aplicativo instalado) e links diretos diferidos (nova instalação) durante o desenvolvimento
- Registre para depuração: habilite o registro abrangente durante o desenvolvimento para rastrear a resolução de links profundos e o fluxo de navegação .
- Tratamento de erros: implemente blocos try-catch para análise JSON e operações de navegação para lidar com dados malformados de maneira elegante