FlutterMiddleSystem design

Как обрабатывать deep linking в приложении Flutter?

Deep linking во Flutter: настраиваете intent filter (Android) и CFBundleURLSchemes (iOS), используете go_router с path-параметрами для маршрутизации. Пакет app_links обрабатывает cold start через getInitialLink() и warm start через uriLinkStream. Главный риск — пропустить один из двух кейсов запуска.

Что такое deep linking во Flutter

Deep link — это URL, который открывает конкретный экран приложения. Flutter поддерживает три варианта: custom scheme (myapp://product/42), universal links на iOS (https://example.com/product/42 через Associated Domains) и App Links на Android (через Digital Asset Links). С Flutter 3.x рекомендуемый подход — использовать go_router, который нативно интегрируется с механизмом deep link обеих платформ.

Базовая настройка с go_router

# pubspec.yaml
dependencies:
  go_router: ^13.0.0
// lib/router.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      // Этот путь откроется при deep link: myapp://product/42
      path: '/product/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return ProductScreen(productId: id);
      },
    ),
    GoRoute(
      path: '/promo',
      builder: (context, state) {
        // Query параметры: myapp://promo?code=SALE20
        final code = state.uri.queryParameters['code'];
        return PromoScreen(code: code);
      },
    ),
  ],
);
// lib/main.dart
import 'package:flutter/material.dart';
import 'router.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp.router(
    routerConfig: router,
    title: 'Deep Link Demo',
  );
}

Настройка платформ

Android — добавляем intent filter в AndroidManifest.xml:

<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity" ...>

  <!-- Custom scheme: myapp://product/42 -->
  <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="myapp" />
  </intent-filter>

  <!-- App Links: https://example.com/product/42 -->
  <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="example.com" />
  </intent-filter>

</activity>

iOS — добавляем custom scheme в Info.plist:

<!-- ios/Runner/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Для Universal Links нужен файл apple-app-site-association на сервере и включение Associated Domains в Xcode Capabilities.

Обработка ссылки, когда приложение уже запущено

go_router слушает incoming links автоматически через Router механизм Flutter. Если нужен кастомный перехват (например, для аналитики), используем app_links:

import 'package:app_links/app_links.dart';
import 'package:go_router/go_router.dart';

class DeepLinkService {
  final GoRouter router;
  DeepLinkService(this.router);

  late final AppLinks _appLinks;

  Future<void> init() async {
    _appLinks = AppLinks();

    // Ссылка, по которой приложение было запущено (cold start)
    final initialUri = await _appLinks.getInitialLink();
    if (initialUri != null) {
      _handleLink(initialUri);
    }

    // Ссылки, когда приложение уже работает (warm start)
    _appLinks.uriLinkStream.listen(
      _handleLink,
      onError: (err) => debugPrint('Deep link error: $err'),
    );
  }

  void _handleLink(Uri uri) {
    // Логируем для аналитики
    debugPrint('Incoming link: $uri');
    // Навигируем через go_router
    router.go(uri.path, extra: uri.queryParameters);
  }
}

Подводные камни

  • Cold start vs warm start. Ссылка при запуске (приложение не было в памяти) приходит через getInitialLink(). Ссылка при возврате из фона — через stream. Пропустить один из кейсов — частая ошибка.
  • Навигация до готовности роутера. Если router.go() вызвать до инициализации MaterialApp, получим exception. Инициализируйте DeepLinkService в initState или после первого frame через WidgetsBinding.addPostFrameCallback.
  • Аутентификация и редиректы. Приватные экраны должны проверять auth state. В go_router используйте redirect колбэк — он перехватит deep link и отправит на логин с сохранением исходного пути.
  • Universal Links на iOS требуют HTTPS. Домен должен отдавать apple-app-site-association на /.well-known/apple-app-site-association со строгим Content-Type application/json. Любая ошибка — iOS fallback на браузер.
  • App Links на Android требуют верификации. autoVerify=true запускает верификацию при установке. Если assetlinks.json недоступен или содержит ошибку, ссылки открываются в браузере, а не в приложении.
  • Back stack. При open по deep link пользователь ожидает кнопку «Назад» на главный экран. go_router не добавляет автоматически родительские маршруты — используйте ShellRoute или явно управляйте стеком через GoRouter.push вместо go.
  • Тестирование на симуляторе. На iOS Simulator команда xcrun simctl openurl booted 'myapp://product/42', на Android Emulator — adb shell am start -W -a android.intent.action.VIEW -d 'myapp://product/42'. Без тестирования обоих платформ баги в cold start незаметны.

Common mistakes

  • Сводить «обрабатывать deep linking в приложении Flutter» к синтаксису и не объяснять frame scheduling.
  • Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии flutter-28.
  • Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.

What the interviewer is testing

  • Формулирует точную модель для «обрабатывать deep linking в приложении Flutter» и подтверждает ее корректным примером.
  • Умеет связать ответ с platform channel, тестированием и отладкой на устройстве.
  • Называет ограничения подхода flutter-28, включая производительность, память и сопровождение.

Sources

Related topics