Диплинки и динамические ссылки

опасно
Firebase Dynamic Links отключены

25 августа 2025 года Firebase Dynamic Links прекратили работу — см. объявление Firebase. Раздел про них оставлен для тех, кто разбирает старые проекты; для новых берите Branch.io.

Диплинк — ссылка, которая открывает конкретный экран приложения, а не главную. Вместе со ссылкой можно передать данные, по которым экран поймёт, что именно показывать.

Пример: вы отправили другу пост из соцсети, и он открывается сразу — искать его в приложении руками не нужно, всё работает как обычная ссылка на сайт.

Схема работы:

img.png

Как отрабатывает диплинк и динамическая ссылка

При переходе по ссылке сначала проверяется, установлено ли приложение. Если нет — открывается App Store или Google Play. После установки, если экран закрыт авторизацией, покажется вход, а уже потом — то, чем с вами поделились.

Важная особенность: обычный путь к экрану внутри приложения может быть длинным — «Главная → Все посты → Пост». Ссылка его пропускает и ведёт сразу на нужную страницу.

Диплинк открывает конкретный экран приложения — при условии, что приложение уже установлено на устройстве.

Структура ссылки

Ссылка состоит из трёх частей: схема, хост и имя страницы — например, designersapp://designersapp.com/profile.

img_1.png

