Cours React Native · Module 3: Navigation

Deep Linking - Les portails magiques

4 min de lecture ·
Dans cette leçon8

Le Deep Linking est comme les portails magiques d'Atlantis : il permet aux utilisateurs de sauter directement vers des écrans précis de l'application depuis des liens externes.

Qu'est-ce que le Deep Linking ?

Le Deep Linking permet de :

  • Ouvrir l'application depuis des liens URL
  • Naviguer directement vers un écran précis
  • Passer des paramètres via l'URL
  • Gérer les notifications push

Configuration dans Expo

// app.json
{
  "expo": {
    "scheme": "atlantis",
    "web": {
      "bundler": "metro"
    }
  }
}

Configurer linking dans NavigationContainer

import { NavigationContainer, LinkingOptions } from '@react-navigation/native';
import * as Linking from 'expo-linking';

// Préfixe d'URL pour l'application
const prefix = Linking.createURL('/');

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Item: { itemId: number };
  Settings: undefined;
};

const linking: LinkingOptions<RootStackParamList> = {
  prefixes: [prefix, 'atlantis://', 'https://atlantis.app'],

  config: {
    screens: {
      Home: '',
      Profile: 'user/:userId',
      Item: {
        path: 'item/:itemId',
        parse: {
          itemId: (itemId: string) => parseInt(itemId, 10),
        },
      },
      Settings: 'settings',
      // Navigateurs imbriqués
      MainTabs: {
        screens: {
          Feed: 'feed',
          Messages: 'messages/:conversationId?',
        },
      },
    },
  },
};

function App() {
  return (
    <NavigationContainer
      linking={linking}
      fallback={<LoadingScreen />}
    >
      <Stack.Navigator>
        {/* Écrans */}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Formats d'URL pris en charge

atlantis://                    → Home
atlantis://user/123            → Profile { userId: '123' }
atlantis://item/42             → Item { itemId: 42 }
atlantis://settings            → Settings
atlantis://feed                → MainTabs > Feed
atlantis://messages/abc        → MainTabs > Messages { conversationId: 'abc' }

Créer des liens au sein de l'application

import * as Linking from 'expo-linking';

function ShareButton({ itemId }: { itemId: number }) {
  const handleShare = async () => {
    // Lien interne (vers l'application)
    const internalUrl = Linking.createURL(`item/${itemId}`);
    console.log(internalUrl); // exp://192.168.1.5:8081/--/item/42

    // Lien universel (web + application)
    const universalUrl = `https://atlantis.app/item/${itemId}`;

    // Partage
    await Share.share({
      message: `Regarde cet élément : ${universalUrl}`,
      url: universalUrl,
    });
  };

  return (
    <Pressable onPress={handleShare}>
      <Text>Partager</Text>
    </Pressable>
  );
}

Ouvrir des URL externes

import * as Linking from 'expo-linking';

function ExternalLinks() {
  const openWebsite = async () => {
    await Linking.openURL('https://atlantis.app');
  };

  const openSettings = async () => {
    // Ouvre les réglages système de l'application
    await Linking.openSettings();
  };

  const sendEmail = async () => {
    await Linking.openURL('mailto:contact@atlantis.app?subject=Question');
  };

  const makeCall = async () => {
    await Linking.openURL('tel:+48123456789');
  };

  const openMaps = async () => {
    // iOS
    await Linking.openURL('maps://app?daddr=52.2297,21.0122');
    // Android
    await Linking.openURL('geo:52.2297,21.0122');
  };

  return (
    <View>
      <Pressable onPress={openWebsite}><Text>Site web</Text></Pressable>
      <Pressable onPress={openSettings}><Text>Réglages</Text></Pressable>
      <Pressable onPress={sendEmail}><Text>E-mail</Text></Pressable>
      <Pressable onPress={makeCall}><Text>Appeler</Text></Pressable>
    </View>
  );
}

Écouter les liens

import * as Linking from 'expo-linking';
import { useEffect } from 'react';

function App() {
  useEffect(() => {
    // Vérifier si l'application a été ouverte depuis un lien
    const getInitialURL = async () => {
      const initialUrl = await Linking.getInitialURL();
      if (initialUrl) {
        console.log('Ouvert depuis le lien :', initialUrl);
      }
    };

    getInitialURL();

    // Écouter les nouveaux liens (quand l'application est en arrière-plan)
    const subscription = Linking.addEventListener('url', ({ url }) => {
      console.log('Lien reçu :', url);
    });

    return () => subscription.remove();
  }, []);

  return <NavigationContainer linking={linking}>{/* ... */}</NavigationContainer>;
}

Persistance de l'état (conserver l'état de navigation)

import AsyncStorage from '@react-native-async-storage/async-storage';

const NAVIGATION_STATE_KEY = 'NAVIGATION_STATE';

function App() {
  const [isReady, setIsReady] = useState(false);
  const [initialState, setInitialState] = useState();

  useEffect(() => {
    const restoreState = async () => {
      try {
        const savedState = await AsyncStorage.getItem(NAVIGATION_STATE_KEY);
        if (savedState) {
          setInitialState(JSON.parse(savedState));
        }
      } finally {
        setIsReady(true);
      }
    };

    restoreState();
  }, []);

  if (!isReady) return <LoadingScreen />;

  return (
    <NavigationContainer
      initialState={initialState}
      onStateChange={(state) => {
        AsyncStorage.setItem(NAVIGATION_STATE_KEY, JSON.stringify(state));
      }}
    >
      {/* ... */}
    </NavigationContainer>
  );
}

Le Deep Linking améliore considérablement l'UX de ton application : il permet de partager du contenu précis et de s'intégrer aux notifications push.

Code de cette leçon : App.tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import * as Linking from 'expo-linking';

const Stack = createNativeStackNavigator();

// Préfixe d'URL de l'application, ex. atlantis://item/42
const prefix = Linking.createURL('/');

const linking = {
  prefixes: [prefix],
  // TODO : complète la config qui associe les chemins aux écrans :
  // config: {
  //   screens: {
  //     Home: '',
  //     Details: 'item/:itemId',
  //   },
  // },
};

function HomeScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>Portail de l'Atlantide</Text>
    </View>
  );
}

function DetailsScreen({ route }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>Objet #{route.params?.itemId}</Text>
    </View>
  );
}

export default function App() {
  return (
    // TODO : transmets la prop linking={linking} à <NavigationContainer>
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#1a5276',
  },
  title: {
    fontSize: 22,
    color: '#fff',
    fontWeight: '600',
  },
});

Entrées de la documentation utilisées dans cette leçon

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que définit la propriété « scheme » dans app.json ?

  2. 2. Quelle méthode ouvre une URL externe ou une application ?

Tâches pratiques dans le jeu

  • Tri horizontal

    Range la création d'une URL à partager :

  • Éditeur de code

    Complète la configuration linking.

Articles utiles