Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead агентства продуктовой разработки Amiga и соавтор телеграм-канала Flutter. Много. Мы нашли интересные статьи о deep links (часть 1, часть 2) и хотим поделиться с вами переводами.

Чему вы научитесь к концу этого гайда:

  • Нажатие на ссылку похожую на https://your-domain.com/details будет открывать определенный экран в вашем приложении.

  • Если вы используете кастомную схему, например myscheme123://details, то этот гайд не для вас. Тут лучше рассмотреть пакет app_links.

Часть 1. Android

Что предстоит сделать:

  • Инициализировать навигацию через роуты в Flutter проекте.

  • Настроить AndroidManifest.xml.

  • Разместить файл assetlinks.json на web-сервере (для http/https схем).

Что нужно знать:

  • Существует 2 способа настройки deep links или app links в Flutter: go_router или app_links (или uni_links), у каждого из них собственная специфическая настройка (смотреть далее) и поведение.

  • Отпечатки (fingerprints) в файле assetlinks.json имеют значение — это будет определять откроется ли ссылка в приложении автоматически.

go_router или app_links ? или оба?

Зависит от определенного случая. Например, go_router легко настраивается, но в нем есть огромное ограничение, которое чаще всего является критичным. Когда используется go_router, то instance GoRouter настраивается как обычно с одной дополненной настройкой в AndroidManifest.xml, после чего настройка deep links по части Flutter завершена. 

Базовое поведение такой навигации аналогично context.go(<path>), что означает, что потеряется весь стек навигации при переходе на <path>. Чтобы понять разницу между go и push, прочитайте эту статью. Но к ней будет одна ремарка: push до сих пор полезен для mobile-only приложений и для некоторых случаев типа диалоговых окон.

Рассмотрим детально настройку через app_links. Автор избегает uni_links и Firebase Dynamic Links, т.к. uni_links не обновлялись уже около двух лет, а Firebase Dynamic Links официально устарели. Вот настройки:

// At a top-level widget
class App extends StatefulWidget {
    const App({super.key});

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

class _AppState extends State<App> {
    late final AppLinks _appLinks;
    StreamSubscription<String>? _linkSubscription;

    @override
    void initState() {
        _initDeepLinks();
        super.initState();
    }

    @override
    void dispose() {
        _linkSubscription?.cancel();
        super.dispose();
    }

    Future<void> _initDeepLinks() async {
        _appLinks = AppLinks();
        _linkSubscription = _appLinks.allStringLinkStream.listen((url) {
            router.push(url); // with your GoRouter instance
        });
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: router,
            builder: ...        
        );
    }
}

В своих проектах автор обычно использует обе библиотеки: go_router и app_links, потому что декларативный подход к API навигации в go_router очень удобен, а app_links нужны для достижения работоспособности context.push(<my_deeplink_path>). Было бы здорово, чтобы go_router позволил кастомизировать это поведение, чтобы избавиться от зависимости от app_links.

Настройка AndroidManifest.xml

Это ключевой момент в настройке deep links на Android. Вот что говорит официальное руководство Flutter:

 <meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
 <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="http" android:host="example.com" />
     <data android:scheme="https" />
 </intent-filter>

Необходимо поместить это код под главную <Activity> (в Flutter приложении должна быть всего одна Activity), заменить значение android:host на нужно, и все готово. 

Обратите внимание, что если использовать app_links для навигации, нужно убрать строку с flutter_deeplinking_enabled. Параметр autoVerify=true нужен для того, чтобу удостовериться, что Android будет автоматически проверять приложение с указанным доменом, чтобы установить связь между приложением и хостом. Конечно, пользователи смогут вручную это изменить. Разработчик должен проверить это поведение на устройстве Android или эмуляторе позже, когда приложение будет установлено после завершения всех настроек. Как это сделать: зайти в приложение, перейти в «О приложении», потом в «Открывать по умолчанию»:

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

Примечание: Стандартное значение параметра android:launchMode для Flutter Activity — singleTop. Как показано здесь, открытие app link может закончиться созданием дополнительной копии приложения, что неоптимально. Изменение launch mode на singleInstance будет лучше, плюс так можно полностью избежать task hijacking, если дополнительно установить android:taskAffinity="" в тег <application>.

Создание assetlinks.json и размещение на web-сервере

Шаги описаны здесь, но добавим несколько важных вещей:

  • SHA256 fingerprint из Google Play Developer Console хороши для релизных сборок из Google Play, т.к. отпечатки генерируются из keystore, используемого для подписи APK. Чаще всего, используется keystore от Google Play или загрузили свой собственный.

Можно использовать один файл assetlinks.json для нескольких app bundles, например, если применять несколько app flavors, то можно сделать так:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp.dev",
      "sha256_cert_fingerprints": [
        "<fingerprint_one>",
        "<fingerprint_two>"
      ]
    }
  },
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp.qa",
      "sha256_cert_fingerprints": [
        "<fingerprint_one>",
        "<fingerprint_two>"
      ]
    }
  },
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": [
        "<fingerprint_one>",
        "<fingerprint_two>"
      ]
    }
  }
]
  • Необходимо дать немного времени после переустановки приложения, чтобы Google мог установить соединение. Google предлагает подождать минимум 20 секунд для того, чтобы асинхронная верификация завершилась.

  • Лайфхак для тех, у кого нет web-сервера: можно использовать Firebase Hosting и загрузить assetlinks.json как статичный файл. Необходимо удостовериться, что конечный путь будет .well-known/assetlinks.json и доменное имя такое, которое было создано в Firebase Hosting.