Без имени страницы (designersapp://mydesignersapp.com/) откроется стартовый экран.

img_2.png

Разберём на примере: делимся ссылкой на страницу профиля и открываем её.

Отправка диплинка и переход по нему

Четыре шага:

  1. Задать схему URL
  2. Задать адрес страницы
  3. Настроить отправку ссылки
  4. Проверить ссылку

1. Схема URL

  1. Откройте Settings & Integrations > General > App Details.

  2. Если диплинки ведут на несколько страниц и все они требуют входа, включите Pages Requires Authentication by Default.

  3. В полях URL scheme уже стоят значения, собранные из имени проекта. Поменяйте scheme (до ://) и hostname (после ://), если нужно своё.

  4. Переключатель Pages Are Subroutes of Root Page делает так, что кнопка «назад» с открытой по ссылке страницы ведёт на главную, а не закрывает приложение.

img_3.png

совет

Последнюю настройку стоит включить: закрывшееся по «назад» приложение пользователь обычно уже не открывает заново.

2. Адрес страницы

Адрес страницы используется и в вебе, и для диплинков на мобильных.

  1. Выберите страницу, которая должна открываться по ссылке.

  2. В панели свойств справа откройте раздел Route Settings.

  3. По умолчанию маршрут совпадает с именем страницы — измените, если в адресе нужно другое.

  4. Отметьте Requires Authentication, если страница доступна только после входа.

Настройка адреса страницы

Отправить диплинк текущей страницы можно действием Share.

  1. Откройте страницу, ссылку на которую нужно отдавать.

  2. Выделите виджет — например, кнопку «Поделиться».

  3. Выберите Actions в панели свойств и нажмите Open — откроется Action Flow Editor.

    1. Нажмите + Add Action.
    2. Справа найдите действие Share.
    3. Value SourceFrom Variable.
    4. SourceGlobal Properties.
    5. Available OptionsLink To Current Page, затем Close.
Отправка диплинка

В Run Mode диплинки не работают — проверять нужно на устройстве или эмуляторе.

Сначала получите саму ссылку: запустите приложение на устройстве, нажмите кнопку «Поделиться» и скопируйте её.

Дальше есть два способа.

Через командную строку

Если установлена Android Studio с platform-tools, выполните в терминале, подставив свою ссылку:

adb shell am start -a android.intent.action.VIEW \
    -c android.intent.category.BROWSABLE \
    -d "designersapp://designersapp.com/profile"
Через мобильный Firefox

Откройте браузер, вставьте ссылку в адресную строку, откройте меню и выберите Open in app.

Открытие диплинка через мобильный Firefox

Динамическая ссылка тоже открывает конкретный экран, но, в отличие от диплинка, переживает установку приложения: если приложения нет, пользователь попадает в магазин, а после установки — сразу на нужную страницу.

Работает она поверх диплинка — то есть его нужно настроить в любом случае.

заметка

Здесь используется Firebase Dynamic Links. Сервис отключён 25 августа 2025 года; раздел оставлен для разбора существующих проектов.

Пример динамической ссылки

Шаги были такие:

  1. Настроить домен
  2. Настроить iOS
  3. Задать схему URL
  4. Задать адрес страницы
  5. Настроить отправку ссылки
  6. Проверить ссылку

1. Домен

Динамической ссылке нужен домен — он становится префиксом адреса.

  1. Откройте консоль Firebase и выберите Dynamic Link в меню слева.

  2. Нажмите Get Started.

  3. Введите домен. Своего нет — возьмите бесплатный Google Provided Domain, оканчивающийся на page.link. Для своего домена см. инструкцию Firebase.

  4. С доменом от Google на этом можно закончить — Finish.

Настройка домена для динамической ссылки

2. Настройка iOS

Для работы на iOS требовались два дополнительных шага.

2.1 App Store ID и Team ID в проекте Firebase

  1. Откройте консоль Firebase и выберите Project Overview.

  2. Выберите проект iOS и нажмите значок шестерёнки.

  3. Прокрутите до выбранного проекта iOS.

  4. В поле App Store ID нажмите на карандаш, введите идентификатор и нажмите Save. Где его взять, подсказывает значок вопроса рядом.

  5. То же самое для поля Team ID.

Добавление App Store ID и Team ID

2.2 Возможность Associated Domains

  1. Откройте портал разработчика Apple и выберите Certificates, IDs & Profiles.

  2. Выберите Identifiers и нажмите на идентификатор своего приложения.

  3. Отметьте Associated Domains и нажмите Save.

Включение Associated Domains

3. Схема URL

  1. Откройте Settings & Integrations > General > App Details.

  2. Если ссылки ведут на страницы, требующие входа, включите Pages Requires Authentication by Default.

  3. Включите Use Firebase Dynamic Links.

  4. В полях URL scheme задайте scheme (до ://) и hostname (после ://).

  5. Включите Pages Are Subroutes of Root Page, чтобы кнопка «назад» вела на главную, а не закрывала приложение.

img_4.png

4. Адрес страницы

  1. Выберите страницу, которая должна открываться по ссылке.

  2. В панели свойств откройте раздел Route Settings.

  3. При необходимости измените маршрут — по умолчанию он совпадает с именем страницы.

  4. Отметьте Requires Authentication, если страница доступна только после входа.

Настройка адреса страницы

Здесь нужны два действия подряд: Generate Current Page Link собирает ссылку, Share её отправляет.

  1. Откройте нужную страницу.

  2. Выделите виджет — например, кнопку «Поделиться».

  3. Добавьте действие Generate Current Page Link.

  4. Нажмите + под его блоком и выберите Add Action.

  5. Справа найдите действие Share.

  6. Value SourceFrom Variable.

  7. SourceWidget State.

  8. Available OptionsCurrent Page Link, затем Close.

Отправка динамической ссылки

В Run Mode динамические ссылки не работают — нужно устройство или эмулятор.

Запустите приложение, нажмите «Поделиться» и скопируйте ссылку. Затем вставьте её в адресную строку мобильного Firefox.

Проверка динамической ссылки

Обычно вместе со ссылкой нужно передать данные: код скидки для страницы товара, идентификатор профиля для страницы профиля. По ним страница и понимает, что показывать.

Передача идентификатора профиля в ссылке

Нужно две вещи.

  1. Параметр на целевой странице.

img_5.png

Параметр страницы
  1. Этот же параметр в маршруте, в Route Settings, с двоеточием перед именем — например, profilePage/:profileId.

img_6.png

Параметр в составе маршрута

Этого достаточно и для диплинка, и для динамической ссылки.

Раз Firebase Dynamic Links больше нет, для новых проектов берут Branch.io — кроссплатформенный сервис диплинков, в том числе отложенных (тех, что срабатывают после установки приложения).

Свой бэкенд для этого писать не нужно.

Настройка в Branch.io

Заведите проект в панели Branch и дальше по шагам.

1. Branch Key

Первым делом запишите Branch Key — он понадобится при настройке FlutterFlow.

Этот ключ однозначно определяет ваше приложение.

2. Ссылки перенаправления

В панели Branch задаются запасные адреса — куда отправить пользователя, если приложение не установлено. Обычно это App Store, Google Play или страница на сайте.

Без них ссылка ведёт в никуда у всех, кто ещё не поставил приложение, — то есть у большей части получателей.

3. Smart Link

Дальше на вкладке Quick Links создаётся сама ссылка: заголовок, псевдоним, метки для аналитики и превью для соцсетей — картинка, заголовок, описание.

После сохранения Branch выдаст готовую Smart Link.

Настройка проекта FlutterFlow

Чтобы Smart Links заработали, нужно поправить нативные конфигурационные файлы на вкладке Custom Code.

  1. Заведите переменные окружения:

    • branchHostUrl — например, brnch4.app.link;
    • branchKey — рабочий ключ Branch. Для отладки можно завести отдельный branchKeyTest и переключаться между окружениями и в панели Branch, и во FlutterFlow.
  2. Откройте FlutterFlow > Custom Code.

Android

  1. В AndroidManifest.xml создайте переменные branchKey и branchHostUrl и привяжите их к переменным окружения.

  2. Через хук Activity Tags добавьте в Main Activity блок intent-filter:

<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <category android:name="android.intent.category.BROWSABLE"/>
  <data android:scheme="https" android:host="{{branchHostUrl}}"/>
</intent-filter>
  1. В блок App Component добавьте метаданные:
<meta-data android:name="io.branch.sdk.BranchKey" android:value="{{branchKey}}"/>
<meta-data android:name="io.branch.sdk.TestMode" android:value="false"/>

iOS

  1. В Info.plist создайте переменную branchKey и привяжите её к переменной окружения.

  2. Туда же добавьте:

<key>branch_key</key>
<string>{{branchKey}}</string>
  1. В Runner.entitlements создайте переменную branchHostUrl и привяжите её к переменной окружения.

  2. Туда же добавьте:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:{{branchHostUrl}}</string>
</array>

Файл apple-app-site-association, нужный для Universal Links, Branch хостит сам — вручную выкладывать его на домен не надо.

Маршрутизация во FlutterFlow

FlutterFlow по умолчанию заводит собственную URI-схему вида myapp://. Даже если ссылки у вас на https://, эту схему стоит держать согласованной с настройками Branch.

  1. Откройте Settings & Integrations > App Settings > App Details.

  2. Прокрутите до раздела Routing & Deep Linking.

  3. В Custom URI Scheme укажите тот же хост, что задан в панели Branch, — например, brnch4:// или dreambrush://.

custom-uri.png

Внутренняя маршрутизация FlutterFlow опирается на эту схему, поэтому расхождение приводит к переходам не на те экраны.

Подключение Flutter Branch SDK

За связь с Branch отвечает пакет flutter_branch_sdk.

  1. Откройте Settings and Integrations > Pubspec Dependencies и добавьте зависимость:
flutter_branch_sdk: ^5.0.1

Актуальную версию смотрите на pub.dev.

  1. Создайте собственное действие, инициализирующее SDK при запуске приложения:
import 'package:flutter_branch_sdk/flutter_branch_sdk.dart';

Future initBranch() async {
  // Add your function code here!
  await FlutterBranchSdk.init();
}

Вызовите его в Final Actions файла main.dart.

  1. Создайте второе действие — слушатель переходов по ссылкам:

// Automatic FlutterFlow imports
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

import 'dart:async';
import 'package:flutter_branch_sdk/flutter_branch_sdk.dart';
import 'package:flutter/services.dart';

StreamSubscription<Map>? _branchSubscription; // подписка на события Branch
final Set<String> _handledBranchLinks = {};

Future handleBranchDeeplink(Future Function(dynamic data) onLinkOpened) async {
   // Add your function code here!

   if (_branchSubscription != null) return; // уже слушаем — второй раз не подписываемся

   _branchSubscription = FlutterBranchSdk.listSession().listen(
           (data) async {
      final clicked = data['+clicked_branch_link'] == true;
      if (!clicked) return;

      final uniqueId = data['~referring_link'] ?? data['deeplink_path'] ?? '';

      if (_handledBranchLinks.contains(uniqueId)) return;
      _handledBranchLinks.add(uniqueId);

      await onLinkOpened(Map<String, dynamic>.from(data)); // вызываем действие пользователя с данными ссылки
   },
   onError: (error) {
      if (error is PlatformException) {
         print('[Branch] PlatformException: ${error.code} - ${error.message}');
      } else {
         print('[Branch] Unknown error: $error');
      }
   },
);
}

При создании ссылки в неё можно положить произвольные пары ключ-значение — "page": "paywall", "navigation_type": "bottom_sheet" — и здесь по ним решать, куда вести пользователя.

Проверять нужно оба сценария: переход при уже установленном приложении и переход с установкой (отложенный диплинк).

совет

Полный разбор в видео:

:::

Библиотека Branch Deeplinking

Собирать интеграцию с нуля необязательно: у FlutterFlow есть бесплатная Branch Deep Linking Library в маркетплейсе.

В ней уже собраны нативная конфигурация, обработка ссылок и вспомогательные функции.

Установка

Библиотека лежит здесь. Как добавить её в аккаунт — см. добавление элемента библиотеки.

Настройка Branch

Из панели Branch понадобятся три значения:

  • Branch Key — рабочий или тестовый ключ.

  • Custom Link Domain — основной домен ссылок, например yourapp.app.link.

  • Alternate Link Domain — второй домен, ведущий на те же ссылки, например yourapp-alternate.app.link. Его стоит завести: часть платформ и мессенджеров обрабатывает ссылки по-разному, и запасной домен повышает шанс, что ссылка откроется.

Эти значения лучше держать в переменных окружения — тогда у отладки и продакшена будут разные ключи, а подставить их в настройки библиотеки можно в один клик.

Значения библиотеки

При добавлении Branch Deep Linking Library (версии 0.0.7 и выше) проект попросит четыре значения:

  • branchApiKey
  • branchLinkDomain
  • branchAlternateLinkDomain
  • isTestMode

Заполните их созданными переменными окружения.

:::info В продакшене isTestMode должен быть false.

Инициализация SDK

Откройте main.dart и добавьте собственное действие initBranch в раздел Final Actions — так SDK инициализируется при запуске приложения.

Действие handleBranchDeeplink слушает входящие ссылки и решает, куда вести пользователя. Его добавляют на входную страницу или на страницу после входа, на триггер onPageLoad, и обязательно первым в потоке действий.

Обратный вызов onLinkOpened

Когда ссылка получена, срабатывает onLinkOpened с данными ссылки — в нём и пишется логика перехода.

Параметр linkData

linkData — это Map со всеми метаданными, которые пришли со ссылкой.

Вот что приходит в приложении Dreambrush:

{
   "$og_title": "Check out my Ai Image on DreamBrush!",
   "$publicly_indexable": true,
   "imageId": "QiC94EaGNoonEKzln07A",
   "~creation_source": 4,
   "$og_description": "This image was created with DreamBrush app. You can check it out here.",
   "+click_timestamp": 1750099254,
   "$match_duration": 100000,
   "~feature": "Ai Image Creation",
   "$tags[0]": "generation",
   "+match_guaranteed": true,
   "$alias": "",
   "$canonical_identifier": "/imageDetails/QiC94EaGNoonEKzln07A",
   "+clicked_branch_link": true,
   "~id": "1461141612502859827",
   "+is_first_session": false,
   "~campaign": "Image Generation",
   "~referring_link": "https://dreambrush.app.link/DZ9liDTc6Tb",
   "~channel": "Share"
}
внимание
Состав данных

Ваш набор полей может отличаться от примера, но структура и большинство ключей будут теми же.

Ключи, которые важны на практике:

  • $canonical_identifier — маршрут, заданный при создании ссылки (например, /imageDetails/:id). Задаётся действием Generate Link; если не задать, Branch выведет его сам из содержимого ссылки.

  • ~referring_link — полный адрес ссылки, по которой перешли.

  • $og_title — заголовок в превью ссылки, задаётся действием Generate Link.

  • $og_description — описание под заголовком в превью, задаётся там же.

  • ~channel, ~feature, ~campaign и $tags[0] — метки аналитики Branch. Задаются при создании ссылки и нужны, чтобы разбирать статистику переходов по каналам и кампаниям.

  • page — не служебный ключ Branch, а общепринятое соглашение: имя экрана, который нужно открыть (paywall, productPage, onboardingStep2).

  • Любые другие пары, добавленные при создании ссылки, — productId, referrer и так далее. И ключ, и значение должны быть строками.

По этим данным строится ветвление: например, показать нижнюю панель вместо перехода на страницу.

Что обычно делают в обработчике:

  • переходят на страницу;
  • открывают нижнюю панель;
  • подгружают содержимое из Firestore по идентификатору из ссылки.

Навигация через глобальный контекст

Если главная страница рано убирается из стека навигации, обычный переход по локальному контексту перестаёт работать. Лечится это подменой локального контекста на глобальный контекст навигатора — тогда переход не зависит от того, что сейчас в дереве виджетов.

Разбор — в примере с DreamBrush.

опасно
Проверка диплинков

Проверяйте ссылки на физическом устройстве: проверка Universal Links и App Links на эмуляторах и симуляторах работает нестабильно. Для запуска на устройстве используйте Local Run.

Действие generateLink создаёт Smart Link прямо из приложения. Это нужно, когда пользователь:

  • делится содержимым — постом, товаром, картинкой;
  • приглашает других по реферальному коду;
  • отправляет ссылку, ведущую на конкретный экран.

Параметры действия:

  • canonicalIdentifier — уникальный путь к содержимому (например, /imageDetails/:id). По нему пользователь и возвращается в приложение.

  • title — заголовок ссылки, он же в превью и в аналитике.

  • description — короткое описание, необязательно.

  • metadata — произвольные пары, которые уйдут вместе со ссылкой: page: "imageDetails", imageRef: "abc123" и так далее.

  • linkProperties — настройки поведения ссылки: feature, channel, campaign, stage.

внимание
Только плоские строки

Из-за ограничения реализации metadata и linkProperties нельзя оставлять пустыми как null — передавайте пустые Map. Все ключи и значения должны быть простыми строками: вложенный JSON и нестроковые типы приводят к тому, что создание ссылки молча срывается.

Вспомогательные функции

Эти функции достают значения из данных ссылки и помогают ветвить навигацию.

  • isTargetingPage(linkData, targetPage) — проверяет, совпадает ли значение page из ссылки с именем экрана. Само значение задаётся при создании ссылки — в панели Branch или во FlutterFlow.

  • getCanonicalIdentifierFromLink(linkData) — возвращает маршрут, привязанный к ссылке (например, /imageDetails/abc123).

  • getReferringLinkFromLink(linkData) — возвращает полный адрес ссылки из ключа ~referring_link. Нужен для аналитики и проверки источника перехода.

  • getLastPathSegmentFromMap(linkData, key) — вытаскивает последний сегмент пути: из /imageDetails/abc123 вернёт abc123.

  • getLinkValue(linkData, key) — безопасно достаёт любое значение по ключу; если ключа нет, вернёт null.

внимание

Служебные ключи Branch пишутся вместе со спецсимволом: ~channel, $canonical_identifier.

  • createLinkProperties(...) — собирает Map настроек для создания ссылки: feature, campaign, stage, channel, псевдоним, метки, свои запасные адреса.

Пример: DreamBrush

В приложении DreamBrush generateLink вызывается после того, как пользователь сгенерировал изображение. В ссылку кладутся:

  • canonicalIdentifier — маршрут текущей страницы, /imageDetails/:imageRef;
  • page — имя целевого экрана, imageDetails;
  • title — «Check out my AI image!».

Такую ссылку отправляют в мессенджер или соцсеть, и получатель попадает сразу к этому изображению внутри приложения.

Создание ссылки со страницы, у которой в маршруте лежит идентификатор документа Firebase:

В обработчике handleBranchDeeplink дописывается разбор такой ссылки:

Чтобы переход шёл через глобальный контекст, добавьте перед действием Navigate To действие Execute Custom Code с таким кодом:

final context = appNavigatorKey.currentContext!;

Тогда переход не зависит от того, есть ли ещё главная страница в стеке.

внимание
Платные тарифы

Действие Execute Custom Code доступно только на платных тарифах.

Частые вопросы

Почему диплинк перестаёт работать после перехода с главной страницы?

Потому что главная страница вылетает из стека навигации — так происходит, когда у действия Navigate To выключено Allow Navigate Back.

Обработчик диплинков обычно объявлен как раз на главной. Вместе со страницей он уничтожается, и ссылки, пришедшие позже, обрабатывать уже некому.

Правильное решение — глобальный контекст. Не завязывайте переход на то, что главная страница жива: добавьте действие Execute Custom Code перед Navigate To и подмените контекст на глобальный. См. полный пример.

Обходной путь — оставить главную в стеке. Включите Allow Navigate Back у всех переходов с главной, даже если возврат вам не нужен: страница останется жива и продолжит слушать ссылки. Решение рабочее, но менее надёжное.

Почему не создаётся ссылка Branch?

Чаще всего — из-за неправильного формата metadata, linkProperties или customParams в createLinkProperties.

Branch ждёт Map из простых строк: без вложенного JSON, объектов и динамических типов.

Убедитесь, что и ключ, и значение объявлены как String.

Почему диплинки не работают на симуляторе?

Из-за ограничений самих платформ:

  • iOS — симулятор не может полноценно проверить Universal Links: нет App Store, ограничена поддержка AASA.

  • Android — часть версий не проверяет App Links автоматически и не обрабатывает отложенные диплинки без сервисов Google.

Проверять диплинки нужно на физическом устройстве.

перевод официальной документации FlutterFlow

обновлено

ESC