Skip to Content
React Native9. APIs natives9.4 Partage & presse-papiers

Chapitre 9.4 — Partage & presse-papiers

Où on en est : Drill garde tout pour lui. Un score de 980 sur Symfony mérite d’être montré — on branche le partage système et le presse-papiers.

⏱️ TL;DRShare.share() (API core RN) ouvre la share sheet Android : le score part vers WhatsApp, Telegram, SMS… sans que Drill connaisse aucune de ces apps. expo-clipboard pour copier (le lien du défi, ch. 5.5). Le message se construit dans lib/share.ts (pur, testable) — texte + le deep link drill:// pour les amis équipés.

🎯 Objectifs

  • Utiliser la share sheet native (et comprendre ce qu’elle abstrait).
  • Construire des messages de partage qui donnent envie (le mini-copywriting).
  • Copier au presse-papiers avec confirmation UX.
  • Panorama : partager une IMAGE (react-native-view-shot + expo-sharing).

La share sheet — l’API la plus rentable du téléphone

import { Share } from 'react-native' await Share.share({ message: '🎯 980 pts sur le quiz Symfony de Drill — 9/10 en série ! Tu fais mieux ?', })

C’est tout. Android affiche SA feuille de partage avec les apps de l’utilisateur ; Drill ne sait ni ne demande où va le texte. Une permission ? Aucune — c’est l’utilisateur qui choisit la destination, le modèle de sécurité est dans le geste.

Le retour : Share.share() résout avec { action: 'sharedAction' | 'dismissedAction' } — ne vous y fiez pas trop sur Android (sharedAction ne garantit pas un envoi réel) ; ne déclenchez pas de récompense dessus.

Le message : lib/share.ts

Le texte de partage est du produit, pas de la technique — il se construit, se teste et s’itère :

// lib/share.ts — pur : testable, itérable import type { SessionSummary } from './types' export function buildShareMessage(s: SessionSummary): string { const accuracy = Math.round((s.correctCount / s.total) * 100) const flair = s.isNewRecord ? '🏆 NOUVEAU RECORD — ' : '' const streakLine = s.bestStreak >= 3 ? ` (série de ${s.bestStreak} !)` : '' return ( `${flair}${s.score} pts sur le quiz ${s.themeTitle} 🎯\n` + `${s.correctCount}/${s.total} bonnes réponses${streakLine} — ${accuracy} %\n` + `Tu fais mieux ? drill://quiz?theme=${s.theme}&count=${s.total}` ) }
// L'écran résultat <Button label="Partager mon score" variant="secondary" onPress={() => Share.share({ message: buildShareMessage(summary) })} />

Les règles du message qui marche : le CHIFFRE d’abord (le score est le héros), un défi (« tu fais mieux ? » — la raison de cliquer), et le deep link pour les amis équipés. Limite honnête : drill:// n’est cliquable que si Drill est installé (ch. 5.5) — pour une app distribuée publiquement, on mettrait l’URL Play Store en plus (P13).

Le presse-papiers

npx expo install expo-clipboard
import * as Clipboard from 'expo-clipboard' import { useState } from 'react' function CopyChallengeButton({ url }: { url: string }) { const [copied, setCopied] = useState(false) return ( <Button label={copied ? '✓ Copié !' : 'Copier le lien du défi'} variant="ghost" onPress={async () => { await Clipboard.setStringAsync(url) feedback.tap() setCopied(true) setTimeout(() => setCopied(false), 2000) }} /> ) }

L’UX à trois temps : action → confirmation visible (le label change — jamais de copie silencieuse) → retour à la normale. Android 13+ affiche aussi sa propre confirmation système ; la vôtre reste utile (les versions antérieures n’en ont pas, et le label est plus clair).

Panorama : partager une image de score

Le niveau au-dessus — la carte de score en IMAGE (façon Wordle) — s’assemble avec deux briques :

  1. react-native-view-shot : capture n’importe quelle View en PNG (captureRef(viewRef) → URI de fichier). On dessine une jolie carte hors écran (score, thème, date, branding Drill), on la capture.
  2. expo-sharing : Sharing.shareAsync(uri) — la share sheet pour un fichier (l’API Share core ne gère bien que le texte sur Android).

