Adição de suporte para Deep Linking
Os links direcionam os usuários para um conteúdo específico dentro do seu aplicativo. Quando os utilizadores tocam numa ligação direta num dispositivo com a sua aplicação instalada, a aplicação abre-se diretamente para o conteúdo pretendido, como uma página de produto ou uma experiência específica.
Os links de rastreamento singulares suportam links diretos padrão (para aplicativos instalados) e links diretos adiados (para novas instalações). Para obter informações abrangentes, consulte as Perguntas frequentes sobre links diretos e as Perguntas frequentes sobre links singulares.
Caminhos de implementação: Este guia cobre ambas as implementações Bare React Native e Expo. A distinção existe porque a Expo abstrai o gerenciamento de código nativo para simplificar o desenvolvimento do React Native, enquanto o bare React Native fornece acesso direto às pastas nativas do iOS e Android. Escolha o caminho de implementação apropriado para a estrutura do seu projeto.
Requisitos
Pré-requisitos
Preencha os Pré-requisitos de Links Singulares para habilitar o deep linking para seu aplicativo.
Observações:
- Este artigo assume que sua organização está usando Singular Links - a tecnologia de link de rastreamento da Singular. Os clientes mais antigos podem estar usando links de rastreamento herdados.
- Os destinos de links diretos do seu aplicativo precisam ser configurados na página Aplicativos no Singular (consulte Configurando seu aplicativo para rastreamento de atribuição).
Implementar o manipulador de links do Singular
O manipulador SingularLink fornece um mecanismo de retorno de chamada para recuperar o deep link, o deferred deep link, os parâmetros de passagem e os parâmetros de URL dos links de rastreamento Singular quando o aplicativo é aberto.
Parâmetros disponíveis:
- 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 os utilizadores que instalam a aplicação depois de clicarem no link
- Passthrough (_p): Dados personalizados passados através do link de rastreamento para contexto adicional
- urlParameters: Objeto de parâmetros de consulta de um Link Singular que abriu o aplicativo. Não aplicável a cenários de Deferred Deep Link.
Implementação nativa do React simples
Configure o deep linking para projetos React Native simples com acesso direto ao código nativo do iOS e do Android.
Configuração da plataforma iOS
Atualizar iOS AppDelegate
Habilite o SDK Singular para processar dados relacionados ao lançamento e manipular links diretos passando os objetos launchOptions e userActivity para o SDK Singular em seu arquivo AppDelegate.
import Singular
// Deferred Deep link and Short Link support
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithLaunchOptions:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: launchOptions, afterDelay: 1)
}
}
factory.startReactNative(
withModuleName: "AppName", // Update with your App Name
in: window,
launchOptions: launchOptions
)
return true
}
// Universal Links (NSUserActivity)
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let url = userActivity.webpageURL {
print("Singular: Universal link received:", url.absoluteString)
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity, afterDelay: 1)
}
}
}
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}
// Top of the AppDelegate.mm
#import <React/RCTLinkingManager.h>
#import <Singular-React-Native/SingularBridge.h>
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Add inside didFinishLaunchingWithOptions
[SingularBridge startSessionWithLaunchOptions:launchOptions];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
[SingularBridge startSessionWithUserActivity:userActivity];
return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
}
Esses métodos garantem que o SDK Singular receba 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 link profundo.
Configuração do SceneDelegate no iOS
A configuração do AppDelegate acima abrange os aplicativos que usam o ciclo de vida clássico dos aplicativos iOS. Se o seu aplicativo optar pelas cenas do UIKit, o seu projeto é o proprietário do SceneDelegate e os universal links que ele recebe nunca chegam aos métodos do AppDelegate. Em vez disso, encaminhe-os a partir dos callbacks da cena.
Disponibilidade: SDK do React Native versão 4.3.0 e superiores. As versões anteriores descartavam um universal link que chegava antes de a camada JavaScript ter chamado init(). A partir da versão 4.3.0, o link é retido e reenviado assim que a configuração fica disponível, de modo que os links de inicialização a frio não são mais perdidos.
Encaminhar os links do ciclo de vida da cena
Chame startSessionWithUserActivity: a partir de scene:willConnectToSession:options: para o caso de inicialização a frio e a partir de scene:continueUserActivity: para um link entregue a um aplicativo já em execução.
// Cold start: a universal link present when the scene connects
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UISceneConnectionOptions) {
if let userActivity = connectionOptions.userActivities.first {
forwardToSingular(userActivity)
}
}
// Warm: a universal link delivered to a running app
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
forwardToSingular(userActivity)
}
private func forwardToSingular(_ userActivity: NSUserActivity) {
guard userActivity.webpageURL != nil else { return }
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity)
}
}
}#import <Singular-React-Native/SingularBridge.h>
// Cold start: a universal link present when the scene connects
- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
NSUserActivity *userActivity = connectionOptions.userActivities.anyObject;
if (userActivity.webpageURL) {
[SingularBridge startSessionWithUserActivity:userActivity];
}
}
// Warm: a universal link delivered to a running app
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
if (userActivity.webpageURL) {
[SingularBridge startSessionWithUserActivity:userActivity];
}
}Observação: A ponte do iOS não expõe nenhum ponto de entrada para URLs com esquema personalizado, portanto, os deep links com esquema URI não são compatíveis no iOS. Use universal links, que os Singular Links emitem por padrão.
Configuração da plataforma Android
Atualizar a MainActivity do Android
Habilite o SDK Singular para processar dados relacionados à inicialização e manipular links profundos modificando o arquivo MainActivity para passar o objeto Intent para o SDK Singular.
// Add as part of the imports at the top of the class
import android.content.Intent
import net.singular.react_native.SingularBridgeModule
// Add to the MainActivity class
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridgeModule.onNewIntent(intent)
}
}
// Add as part of the imports at the top of the class
import android.content.Intent;
import net.singular.react_native.SingularBridgeModule;
// Add to the MainActivity class
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridgeModule.onNewIntent(intent);
}
}
O objeto Intent contém informações sobre como e por que seu aplicativo foi lançado, que o Singular usa para rastreamento de atribuição e navegação de link profundo.
Configuração do SDK
Adicionar retorno de chamada withSingularLink
Configure o retorno de chamada withSingularLink para manipular dados de deep link recebidos e dados de deep link adiados durante a inicialização do SDK.
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import type { SingularConfig } from 'singular-react-native/js/NativeSingular';
import { NativeEventEmitter } from 'react-native';
function App() {
useEffect(() => {
// Create config
const config: SingularConfig = {
apikey: 'YOUR_SDK_KEY',
secret: 'YOUR_SDK_SECRET'
// Note: Deep link handlers are NOT set in config for TurboModule
};
NativeSingular.init(config);
// Set up deep link handler using event emitter
const emitter = new NativeEventEmitter(NativeSingular);
const linkListener = emitter.addListener('SingularLinkHandler', (params) => {
console.log('Singular Link resolved:', params);
// Extract deep link parameters
console.log('Singular: Deep link received:', params.deeplink);
console.log('Singular: Passthrough data:', params.passthrough);
console.log('Singular: Is deferred:', params.isDeferred);
console.log('Singular: urlParameters:', params.urlParameters);
// Handle deep link navigation
});
// Clean up event listener on unmount
return () => {
linkListener.remove();
};
}, []);
return (
// Your app components
null
);
}
import React, { useEffect } from 'react';
import { Singular, SingularConfig } from 'singular-react-native';
function App() {
useEffect(() => {
const config = new SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
)
.withSingularLink((params) => {
console.log('Singular Link resolved:', params);
// Extract deep link parameters
console.log('Singular: Deep link received:', params.deeplink);
console.log('Singular: Passthrough data:', params.passthrough);
console.log('Singular: Is deferred:', params.isDeferred);
console.log('Singular: urlParameters:', params.urlParameters);
// Handle deep link navigation
});
// Initialize SDK
Singular.init(config);
}, []);
return (
// Your app components
);
}
Observação: O retorno de chamada withSingularLink é acionado somente quando o aplicativo é aberto por meio de um Singular Link. Para obter mais informações, consulte as Perguntas frequentes sobre links singulares.
Para obter a documentação completa do método, consulte a referência withSingularLink.
Implementação da Expo
Configure o deep linking para projetos Expo usando arquivos de configuração e plug-ins personalizados para lidar com modificações de código nativo automaticamente.
Configuração do aplicativo
Atualizar App.json para Deep Linking
Devido à abstração da Expo do gerenciamento de código nativo, adicione configurações de deep linking específicas da plataforma no arquivo app.json.
Configuração do iOS
- Adicione o recurso associatedDomains com seu domínio de link de rastreamento Singular
- Adicione o esquema para suporte de esquema de URL personalizado (fallback) No iOS, isso cobre as aberturas em que o app ainda não está em execução; um link de esquema que abre um app já em execução não é repassado ao Singular.
Configuração do Android
-
Actualize o valor do anfitrião para corresponder ao seu domínio de ligações Singular (por exemplo,
example.sng.link) - Configurar o esquema no filtro de última intenção para suporte de ligação de esquema tradicional
{
"expo": {
"name": "MySimpleApp",
"slug": "mysimpleapp",
"scheme": "ios-app-scheme",
"ios": {
"associatedDomains": [
"applinks:example.sng.link"
]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/A"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/A"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/B"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/B"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/E"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/E"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/F"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/F"
}
],
"category": [
"BROWSABLE",
"DEFAULT"
]
},
{
"action": "VIEW",
"data": [
{
"scheme": "android-app-scheme"
}
],
"category": [
"BROWSABLE",
"DEFAULT"
]
}
]
}
}
}
Plug-ins personalizados da Expo
Criar plug-ins específicos da plataforma
Esses arquivos de plug-in automatizam as modificações de código nativo necessárias para a integração do SDK Singular no fluxo de trabalho gerenciado da Expo, eliminando a necessidade de edição manual após cada expo prebuild.
- Plug-in iOS: Detecta Swift ou Objective-C AppDelegate e injeta instruções de importação, registro de identificador de dispositivo e código de manipulação de link universal que encaminha dados de atribuição para a ponte Singular React Native
-
Plug-in do Android: Detecta arquivos Java ou Kotlin MainActivity e adiciona importações, registro de intenção e o método
onNewIntentque captura links diretos e os passa para o módulo de ponte do Singular
Ambos os plug-ins garantem que, quando os usuários tocam em deep links ou links universais, o código nativo captura e encaminha adequadamente esses dados para seus hooks do React Native para rastreamento de atribuição.
Etapas de configuração
- Navegue até o diretório raiz do seu projeto
- Crie uma pasta de plug-ins
- Adicione os seguintes arquivos de plug-in JavaScript
Implementação do plug-in do iOS
Crie plugins/singular-ios-deeplink-plugin.js para configurar automaticamente o iOS deep linking.
// plugins/singular-ios-deeplink-plugin.js
const { withAppDelegate } = require('@expo/config-plugins');
const withUniversalSingular = (config) => {
return withAppDelegate(config, (config) => {
const { modResults } = config;
// Detect file type
const isSwift = modResults.contents.includes('import Expo') ||
modResults.contents.includes('class AppDelegate');
const isObjectiveC = modResults.contents.includes('#import') ||
modResults.contents.includes('@implementation');
console.log(`Detected AppDelegate type: ${isSwift ? 'Swift' : isObjectiveC ? 'Objective-C' : 'Unknown'}`);
if (isSwift) {
modResults.contents = applySwiftModifications(modResults.contents);
} else if (isObjectiveC) {
modResults.contents = applyObjectiveCModifications(modResults.contents);
} else {
console.warn('Unable to detect AppDelegate type - skipping Singular modifications');
}
return config;
});
};
function applySwiftModifications(contents) {
// Add Singular import
if (!contents.includes('import Singular')) {
contents = contents.replace(
/import ReactAppDependencyProvider/,
`import ReactAppDependencyProvider
import Singular`
);
}
// Add IDFV logging
const swiftLaunchPattern = /didFinishLaunchingWithOptions.*?\) -> Bool \{/s;
if (!contents.includes('UIDevice.current.identifierForVendor')) {
contents = contents.replace(
swiftLaunchPattern,
`didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
print("IDFV", UIDevice.current.identifierForVendor!.uuidString)`
);
}
// Add universal link handling
if (!contents.includes('Universal link received for Singular')) {
contents = contents.replace(
/continue userActivity: NSUserActivity,\s*restorationHandler: @escaping \(\[UIUserActivityRestoring\]\?\) -> Void\s*\) -> Bool \{/,
`continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if let url = userActivity.webpageURL {
print("Universal link received for Singular:", url.absoluteString)
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity, afterDelay: 1)
}
}
}`
);
}
return contents;
}
function applyObjectiveCModifications(contents) {
// Add Singular import
if (!contents.includes('#import <Singular-React-Native/SingularBridge.h>')) {
contents = contents.replace(
/#import "AppDelegate.h"/,
`#import "AppDelegate.h"
#import <Singular-React-Native/SingularBridge.h>`
);
}
// Add IDFV logging to didFinishLaunchingWithOptions
const objcLaunchPattern = /- \(BOOL\)application:\(UIApplication \*\)application didFinishLaunchingWithOptions:\(NSDictionary \*\)launchOptions\s*{/;
if (!contents.includes('NSLog(@"IDFV %@"')) {
contents = contents.replace(
objcLaunchPattern,
`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"IDFV %@", [UIDevice currentDevice].identifierForVendor.UUIDString);
[SingularBridge startSessionWithLaunchOptions:launchOptions];`
);
}
// Add continueUserActivity method if not present
if (!contents.includes('continueUserActivity')) {
const methodToAdd = `
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
NSLog(@"Universal link received for Singular: %@", userActivity.webpageURL.absoluteString);
[SingularBridge startSessionWithUserActivity:userActivity];
return YES;
}`;
contents = contents.replace(
/@end$/,
`${methodToAdd}
@end`
);
}
return contents;
}
module.exports = withUniversalSingular;
Implementação do plug-in do Android
Crie plugins/singular-android-deeplink-plugin.js para configurar automaticamente o Android deep linking.
// plugins/singular-android-deeplink-plugin.js
const { withMainActivity } = require('@expo/config-plugins');
const withSingularAndroid = (config) => {
// Add MainActivity modifications
config = withMainActivity(config, (config) => {
const { modResults } = config;
// Detect Java vs Kotlin
const isKotlin = modResults.contents.includes('class MainActivity') &&
modResults.contents.includes('override fun');
const isJava = modResults.contents.includes('public class MainActivity') &&
modResults.contents.includes('@Override');
if (isKotlin) {
modResults.contents = applyKotlinModifications(modResults.contents);
} else if (isJava) {
modResults.contents = applyJavaModifications(modResults.contents);
}
return config;
});
return config;
};
function applyKotlinModifications(contents) {
// Add imports
if (!contents.includes('android.content.Intent')) {
contents = contents.replace(
/package com\..*?\n/,
`$&
import android.content.Intent
import net.singular.react_native.SingularBridgeModule`
);
}
// Add onNewIntent method
if (!contents.includes('onNewIntent')) {
const methodToAdd = `
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridgeModule.onNewIntent(intent)
}
}`;
contents = contents.replace(
/}\s*$/,
`${methodToAdd}
}`
);
}
return contents;
}
function applyJavaModifications(contents) {
// Add imports
if (!contents.includes('android.content.Intent')) {
contents = contents.replace(
/package com\..*?;/,
`$&
import android.content.Intent;
import net.singular.react_native.SingularBridgeModule;`
);
}
// Add onNewIntent method
if (!contents.includes('onNewIntent')) {
const methodToAdd = `
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridgeModule.onNewIntent(intent);
}
}`;
contents = contents.replace(
/}\s*$/,
`${methodToAdd}
}`
);
}
return contents;
}
module.exports = withSingularAndroid;
Registar plug-ins
Atualize seu arquivo app.json para incluir os novos plug-ins personalizados juntamente com o plug-in Singular React Native.
{
"expo": {
"plugins": [
"singular-react-native",
"./plugins/singular-ios-deeplink-plugin.js",
"./plugins/singular-android-deeplink-plugin.js"
]
}
}
Configuração de SDK para Expo
Configure o retorno de chamada withSingularLink em seu aplicativo Expo usando a mesma abordagem do React Native simples. Consulte a seção Configuração do SDK acima para obter detalhes completos de implementação.
Comportamento do manipulador
Entendendo a resolução de links
O manipulador withSingularLink se comporta de maneira diferente, dependendo se o aplicativo foi instalado recentemente ou já está instalado.
Instalação recente (Deep Link diferido)
Em uma instalação recente, não existe nenhuma URL aberta quando o aplicativo é iniciado. A Singular completa a atribuição para determinar se o link de rastreamento contém um valor de deep link ou de deferred deep link.
Fluxo de Deep Link Diferido:
- O usuário clica em um link de rastreamento da Singular configurado com um valor de deep link
- O usuário instala e abre o aplicativo pela primeira vez
- O Singular SDK envia a primeira sessão para os servidores Singular
- A atribuição é concluída e identifica o deep link a partir do link de rastreamento
-
O valor do deep link retorna para o manipulador
withSingularLinkno parâmetrodeeplinkcomisDeferred = true
Testando Deep Links adiados:
- Desinstale o aplicativo do dispositivo de teste (se estiver instalado no momento)
- iOS: Redefinir seu IDFA. Android:Redefina seu ID de publicidade do Google (GAID)
- Clique no link de rastreamento Singular do dispositivo (verifique se ele está configurado com um valor de link profundo)
- Instale e abra o aplicativo
A atribuição deve ser concluída com êxito e o valor diferido do deep link será passado para o manipulador withSingularLink.
Dica profissional: Ao testar links diretos com uma compilação de desenvolvimento usando um nome de pacote diferente (por exemplo, com.example.devem 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 compilação de desenvolvimento diretamente no dispositivo (através do Android Studio ou Xcode) em vez de transferir a aplicação de produção a partir da loja de aplicações.
Já instalado (ligação direta imediata)
Quando o aplicativo já está instalado, clicar em um link singular abre o aplicativo imediatamente usando a tecnologia Universal Links (iOS) ou Android App Links.
Fluxo imediato do Deep Link:
- O usuário clica em um link de rastreamento Singular
- O sistema operacional fornece um URL aberto contendo todo o link de rastreamento Singular
- Durante a inicialização do SDK, a Singular analisa a URL
-
A Singular extrai os valores
deeplinkepassthrough -
Os valores retornam através do handler
withSingularLinkcomisDeferred = false
Recursos avançados
Parâmetros de passagem
Capture dados adicionais do clique do link de rastreamento usando parâmetros de passagem.
Se um parâmetro passthrough (_p) for incluído no link de rastreamento, o parâmetro passthroughdo manipulador withSingularLink conterá os dados correspondentes. Utilize-o para capturar metadados de campanha, dados de segmentação de utilizadores ou qualquer informação personalizada de que necessite na aplicação.
Exemplo de valor de passagem:
{
"utm_source": "Facebook",
"utm_campaign": "Web-to-App US RON",
"utm_creative": "Blue CTA for Promo",
"promo_code": "45k435hg"
}
Exemplo de link de rastreamento com valor de passagem codificado por URL no parâmetro (_p):
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_p=%7B%22utm_source%22%3A%20%22Facebook%22%2C%22utm_campaign%22%3A%20%22Web-to-App%20US%20RON%22%2C%22utm_creative%22%3A%20%22Blue%20CTA%20for%20Promo%22%2C%22promo_code%22%3A%20%2245k435hg%22%7D
O manipulador withSingularLink receberá:
urlParameters = {"utm_source": "Facebook","utm_campaign": "Web-to-App US RON","utm_creative": "Blue CTA for Promo","promo_code": "45k435hg"}
Encaminhar todos os parâmetros de consulta
Capture todos os parâmetros de consulta do 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 withSingularLink, dando-lhe 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 withSingularLink receberá:
deeplink = "myapp://product/123?utm_source=facebook&promo=SALE2024"