Kurs React Native · Moduł 3: Nawigacja

Deep Linking - Magiczne Portale

3 min czytania ·
W tej lekcji8

Deep Linking to jak magiczne portale w Atlantydzie - pozwalają użytkownikom przeskakiwać bezpośrednio do określonych ekranów aplikacji z linków zewnętrznych.

Czym jest Deep Linking?

Deep Linking umożliwia:

  • Otwieranie aplikacji z linków URL
  • Nawigację bezpośrednio do konkretnego ekranu
  • Przekazywanie parametrów przez URL
  • Obsługę powiadomień push

Konfiguracja w Expo

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

Konfiguracja linking w NavigationContainer

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

// Prefiks URL dla aplikacji
const prefix = Linking.createURL('/');

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

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

  config: {
    screens: {
      Home: '',
      Profile: 'user/:userId',
      Item: {
        path: 'item/:itemId',
        parse: {
          itemId: (itemId: string) => parseInt(itemId, 10),
        },
      },
      Settings: 'settings',
      // Zagnieżdżone nawigatory
      MainTabs: {
        screens: {
          Feed: 'feed',
          Messages: 'messages/:conversationId?',
        },
      },
    },
  },
};

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

Obsługiwane formaty URL

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

Tworzenie linków w aplikacji

import * as Linking from 'expo-linking';

function ShareButton({ itemId }: { itemId: number }) {
  const handleShare = async () => {
    // Link wewnętrzny (do aplikacji)
    const internalUrl = Linking.createURL(`item/${itemId}`);
    console.log(internalUrl); // exp://192.168.1.5:8081/--/item/42

    // Link uniwersalny (web + aplikacja)
    const universalUrl = `https://atlantyda.app/item/${itemId}`;

    // Udostępnianie
    await Share.share({
      message: `Sprawdź ten przedmiot: ${universalUrl}`,
      url: universalUrl,
    });
  };

  return (
    <Pressable onPress={handleShare}>
      <Text>Udostępnij</Text>
    </Pressable>
  );
}

Otwieranie zewnętrznych URL

import * as Linking from 'expo-linking';

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

  const openSettings = async () => {
    // Otwiera ustawienia systemowe aplikacji
    await Linking.openSettings();
  };

  const sendEmail = async () => {
    await Linking.openURL('mailto:kontakt@atlantyda.app?subject=Pytanie');
  };

  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>Strona WWW</Text></Pressable>
      <Pressable onPress={openSettings}><Text>Ustawienia</Text></Pressable>
      <Pressable onPress={sendEmail}><Text>Email</Text></Pressable>
      <Pressable onPress={makeCall}><Text>Zadzwoń</Text></Pressable>
    </View>
  );
}

Nasłuchiwanie na linki

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

function App() {
  useEffect(() => {
    // Sprawdź czy aplikacja została otwarta z linku
    const getInitialURL = async () => {
      const initialUrl = await Linking.getInitialURL();
      if (initialUrl) {
        console.log('Otwarto z linku:', initialUrl);
      }
    };

    getInitialURL();

    // Nasłuchuj na nowe linki (gdy aplikacja jest w tle)
    const subscription = Linking.addEventListener('url', ({ url }) => {
      console.log('Otrzymano link:', url);
    });

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

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

State persistence (zachowanie stanu nawigacji)

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>
  );
}

Deep Linking znacząco poprawia UX aplikacji, pozwalając na udostępnianie konkretnych treści i integrację z powiadomieniami push.

Kod do tej lekcji: 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();

// Prefiks URL aplikacji, np. atlantyda://item/42
const prefix = Linking.createURL('/');

const linking = {
  prefixes: [prefix],
  // TODO: uzupełnij konfigurację mapowania ścieżek na ekrany:
  // config: {
  //   screens: {
  //     Home: '',
  //     Details: 'item/:itemId',
  //   },
  // },
};

function HomeScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>Portal Atlantydy</Text>
    </View>
  );
}

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

export default function App() {
  return (
    // TODO: przekaż prop linking={linking} do <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',
  },
});

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co definiuje właściwość 'scheme' w app.json?

  2. 2. Która metoda otwiera zewnętrzny URL lub aplikację?

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż tworzenie URL do udostępnienia:

  • Edytor kodu

    Uzupełnij konfigurację linking.

Przydatne artykuły