Flutter'da Deep Linking: Dynamic Links, URL Yönlendirme ve App Links

Deep linking, kullanıcıların bir bağlantıya tıkladığında doğrudan uygulamanın belirli bir ekranına yönlendirilmesini sağlar. Üç farklı yaklaşım vardır: Custom URL Scheme (myapp://product/123), Android App Links / iOS Universal Links (https://example.com/product/123) ve Firebase Dynamic Links (uygulama yoksa mağazaya yönlendirir).

GoRouter ile URL Yapısı Tasarımı

// pubspec: go_router: ^14.2.7

// Tüm uygulama rotaları tek bir yerde tanımlanır
final router = GoRouter(
  initialLocation: '/',
  debugLogDiagnostics: kDebugMode,
  redirect: _guardRoute,
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
    GoRoute(
      path: '/products',
      builder: (_, __) => const ProductsScreen(),
      routes: [
        GoRoute(
          path: ':id',
          builder: (_, state) => ProductDetailScreen(
            productId: state.pathParameters['id']!,
          ),
          routes: [
            GoRoute(
              path: 'reviews',
              builder: (_, state) => ReviewsScreen(
                productId: state.pathParameters['id']!,
              ),
            ),
          ],
        ),
      ],
    ),
    GoRoute(
      path: '/orders/:orderId',
      builder: (_, state) {
        final orderId = state.pathParameters['orderId']!;
        return OrderDetailScreen(orderId: orderId);
      },
    ),
    GoRoute(
      path: '/invite',
      builder: (_, state) {
        final code = state.uri.queryParameters['code'];
        final ref = state.uri.queryParameters['ref'];
        return InviteScreen(code: code, referrer: ref);
      },
    ),
    ShellRoute(
      builder: (_, __, child) => MainShell(child: child),
      routes: [
        GoRoute(path: '/home', builder: (_, __) => const HomeTab()),
        GoRoute(path: '/search', builder: (_, __) => const SearchTab()),
        GoRoute(path: '/profile', builder: (_, __) => const ProfileTab()),
      ],
    ),
  ],
);

// Auth guard
String? _guardRoute(BuildContext context, GoRouterState state) {
  final isLoggedIn = context.read<AuthCubit>().state.isAuthenticated;
  final isPublicRoute = ['/login', '/register', '/invite']
      .any((r) => state.uri.path.startsWith(r));

  if (!isLoggedIn && !isPublicRoute) return '/login';
  if (isLoggedIn && state.uri.path == '/login') return '/home';
  return null;
}

Android App Links Yapılandırması

<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity" ...>
  <!-- Custom URL Scheme -->
  <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) -->
  <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>

<!-- Web sunucusunda: https://example.com/.well-known/assetlinks.json -->
<!-- [{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.app",
    "sha256_cert_fingerprints": ["AA:BB:CC:..."]
  }
}] -->

iOS Universal Links Yapılandırması

<!-- ios/Runner/Runner.entitlements -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC ...>
<plist version="1.0">
  <dict>
    <key>com.apple.developer.associated-domains</key>
    <array>
      <string>applinks:example.com</string>
      <string>webcredentials:example.com</string>
    </array>
  </dict>
</plist>

<!-- Web sunucusunda: https://example.com/.well-known/apple-app-site-association -->
<!-- {
  "applinks": {
    "apps": [],
    "details": [{
      "appID": "TEAMID.com.example.app",
      "paths": ["/products/*", "/orders/*", "/invite"]
    }]
  }
} -->

App Lifecycle'da Link Yakalama

import 'package:app_links/app_links.dart';

class DeepLinkService {
  static final _appLinks = AppLinks();
  static StreamSubscription? _sub;

  static Future<void> initialize(GoRouter router) async {
    // Uygulama kapalıyken gelen ilk link
    final initial = await _appLinks.getInitialLink();
    if (initial != null) _handleUri(initial, router);

    // Uygulama açıkken gelen linkler
    _sub = _appLinks.uriLinkStream.listen(
      (uri) => _handleUri(uri, router),
      onError: (e) => debugPrint('Deep link hatası: $e'),
    );
  }

  static void _handleUri(Uri uri, GoRouter router) {
    debugPrint('Deep link: $uri');

    // Custom scheme: myapp://products/123
    if (uri.scheme == 'myapp') {
      router.go('/${uri.host}${uri.path}');
      return;
    }

    // Universal link: https://example.com/products/123
    if (uri.host == 'example.com') {
      router.go(uri.path + (uri.query.isNotEmpty ? '?${uri.query}' : ''));
      return;
    }
  }

  static void dispose() => _sub?.cancel();
}

// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // ...
  runApp(ProviderScope(
    child: Consumer(builder: (_, ref, __) {
      final router = ref.watch(routerProvider);
      // Router hazır olduğunda deep link servisini başlat
      WidgetsBinding.instance.addPostFrameCallback((_) {
        DeepLinkService.initialize(router);
      });
      return MaterialApp.router(routerConfig: router);
    }),
  ));
}

Paylaşım Entegrasyonu: Dinamik Link Oluşturma

class ShareService {
  // Ürün sayfasını paylaş — doğrudan URL
  static Future<void> shareProduct(Product product) async {
    final url = 'https://example.com/products/${product.id}';
    final text = '${product.title} - ₺${product.price}\n$url';

    await Share.share(
      text,
      subject: product.title,
    );
  }

  // Davet linki paylaş
  static Future<void> shareInvite(String userId) async {
    final url = 'https://example.com/invite?ref=$userId&utm_source=share';
    await Share.share(
      'Bu uygulamayı mutlaka dene! 🎉\n$url',
      subject: 'Seni davet ediyorum',
    );
  }
}

// Sosyal medya meta tag'leri için sunucu tarafında OG tags ekleyin:
// <meta property="og:title" content="Ürün Adı" />
// <meta property="og:image" content="https://..." />
// <meta property="og:url" content="https://example.com/products/123" />

Deep linking kurulumu biraz karmaşıktır ama kullanıcı deneyimi açısından büyük değer taşır. GoRouter, URL tabanlı navigasyonu Dart koduyla tutarlı hale getirir. Android App Links ve iOS Universal Links için web sunucunuzdaki doğrulama dosyalarını (.well-known) doğru yapılandırın; aksi takdirde linkler uygulamayı açmaz.