Тестирование при помощи команд ADB

Теперь, если все правильно сделано, можно напечатать URL в отдельном приложении. Например, в Заметках, или отправить сообщением самому себе, или добавить в Google Doc и затем нажать на него. 

Примечание: ввести или вставить URL в браузер не работает! Получится автоматическая навигация на определенный экран вашего приложения. Но если что-то не так, можно отладить и проверить это при помощи команд adb:

  • adb shell 'am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://dev.example.com/product-details"' com.example.myapp.dev для проверки открывается ли необходимый экран в приложении (будет работать и без установления соединения с хостом)

  • adb shell dumpsys package com.example.myapp.dev для проверки всего, что связано с приложением, включая установку соединения с хостом и автоматическую верификацию.

Часть 2. iOS

Что предстоит сделать:

  • Отредактировать Info.plist 

  • Добавить capability «Assosiated domains» для каждого bundle ID

  • Разместить файл apple-app-site-association на web-сервере

  • Протестировать на симуляторе или реальном устройстве, или при помощи Xcode CLI

Отредактировать Info.plist

У настройки под iOS меньше странностей и в официальном руководстве можно найти практически все, что нужно. Но при обновлении Info.plist стоит учесть, что с использованием библиотеки app_links, не нужно добавлять параметр FlutterDeepLinkingEnabled.

Добавить capability «Assosiated domains» для каждого bundle ID

Советуем использовать официальное руководство. Помимо настройки associated domain(s) в Xcode также следует запомнить, что нужно добавить тоже самое на портале Apple Developer (Certificates, Identifiers & Profiles > Identifiers > Select an identifier > Capabilities).

Разместить файл apple-app-site-association на web-сервере

Также как и с assetlinks.json, необходимо разместить эквивалент от Apple на web-сервере. Но в отличии от Android, iOS universal links должны быть в https, поэтому некоторые серверы могут быть не доступны.

Папка для расположения осталась той же (.well-known), изменилось только название файла: <webdomain>/.well-known/apple-app-site-association . Тут действует тот же лайфхак с Firebase Hosting, если нет собственного web-сервера и обновить доменное имя соответственно.

Также можно добавить поддержку одного доменного имени с несколькими app ID. Например, если используются различные flavors:

{
  "applinks": {
      "apps": [],
      "details": [
      {
        "appID": "<team_id>.com.example.myapp.dev",
        "paths": ["*"]
      },
      {
        "appID": "<team_id>.com.example.myapp.qa",
        "paths": ["*"]
      },
      {
        "appID": "<team_id>.com.example.myapp",
        "paths": ["*"]
      }
    ]
  }
}

Если нужно открывать только определенные пути в приложении, то можно настроить ассоциацию с доменом для сравнения только определенного web-пути. Также поддерживается продвинутое сопоставление, как показано здесь.

Проверка работоспособности

Аналогично Android, необходимо удостовериться, что переустановка приложения прошла успешно после выполнения прошлых шагов. Теперь можно набрать URL в другом приложении, это должно перенести пользователя в приложение и открыть необходимый экран.

Или другой способ — вызвать команду: xcrun simctl openurl booted https://<web domain>/details. В официальном руководстве есть важное замечание, что переустановка приложения может занять до суток, чтобы CDN Apple запросил файл apple-app-site-association (AASA) с домена, так что app link не будут работать, пока CDN не запросит файл.

Несколько советов по устранению неполадок:

  1. При тестировании следует проверить заканчивается ли ссылка на . Если это так, то нужно использовать приложения, такие как это и это для запуска ссылок. Многие редакторы текста не включают . при нажатии и открытии ссылки.

  2. Если нужно проверить deeplinks на сервере, к которому нет доступа у Apple CDN, можно использовать Associated Domains Development, чтобы устройство получило JSON-файл напрямую. Видео с обсуждением этого от Apple, начало на 18:22.

    a) Включить Developer Mode (Settings > Privacy & Security > Developer Mode)
    b) Включить Associated Domains Development (Settings > Developer > Section: Universal Links > Associated Domains Development) и добавить внутреннюю ссылку из Diagnostics для получения файла apple-app-site-association.

  3. Руководство по устранению неполадок в deeplinks

  4. Проверка, правильно ли размещен файл в Apple CDN, осуществляется при помощи запуска этой ссылки, заменив <your website link> собственной ссылкой: https://app-site-association.cdn-apple.com/a/v1/<your website link>.

  5. Если rootViewController — это не FlutterViewController (если это переписано в файле AppDelegate.swift), то нужно вручную добавить код для поимки запроса со стороны нативного iOS.

Вот и все про deeplinks. Если было полезно, оставьте комментарий, это мотивирует нас писать больше!

Подписывайтесь на наш авторский телеграм-канал Flutter.Много, чтобы всегда все новости узнавать первыми! 

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


  1. rimidalvv
    21.08.2024 21:16

    Рассказывать про flutter и не затронуть web. Надеюсь в продолжении это будет поправлено.