Deep Linking : Lancer l'application depuis l'extérieur
Introduction
Le deep linking permet d'ouvrir l'application directement sur un écran spécifique via :
- Un lien externe (URL partagée)
- Une notification push
- Un email ou SMS
- Un raccourci système
- Une autre application
Cas d'usage typiques :
- Notification → Détails du produit
- Email → Page de réinitialisation de mot de passe
- Lien partagé → Profil utilisateur
- QR Code → Écran de paiement
Configuration du Deep Linking
Android (AndroidManifest.xml)
Ajouter des intent-filter dans android/app/src/main/AndroidManifest.xml :
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:exported="true">
<!-- Deep link HTTPS (Universal Links) -->
<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="https"
android:host="myapp.com"
android:pathPrefix="/product" />
</intent-filter>
<!-- Deep link avec schéma custom -->
<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>
</activity>
Explications :
android:scheme="https": Liens web standards (https://myapp.com/product/123)android:scheme="myapp": Schéma custom (myapp://product/123)android:host: Domaine de votre siteandroid:pathPrefix: Chemin spécifique (optionnel)
iOS (Info.plist)
Ajouter dans ios/Runner/Info.plist :
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.myapp.deeplink</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Pour les Universal Links (HTTPS) sur iOS : Ajouter également :
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.com</string>
</array>
Gérer les Deep Links dans Flutter
Parsing de l'URL avec onGenerateRoute
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
// Parser l'URL du deep link
final uri = Uri.parse(settings.name ?? '/');
// Deep link custom : myapp://product/123
if (uri.scheme == 'myapp') {
if (uri.host == 'product' && uri.pathSegments.isNotEmpty) {
final productId = int.tryParse(uri.pathSegments[0]);
if (productId != null) {
return MaterialPageRoute(
builder: (_) => ProductDetailScreen(productId: productId),
);
}
}
}
// Deep link HTTPS : https://myapp.com/product/123
if (uri.scheme == 'https' && uri.host == 'myapp.com') {
if (uri.path.startsWith('/product') && uri.pathSegments.length > 1) {
final productId = int.tryParse(uri.pathSegments[1]);
if (productId != null) {
return MaterialPageRoute(
builder: (_) => ProductDetailScreen(productId: productId),
);
}
}
}
// Route par défaut
return MaterialPageRoute(builder: (_) => const HomeScreen());
},
);
}
}
class ProductDetailScreen extends StatelessWidget {
final int productId;
const ProductDetailScreen({super.key, required this.productId});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Produit')),
body: Center(
child: Text('Produit ID: $productId'),
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Accueil')),
body: const Center(child: Text('Bienvenue')),
);
}
}
Gestion des paramètres de query
Les deep links peuvent contenir des paramètres de query (ex: ?query=flutter&category=tutorial).
Exemple : myapp://search?query=flutter&category=tutorial
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
final uri = Uri.parse(settings.name ?? '/');
// Deep link avec query parameters
if (uri.host == 'search') {
final query = uri.queryParameters['query'];
final category = uri.queryParameters['category'];
return MaterialPageRoute(
builder: (_) => SearchScreen(
query: query ?? '',
category: category,
),
);
}
return MaterialPageRoute(builder: (_) => const HomeScreen());
},
);
}
}
class SearchScreen extends StatelessWidget {
final String query;
final String? category;
const SearchScreen({
super.key,
required this.query,
this.category,
});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Recherche')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Recherche: $query'),
if (category != null)
Text('Catégorie: $category'),
],
),
),
);
}
}
Exemples d'intégration
Exemple 1 : Deep Link depuis une notification
Utiliser Firebase Cloud Messaging (FCM) pour envoyer des notifications avec deep links.
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
class NotificationService {
static Future<void> initialize() async {
// Écouter quand l'utilisateur clique sur une notification
FirebaseMessaging.instance.onMessageOpenedApp.listen((message) {
final deepLink = message.data['deepLink'];
if (deepLink != null) {
// Naviguer via le deep link
navigatorKey.currentState?.pushNamed(deepLink);
}
});
}
}
// Clé globale pour accéder au Navigator depuis n'importe où
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
// Logique de routing...
return MaterialPageRoute(builder: (_) => const HomeScreen());
},
);
}
}
Exemple 2 : Partager un deep link
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
// Partager un produit via schéma custom
void shareProduct(int productId) {
final deepLink = 'myapp://product/$productId';
Share.share('Regarde ce produit: $deepLink');
}
// Partager via HTTPS (Universal Link)
void shareProductViaWeb(int productId) {
final deepLink = 'https://myapp.com/product/$productId';
Share.share('Regarde ce produit: $deepLink');
}
// Envoyer par SMS
void shareProductViaSms(int productId) async {
final deepLink = 'https://myapp.com/product/$productId';
final uri = Uri(
scheme: 'sms',
path: '',
queryParameters: {'body': 'Clique ici: $deepLink'},
);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}
Validation et sécurité
Valider les deep links reçus
class DeepLinkValidator {
static Future<bool> handleDeepLink(String deepLink) async {
try {
final uri = Uri.parse(deepLink);
// Valider le schéma
if (uri.scheme != 'myapp' && uri.scheme != 'https') {
throw FormatException('Schéma non supporté: ${uri.scheme}');
}
// Valider le host (pour HTTPS)
if (uri.scheme == 'https' && uri.host != 'myapp.com') {
throw FormatException('Host non autorisé: ${uri.host}');
}
// Valider que le host n'est pas vide
if (uri.host.isEmpty) {
throw FormatException('Host vide dans le deep link');
}
// Naviguer si valide
navigatorKey.currentState?.pushNamed(deepLink);
return true;
} catch (e) {
debugPrint('Erreur deep link: $e');
return false;
}
}
}
Tester les Deep Links
Android (via ADB)
# Tester un deep link custom
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123" com.example.myapp
# Tester un deep link HTTPS
adb shell am start -W -a android.intent.action.VIEW -d "https://myapp.com/product/123" com.example.myapp
iOS (via Terminal)
# Ouvrir l'app avec un deep link
xcrun simctl openurl booted "myapp://product/123"
# Ou via HTTPS
xcrun simctl openurl booted "https://myapp.com/product/123"
Dans l'app (pour debug)
class DeepLinkTestScreen extends StatelessWidget {
const DeepLinkTestScreen({super.key});
void _testDeepLink(BuildContext context, String deepLink) {
Navigator.pushNamed(context, deepLink);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Test Deep Links')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _testDeepLink(context, 'myapp://product/123'),
child: const Text('Tester myapp://product/123'),
),
ElevatedButton(
onPressed: () => _testDeepLink(context, 'myapp://search?query=flutter'),
child: const Text('Tester recherche'),
),
],
),
),
);
}
}
Bonnes pratiques
- Valider toujours : Vérifier le schéma, host et paramètres avant de naviguer
- Schémas uniques : Utiliser un schéma unique pour éviter les conflits (ex:
com.mycompany.myapp://) - Fallback : Prévoir une page par défaut si l'URL est invalide
- Sécurité : Ne jamais faire confiance aux données reçues via deep link
- Universal Links : Préférer HTTPS (Universal Links) pour une meilleure expérience utilisateur
- Documentation : Documenter tous les deep links supportés par l'app
- Analytics : Tracker les deep links pour mesurer leur utilisation
- Tester régulièrement : Vérifier que tous les deep links fonctionnent après chaque build