В мире Android и iOS разработки есть механизм диплинков. Диплинк представляет из себя обычную ссылку, при переходе на которую у вас открывается приложение (если таковое имеется) и зачастую показывается определенный контент. В диплинки мы можем добавлять различные параметры, поскольку это просто ссылка (например, id видео, код подтверждения и т.д.). В этой статье мы разберемся, как заимплементить диплинки для нашего Флатер приложения для двух платформ: Android и iOS.

Где применяются диплинки?

  • Старт приложения на определенном контенте - ссылка на ютуб видео стартует приложение и открывает определенное видео. Стоит упомянуть, что при уже запущенном приложении, новое не будет стартовать, а видео откроется в текущей сессии.

  • Восстановление пароля - стартует приложение с окном на уровне "Введите новый пароль"

  • Любой другой юзкейс, когда надо стартовать приложение (или во время текущей сессии) и добавить какое-то поведение на основе параметров в диплинке

  • Навигация по приложению (в качестве основного способа навигации такой вариант не совсем рациональный)

Во флатере нет единого решения для добавления диплинков, поскольку это платформозависимый механизм и необходимо это реализовывать отдельно для двух платформ. Далее последовательно рассмотрим перехват нашим приложением диплинков на Android и iOS системах и их обработку в флатер приложении.

Android

В Android есть 2 типа ссылок, открывающих приложение - Deep Links и App Links.
Внешние отличия: Deep Links могут иметь кастомную схему либо схему http, а App Links имеют исключительно схему https:

  • http://habr.com и awesome-scheme://habr.com - Deep Links

  • https://habr.com - App Link

Также важно уточнить, что при открытии App Link нас сразу перекинет в приложение, а при открытии же Deep Link появится похожее окошко с выбором приложения, в котором эту ссылку и открыть:

Внутренние отличия:

  • Реализация App Links требует наличие google-developer аккаунта и сайта со схемой https вместе с размещением на нем файла-конфигурации assetlinks.json

  • При отсутствии приложения, обрабатывающего такую ссылку, в случае Deep Link откроется ссылка в браузере по этому пути, в случае App Link - откроется ссылка на это приложение в Play Market'е.

Android Deep Links

В файле android/app/src/main/AndroidManifest.xml внутри <activity>...</activity> необходимо добавить следующий intent-filter:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
        android:scheme="my-scheme"
        android:host="my-host.com"
        android:pathPrefix="/my-prefix" />
</intent-filter>

android:scheme - указывается кастомная схема либо http
android:host - указывается имя хоста
android:pathPrefix - опциональный параметр
Такой интент фильтр будет открывать наше приложение для ссылок вида my-scheme://my-host/my-prefix и my-scheme://my-host/my-prefix*

Чтобы протестировать, что все работает верно, можно ввести в консоль следующую команду:


adb shell am start -a android.intent.action.VIEW -d "my-scheme://my-host.com/my-prefix" your-app-package-name

Android App Links

Для начала необходимо подписать приложение и зарегистрировать его в Google Play Console (необходимо иметь платный google-developer аккаунт). Откуда в project-name->setup->app-integrity можно найти необходмый sha-ключ для конфиг-файла.
Сам конфиг файл выглядит следующим образом, только вместо com.example необходимо вставить свой package-name и вместо 14:6D* необходимо вставить свой sha-ключ:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

Потом необходимо добавить файл-конфигурацию на наш сайт с https-схемой. Если сайта нет, можно создать свой посредством сервисов firebase-deploy, heroku или github-pages.
Создаем новую папку .well-known в корне нашего сайта и добавляем туда файл assetlinks.json, чтобы перейдя по ссылке https://my-host.com/.well-known/assetlinks.json можно было посмотреть содержимое этого файла.

В файле android/app/src/main/AndroidManifest.xml внутри <activity>...</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" />
    <data android:host="my-host.com" />
</intent-filter>

android:scheme - указывается https
android:host - указывается имя существующего хоста
android:pathPrefix - опциональный параметр, как в Deep Links

Чтобы протестировать, что все работает верно, можно ввести в консоль следующую команду:


adb shell am start -a android.intent.action.VIEW -d "https://my-host.com" your-app-package-name

iOS

В iOS также есть 2 типа ссылок: Custom Scheme Links и Uni Links.
Они очень схожи с Deep Links и App Links в андроиде, но Custom Scheme Links могут иметь только кастомные схемы (как неудивительно), а Uni Links только схемы http и https.

  • http://habr.com и https://habr.com - Uni Links

  • awesome-scheme://habr.com - Custom Scheme Link