Le pattern complet est un excellent exercice de P19 (Drill v2) : composant ScoreCard stylé NativeWind → ref → capture → partage. Comptez 30 lignes — l’effet « wow » le moins cher du développement mobile.

💡 Pour un dev Next.js — L’équivalent web (navigator.share) existe mais reste capricieux desktop. En mobile natif, la share sheet est LE canal de croissance organique : chaque partage est une pub gratuite portée par la fierté de l’utilisateur. Dans vos apps perso, c’est aussi le pont le plus simple vers « sortir les données » (un CSV de dépenses partagé vers Drive — P19).

⚠️ Piège — Ne mettez RIEN de sensible dans un message de partage construit automatiquement : le texte part en clair vers l’app que l’utilisateur choisit. Pour Drill (un score), aucun enjeu — mais le réflexe de relire « qu’est-ce qui part exactement ? » avant chaque Share.share doit devenir automatique.

✏️ Exercices

1. Testez buildShareMessage sur trois cas : session ordinaire, nouveau record avec streak, 0/10 lamentable. Le message du 0/10 donne-t-il envie de partager ? Ajustez.

✅ Solution

Le 0/10 en l’état est humiliant (« 0 pts — 0 % »). Deux écoles : l’autodérision ('💀 0/10 sur Symfony… je retourne réviser. Tu feras forcément mieux :') ou masquer le bouton sous un seuil. La bonne réponse est produit, pas technique : l’autodérision partage MIEUX que la gloire (cf. Wordle) — on garde le bouton avec un message adapté par palier (< 30 %, 30-70 %, > 70 %, record). buildShareMessage devient un switch sur le palier : toujours pur, toujours testable.

2. Le bouton « Copier le défi » doit produire une URL valide même si themeTitle contient espaces/accents. Où est le bug potentiel et comment le fixer proprement ?

✅ Solution

L’URL embarque s.theme (le slug — déjà URL-safe par contrat du manifest, ch. 7.3), pas themeTitle. Si un doute subsiste : encodeURIComponent(theme) à la construction. La leçon : les URLs se construisent depuis les identifiants machine (slugs), jamais depuis les libellés humains — le duo title/slug du manifest existait précisément pour ça.

3. Implémentez la carte de score partageable (panorama) en version minimale : ScoreCard stylée + captureRef + shareAsync.

✅ Solution

const cardRef = useRef<View>(null) <View ref={cardRef} collapsable={false} className="rounded-2xl bg-ink-900 p-6"> {/* score, thème, badges — votre plus belle carte */} </View> <Button label="Partager en image" onPress={async () => { const uri = await captureRef(cardRef, { format: 'png', quality: 1 }) await Sharing.shareAsync(uri) }} />

(npx expo install react-native-view-shot expo-sharing.) Le collapsable={false} est le piège Android : sans lui, la View peut être « optimisée » hors de l’arbre natif et la capture échoue. La carte peut même être rendue hors écran (position absolute, left: -9999).

🧠 Quiz

1. Quelle permission exige la share sheet ?

Réponse

Aucune : l’utilisateur choisit lui-même la destination dans la feuille système — le consentement EST le geste. Drill ne sait pas où part le texte.

2. Pourquoi ne pas récompenser sur action: ‘sharedAction’ ?

Réponse

Sur Android, ce retour ne garantit pas un envoi réel (il peut signaler la simple sélection). Fiabilité insuffisante pour du gameplay/récompense.

3. Les trois ingrédients d’un message de partage efficace ?

Réponse

Le chiffre en héros (score), le défi qui motive le destinataire (« tu fais mieux ? »), et un lien actionnable (deep link, + URL store pour une app publique).

4. Quelle est l’UX minimale d’une copie presse-papiers ?

Réponse

Une confirmation visible immédiate (label « ✓ Copié ! » 2 s, léger haptic) — jamais de copie silencieuse, même si Android 13+ ajoute la sienne.

5. Quelles briques pour partager une IMAGE de score ?

Réponse

react-native-view-shot (captureRef d’une View stylée → PNG) + expo-sharing (shareAsync du fichier) — et collapsable={false} sur la View capturée (piège Android).


👉 Chapitre suivant : 9.5 — Panorama des APIs natives — fichiers, caméra, capteurs : la carte pour vos futures apps.