Ответ
Deep Link — это URL, который открывает конкретный экран или контент внутри приложения. В моих Flutter-проектах это ключевая функциональность для интеграции с вебом, push-уведомлениями и кросс-платформенной навигацией.
Полная реализация с go_router (рекомендуемый подход):
// pubspec.yaml зависимости:
// go_router: ^13.0.0
// uni_links: ^0.5.1
// url_launcher: ^6.0.0
import 'package:go_router/go_router.dart';
import 'package:uni_links/uni_links.dart';
// 1. Определение маршрутов
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProductScreen(productId: id);
},
),
GoRoute(
path: '/category/:name',
builder: (context, state) {
final name = state.pathParameters['name']!;
return CategoryScreen(categoryName: name);
},
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
// 2. Обработка deep links в main
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Обработка cold start (приложение закрыто)
final initialUri = await getInitialUri();
if (initialUri != null) {
handleDeepLink(initialUri);
}
// Обработка warm start (приложение открыто)
uriLinkStream.listen((Uri? uri) {
if (uri != null) handleDeepLink(uri);
});
runApp(MyApp(router: router));
}
// 3. Функция обработки ссылок
void handleDeepLink(Uri uri) {
if (uri.pathSegments.length >= 2) {
final type = uri.pathSegments[0];
final id = uri.pathSegments[1];
switch (type) {
case 'product':
router.go('/product/$id');
break;
case 'category':
router.go('/category/$id');
break;
}
}
// Обработка query параметров
final params = uri.queryParameters;
if (params.containsKey('ref')) {
analytics.trackReferral(params['ref']!);
}
}
// 4. Генерация deep links
String createProductLink(String productId) {
return 'https://myapp.com/product/$productId';
}
// 5. Открытие ссылок из приложения
void shareProduct(String productId) async {
final link = createProductLink(productId);
await launchUrl(Uri.parse(link));
}
Настройка платформ:
Android (android/app/src/main/AndroidManifest.xml):
<activity android:name=".MainActivity">
<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="myapp.com"
android:pathPrefix="/product/" />
</intent-filter>
</activity>
iOS (ios/Runner/Info.plist):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
<key>FlutterDeepLinkingEnabled</key>
<true/>
Тестирование deep links:
# Android
adb shell am start -a android.intent.action.VIEW
-d "https://myapp.com/product/123"
# iOS (симулятор)
xcrun simctl openurl booted "https://myapp.com/product/123"
# iOS (устройство)
open "myapp://product/123"
Особенности из практики:
- Universal Links (iOS) требуют серверной настройки
apple-app-site-association - App Links (Android) требуют
assetlinks.jsonна сервере - Фоновая обработка через
WidgetsBindingObserver - Аналитика — отслеживание источников переходов
- Резервный вариант — открытие в браузере с кнопкой "Открыть в приложении"
В production-приложениях всегда добавляю обработку ошибок для битых ссылок и логирование всех переходов для аналитики.
Ответ 18+ 🔞
Давай разберём эту тему про deep links, а то народ часто путается, как будто на дворе 2002-й год и мы только учимся ссылки в интернете открывать. Ёпта, это же просто ссылка, которая тыкает пользователя прямо в нужное место твоего приложения, минуя главный экран. Типа, получил пуш-уведомление — тык — и ты уже в корзине, а не смотришь на логотип.
Полная реализация с go_router (делай так и не парься):
// pubspec.yaml зависимости:
// go_router: ^13.0.0
// uni_links: ^0.5.1
// url_launcher: ^6.0.0
import 'package:go_router/go_router.dart';
import 'package:uni_links/uni_links.dart';
// 1. Раскидываем маршруты, как карты на столе
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProductScreen(productId: id);
},
),
GoRoute(
path: '/category/:name',
builder: (context, state) {
final name = state.pathParameters['name']!;
return CategoryScreen(categoryName: name);
},
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
// 2. Ловим deep links прямо в main, чтобы не проёбать
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Холодный старт (приложение было закрыто)
final initialUri = await getInitialUri();
if (initialUri != null) {
handleDeepLink(initialUri);
}
// Тёплый старт (приложение уже болталось в фоне)
uriLinkStream.listen((Uri? uri) {
if (uri != null) handleDeepLink(uri);
});
runApp(MyApp(router: router));
}
// 3. Функция, которая разбирает ссылку как Шерлок Холмс
void handleDeepLink(Uri uri) {
// Смотри, чтобы сегментов было достаточно, а то будет тебе **хитрая жопа**
if (uri.pathSegments.length >= 2) {
final type = uri.pathSegments[0];
final id = uri.pathSegments[1];
switch (type) {
case 'product':
router.go('/product/$id');
break;
case 'category':
router.go('/category/$id');
break;
}
}
// Выковыриваем query-параметры, если они есть
final params = uri.queryParameters;
if (params.containsKey('ref')) {
analytics.trackReferral(params['ref']!); // Отслеживаем, откуда пришёл юзер
}
}
// 4. Генерируем ссылки, чтобы ими делиться
String createProductLink(String productId) {
return 'https://myapp.com/product/$productId';
}
// 5. Открываем эти ссылки где угодно
void shareProduct(String productId) async {
final link = createProductLink(productId);
await launchUrl(Uri.parse(link));
}
Настройка платформ (тут без этого нихуя не заработает):
Android (android/app/src/main/AndroidManifest.xml):
<activity android:name=".MainActivity">
<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="myapp.com"
android:pathPrefix="/product/" />
</intent-filter>
</activity>
iOS (ios/Runner/Info.plist):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
<key>FlutterDeepLinkingEnabled</key>
<true/>
Тестирование (а то запустишь и будешь орать "не работает, ёб твою мать!"):
# Android
adb shell am start -a android.intent.action.VIEW
-d "https://myapp.com/product/123"
# iOS (симулятор)
xcrun simctl openurl booted "https://myapp.com/product/123"
# iOS (устройство)
open "myapp://product/123"
Особенности из боевого опыта, чувак:
- Universal Links на iOS — это отдельная песня. Нужен файл
apple-app-site-associationна твоём сервере, иначе будет просто открываться в Safari. Доверия ебать ноль к этой схеме, пока не проверишь десять раз. - App Links на Android — тоже требуют
assetlinks.jsonна сервере. Без этого — пидарас шерстяной, ссылка будет спрашивать "в каком приложении открыть?". - Фоновая обработка — через
WidgetsBindingObserverлови, когда приложение выходит на передний план, чтобы перепроверить ссылку. А то пользователь тыкнет, а у тебя ни хуя себе — навигация сломалась. - Аналитика — обязательно пиши, откуда пришёл пользователь. Без этого ты как слепой кот.
- Резервный вариант — если приложения нет, открывай страницу в браузере с большой-пребольшой кнопкой "Открыть в приложении". Волнение ебать у пользователя, если он не поймёт, что делать.
В продакшене всегда накидывай обработку ошибок для битых ссылок и логируй всё, что движется. Чтобы когда придёт багрепорт "не открывается", ты не сам от себя охуел, а сразу посмотрел в логи и понял, в каком месте накрылся медным тазом твой код.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