Introduction
Les applications mobiles hybrides révolutionnent le développement en 2026 : un seul codebase JavaScript/TypeScript pour cibler le Web, iOS et Android simultanément. Ionic, avec son framework UI basé sur des composants web standards (StencilJS), et Capacitor, le moteur natif moderne de Ionic, permettent d'accéder aux APIs natives comme la caméra ou les notifications via des plugins bridge.
Pourquoi choisir cette stack ? Contrairement à React Native (plus lourd), Ionic + Capacitor offre une perf proche du natif via WebView optimisée, un hot reload ultra-rapide, et une maintenance simplifiée. Imaginez : votre app PWA devient native en quelques commandes. Ce tutoriel intermédiaire vous guide pas à pas pour un projet complet avec une page d'accueil interactive et un plugin caméra. À la fin, vous builderez pour Android/iOS. Temps estimé : 30 min. Parfait pour les devs React qui veulent conquérir le mobile sans réécrire tout.
Prérequis
- Node.js 20+ et npm 10+
- Connaissances en React et TypeScript
- Ionic CLI (installée ci-après)
- Android Studio (pour Android) ou Xcode 15+ (pour iOS, macOS requis)
- Capacitor CLI
- Un émulateur Android ou simulateur iOS configuré
Installer Ionic CLI et créer le projet
npm install -g @ionic/cli@latest
ionic start photoGallery tabs --type react --capacitor
cd photoGallery
ionic serveCette commande installe globalement la CLI Ionic, crée un projet tabs React avec Capacitor intégré, et lance le serveur dev. Le template 'tabs' fournit une structure navigable prête. Évitez 'blank' pour un démarrage rapide ; testez toujours avec 'ionic serve' pour vérifier le hot reload.
Structure du projet généré
Votre projet contient :
src/: Composants React/Ionic (pages, hooks).capacitor.config.ts: Configuration Capacitor.android/etios/: Générés après sync.
Ouvrez
http://localhost:8100 : tabs Explore, Tab1, Tab2 avec UI Material Design-like. Analogie : comme un Next.js mais pour mobile, avec routing Ionic.Configurer Capacitor pour plugins natifs
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.learni.photogallery',
appName: 'Photo Gallery',
webDir: 'build',
bundledWebRuntime: false,
plugins: {
Camera: {
permissions: {
camera: true,
photos: true
}
}
}
};
export default config;Ce fichier centralise l'ID app unique (pour stores), le dossier de build web, et active le plugin Camera avec permissions iOS/Android. 'bundledWebRuntime: false' utilise le WKWebView natif pour perf max. Piège : oubliez 'webDir' et le build web échoue.
Ajouter le plugin Camera et sync
npm install @capacitor/camera @capacitor/core
npm install @capacitor/android @capacitor/ios
npx cap sync
npx cap add android
npx cap add iosInstalle le plugin Camera officiel, les plateformes natives, sync le web vers natives, et ajoute les projets Android/iOS. 'cap sync' copie assets et régénère. Attention : exécutez après tout changement de dépendances pour éviter crashes natifs.
Intégrer la caméra dans une page
Modifiez maintenant src/pages/Explore.tsx pour capturer/prendre photo. Utilisez useCamera hook fourni par Ionic.
Page Explore avec capture photo
import { useState } from 'react';
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonFab, IonFabButton, IonIcon, IonImg, useCamera } from '@ionic/react';
import { camera } from 'ionicons/icons';
const Explore: React.FC = () => {
const [photo, setPhoto] = useState<string>();
const { photo: cameraPhoto, takePhoto } = useCamera();
const takePicture = async () => {
const photoTaken = await takePhoto({
quality: 90,
allowEditing: true,
resultType: 'dataUrl'
});
setPhoto(photoTaken.dataUrl);
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Explore</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
{photo && <IonImg src={photo} alt="Photo capturée" />}
<IonFab vertical="bottom" horizontal="center" slot="fixed">
<IonFabButton onClick={takePicture}>
<IonIcon icon={camera}></IonIcon>
</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
);
};
export default Explore;Ce composant complet utilise le hook useCamera() pour prendre une photo éditée (qualité 90%), l'affiche via , et un FAB flottant. Hook gère permissions auto. Piège : sans resultType: 'dataUrl', pas d'affichage web ; testez sur device pour permissions.
Build web et sync Capacitor
npm run build
npx cap sync
npx cap open android
# Ou pour iOS : npx cap open iosBuild l'app web en prod (dist/build), sync vers natives, et ouvre Android Studio/Xcode. Lancez l'émulateur depuis l'IDE. Évitez de skip 'sync' : natives ne voient pas les assets mis à jour.
Tester sur device
Web : ionic serve.
Android : Studio > Run.
iOS : Xcode > Run.
Permissions demandées auto pour caméra. Debug : Chrome DevTools via chrome://inspect pour Android.
Permissions Android personnalisées
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name">
<activity android:exported="true" android:launchMode="singleTask" android:name="com.getcapacitorcommunity.camera.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>Extrait clé du Manifest Android : ajoute permissions caméra/stockage. 'required=true' force présence caméra. Éditez post-'cap sync' ; rebuild APK. Piège iOS : équivalent dans Info.plist via Xcode.
Bonnes pratiques
- Toujours sync après build :
npx cap syncavant toute ouverture IDE. - Utilisez hooks Ionic pour plugins : abstraction permissions/state.
- PWA first : testez web avant native pour 80% des bugs.
- Gérez erreurs offline : Capacitor plugins wrap try/catch.
- Versionnez
capacitor.config.tsen Git : clé pour CI/CD.
Erreurs courantes à éviter
- Oubli de
npm run build: natives chargent version dev cassée. - Permissions non déclarées : crash silencieux sur device (ajoutez en Manifest/Info.plist).
resultType: 'uri'sans stockage : photos perdues (préférez 'dataUrl' pour simplicité).- Pas de 'bundledWebRuntime: false' : perf WebView dégradée sur iOS.
Pour aller plus loin
- Plugins avancés : Geolocation, Push Notifications (docs Capacitor).
- State management : Zustand ou Jotai pour apps complexes.
- CI/CD : GitHub Actions pour auto-build APK/IPA.