Внутренние отличия такие же, как для андроидовских линков: необходимо наличие apple-developer аккаунта и сайта с https схемой вместе с конфиг файлом apple-app-site-association для реализации Uni Links + при отсутствии приложения на девайсе ссылка откроется в браузере для Custom Scheme Links и откроется ссылка на приложение в AppStore для Uni Links.

iOS Custom Scheme Links

Для добавления ссылок с кастомными схемами нам достаточно зайти в Xcode и добавить список схем(не http и https), которое приложение может отлавливать:

Чтобы протестировать, что все работает верно, можно ввести в консоль следующую команду:

xcrun simctl openurl booted awesome-scheme://any-text

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

iOS Uni Links

Необходим apple-developer аккаунт.

Для начала необходимо добавить соответствующее разрешение в консоли разработчика - Identifiers-> Associated Domains - enabled.

Затем в Xcode добавить Capabilities->Associated Domains и новый домен, имеющий тип applinks и содержащий имя хоста. После добавления должны сгенерироваться файлы Runner.entitlements, RunnerDebug.entitlements, RunnerRelease.entitlements.
Остался последний шаг - добавить конфиг файл apple-app-site-association на наш сайт в папку .well-known, как и в случае с assetlinks.json.
Сам конфиг-файл имеет следующий вид:

{ 
    "applinks": { 
        "apps": [],
        "details": [
            { 
                "appID": "<TeamId>.<BundleId>",
                "paths": [ "*" ]
            }
        ]
    }
}

Чтобы протестировать, что все работает верно, можно ввести в консоль следующую команду:

xcrun simctl openurl booted https://my-host.com

Обработка диплинков в приложении

Для обработки будем использовать плагин uni_links.

Диплинки мы можем получить в двух состояниях: когда приложение активно (Incoming deep links) и когда приложение убито (Initial deep links).

uni_links предоставляет нам два основных компонента - linkStream, по которому мы будем получать диплинки в уже стартовавшем приложении, и метод getInitialLink, через который мы получаем диплинк, стартовавший приложение.

Весь код обработки таких случаев:

class App extends StatefulWidget {
  const App({Key? key}) : super(key: key);

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  @override
  void initState() {
    super.initState();
    handleIncomingDeepLinks();
    handleInitialDeepLink();
  }

  void handleIncomingDeepLinks() {
    linkStream.listen((link) {
      print(link);
    });
  }

  Future<void> handleInitialDeepLink() async {
    final link = await getInitialLink();
    if (link != null) {
      print(link);
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold();
  }
}

Заключение

С большим удовольствием отвечу на появившиеся вопросы. Это моя первая статья на Хабре, и любая обратная связь будет очень полезной!

Комментарии (6)


  1. paamayim
    13.10.2022 02:45
    +1

    Отличная статья, все разжеванно. Побольше бы таких для флаттера. Как пожелание автору для дальнейших статей было бы интересны ваши подходы к навигации. С диплинками и вложенными роутерами для кроссплтаформы( тобишь веба и мобилок), если есть опыт конечно. Все решения какие пробовал я для своих проектов либо работают с костылями(если использовать готовые пакеты), либо отнимают много времени (если писать что то кастомное на navigator 2.0)


    1. hurray
      13.10.2022 12:10

      а пробовали AutoRoute?


      1. paamayim
        14.10.2022 15:04

        Пробовал routemaster и немного поковырялся с go_router. AutoRoute мне не понравился тем что там кодогенерация. Routemaster более менее удобный, но там есть моменты которые не разруливаются без костылей и как мне кажется автор подзабил на поддержку. Из перспективных мне кажется go_router, так как поддерживается командой флаттера но там не было полноценных решений для вложенных роутеров внутри табов и снаружи. Возможно это уже допилили.


  1. Necessitudo
    13.10.2022 07:57
    +1

    А причем тут Флаттер?


    1. Andrey_chik
      13.10.2022 14:44

      Статья ж называется "Deeplinks и Flutter"


    1. paamayim
      14.10.2022 15:12

      Большая часть статьи конечно относится к платформам. Но это актуально для флаттера так как это кроссплатформа и можно ориентироваться на статью как мануал, вместо того чтобы читать доки по каждой платформе отдельно. А в конце статьи чисто флаттерский пакет описан для поддержки диплинков. Так что да, статья актуальна именно для флаттера