Guide pour accéder à la galerie photo du téléphone avec Flutter


Avatar de Pierre Courtois

Pouvoir accéder à la galerie s’avère être une fonctionnalité importante pour de nombreuses applications. Par exemple, vous pourriez en avoir besoin pour laisser un utilisateur ajouter sa photo de profil. Dans ce guide, je vous explique non seulement comment accéder à la galerie, mais aussi comment stocker des photos pour les réutiliser.


Accéder à la galerie photo avec Flutter

Implémenter Image Picker dans Flutter

Flutter ne permet pas de manière native d’accéder à la galerie photo du téléphone. Vous allez devoir passer par le package image_picker, qui va vous permettre de réaliser cette action. La première étape consiste donc à le télécharger et à l’implémenter sur votre application Flutter.

Installer le package

L’installation du package image_picker peut se faire soit en allant dans votre fichier pubspec.yaml et en y ajoutant la ligne suivante en dessous de dependencies :

image_picker: ^1.1.2 //Changer par la dernière version disponible

Soit en exécutant la commande suivante dans votre terminal :

flutter pub add image_picker

Une fois ajouté, synchronisez les dépendances avec la commande suivante :

flutter pub get

Configurer image_picker pour iOS

Une fois le package téléchargé, il reste encore quelques étapes avant de pouvoir l’utiliser dans votre application Flutter. En effet, celui-ci est directement disponible pour Android, mais demande quelques ajouts dans le fichier Info.plist pour IOS.

Voici les permissions que vous devez ajouter dans le fichier ios > Runner > Info.plist , à l’intérieur du tag <dict>:

<key>NSPhotoLibraryUsageDescription</key> //Permet d'accéder à la galerie
<string>Nous avons besoin d'accéder à votre galerie pour sélectionner des photos.</string>
//Le reste est optionnel, mais vous donne aussi accès à la caméra et au micro
<key>NSCameraUsageDescription</key>
<string>Nous avons besoin d'accéder à votre caméra pour capturer des photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Nous avons besoin d'accéder au microphone pour enregistrer des vidéos.</string>

Implémentation du code dans Flutter

Maintenant que image_picker est implémenté, commencez par ajouter la ligne d’importation du package, dans le fichier contenant le widget est charge d’accéder à la galerie photo:

import 'package:image_picker/image_picker.dart';

Voici ensuite les étapes à suivre pour accéder à la galerie :

  1. Initialiser l’instance d’ImagePicker

Nous devons créer une instance de ImagePicker pour appeler ses méthodes. Cela se fait généralement dans la classe State de votre widget.

final ImagePicker _picker = ImagePicker();

Cette instance agit comme un « contrôleur » qui permet de demander des images à la galerie ou à la caméra.

2. Créer une variable pour stocker l’image sélectionnée

Les fichiers tels que les photos peuvent être stockés dans une variable de type File? . Une fois stockée, vous allez pouvoir réutiliser votre variable, là où vous en aurez besoin :

File? _imageFile;

Astuce : Si vous souhaitez autoriser plusieurs images, vous pouvez utiliser une liste à la place :

List<File> _images = [];

3. Implémenter la méthode pour récupérer l’image

La méthode _pickImage() appelle le package image_picker pour ouvrir la galerie et sélectionner une image.

Future<void> _pickImage() async {
  final XFile? pickedImage = await _picker.pickImage(source: ImageSource.gallery);
  if (pickedImage != null) {
    setState(() {
      _imageFile = File(pickedImage.path);
    });
  }
}
  • await _picker.pickImage : Ouvre la galerie. Vous pouvez remplacer ImageSource.gallery par ImageSource.camera si vous voulez capturer une photo avec l’appareil photo.
  • XFile? pickedImage : Représente l’image sélectionnée.
  • pickedImage.path : Contient le chemin du fichier sélectionné.
  • File(pickedImage.path) : Convertit le chemin en un fichier manipulable.

4. Construire l’interface utilisateur pour accéder à la galerie

Ici je crée simplement un bouton qui me permet d’ouvrir la galerie et un widget Image qui va contenir ma photo, une fois que je l’ai sélectionnée.

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Image Picker Example')),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // Afficher l'image sélectionnée
          _imageFile != null
              ? Image.file(_imageFile!, width: 200, height: 200)
              : Text('Aucune image sélectionnée'),
          SizedBox(height: 20),
          // Bouton pour ouvrir la galerie
          ElevatedButton(
            onPressed: _pickImage,
            child: Text('Choisir une image'),
          ),
        ],
      ),
    ),
  );
}

Voici le code complet que vous pouvez adapter selon vos besoins :

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized(); // Initialisation des plugins
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ImagePickerExample(),
    );
  }
}

class ImagePickerExample extends StatefulWidget {
  @override
  _ImagePickerExampleState createState() => _ImagePickerExampleState();
}

class _ImagePickerExampleState extends State<ImagePickerExample> {
  final ImagePicker _picker = ImagePicker();
  File? _imageFile;

  Future<void> _pickImage() async {
    final XFile? pickedImage = await _picker.pickImage(source: ImageSource.gallery);
    if (pickedImage != null) {
      setState(() {
        _imageFile = File(pickedImage.path);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Image Picker Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _imageFile != null
                ? Image.file(_imageFile!, width: 200, height: 200)
                : Text('Aucune image sélectionnée'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _pickImage,
              child: Text('Choisir une image'),
            ),
          ],
        ),
      ),
    );
  }
}

Sélectionner plusieurs images à la fois

Si vous souhaitez laisser l’utilisateur sélectionner plusieurs images à la fois, il vous suffit de légèrement changer le code présenté en exemple :

List<File> _images = [];

Future<void> _pickMultipleImages() async {
  final List<XFile>? pickedImages = await _picker.pickMultiImage();
  if (pickedImages != null) {
    setState(() {
      _images = pickedImages.map((file) => File(file.path)).toList();
    });
  }
}

Vous pouvez ensuite afficher toutes les photos dans un widget GridView

Expanded(
// Utilisation de Expanded pour limiter l'espace du GridView
                    child: GridView.builder(
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 3,
                      ),
                      itemCount: _images.length,
                      itemBuilder: (context, index) {
                        return Padding(
                          padding: const EdgeInsets.all(4.0),
                          child: Image.file(_images[index]),
                        );
                      },
                    ),
                  )

Stocker une photo

Dans certains cas, vous pourrez avoir besoin que la photo d’un utilisateur soit stockée, afin de ne pas avoir à la lui demander à chaque utilisation. Voici deux manières de le faire.

Sauvegarder une photo localement

Pour sauvegarder localement une photo prise dans la galerie, vous pouvez utiliser le package path_provider de Flutter. Celui-ci vous permet d’accéder au répertoire des documents de l’application, qui est un endroit sûr pour stocker des fichiers persistants.

Commencez par l’installer en ajoutant la dépendance path_provider: ^2.0.5 à votre fichier pubspec.yaml.

Pensez aussi à ajouter les lignes <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> et <uses-permission android:name= »android.permission.READ_EXTERNAL_STORAGE » /> dans votre fichier android > app > src > main > AndroidManifest.xml si elle n’est pas déjà présente.

Vous pouvez ensuite sauvegarder votre photo, dans les fichiers de votre application, de la manière suivante :

  Future<File> saveImageLocally(File imageFile) async {
    final directory = await getApplicationDocumentsDirectory();
    final String path = directory.path;

    // Générer un nom de fichier unique
    final String fileName =
        'saved_image_${DateTime.now().millisecondsSinceEpoch}.jpg';

    // Créer le chemin complet
    final String newPath = '$path/$fileName';

    // Copier le fichier dans le répertoire permanent
    final File newImage = await imageFile.copy(newPath);

    print('Image sauvegardée localement : $newPath');
    return newImage;
  }
Explication :
  1. getApplicationDocumentsDirectory() : Retourne un répertoire permanent unique à l’application.
  2. DateTime.now().millisecondsSinceEpoch : Génère un nom de fichier unique basé sur l’horodatage actuel.
  3. imageFile.copy() : Copie le fichier à un nouvel emplacement.

Vous devrez ensuite récupérer l’image au démarrage de votre application et la stocker dans une variable de type File, pour pouvoir l’utiliser :

//Cette fonction doit être placé dans votre initState() pour charger la photo quand le widget se construit

  Future<File?> loadSavedImage() async {
    try {
      final directory = await getApplicationDocumentsDirectory();
      print("Le répertoire est récupéré : ${directory.path}");
      final String path = directory.path;

      // Rechercher des fichiers image dans le répertoire
      final List<FileSystemEntity> files =
          await Directory(path).list().toList();

      // Filtrer uniquement les fichiers images (par exemple .png, .jpg)
      final imageFiles = files.where((file) {
        return file is File &&
            (file.path.endsWith('.png') ||
                file.path.endsWith('.jpg') ||
                file.path.endsWith('.jpeg'));
      }).toList();

      if (imageFiles.isNotEmpty) {
        final String filePath =
            imageFiles.first.path; // Charger le premier fichier image trouvé
        print('Image trouvée localement : $filePath');
        return File(filePath);
      }

      print("Aucune image trouvée dans le répertoire local");
      return null;
    } catch (e) {
      print("Erreur lors du chargement de l'image : $e");
      return null;
    }
  }

Voici l’implémentation complète :

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized(); // Initialisation des plugins
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LocalImageApp(),
    );
  }
}

class LocalImageApp extends StatefulWidget {
  @override
  _LocalImageAppState createState() => _LocalImageAppState();
}

class _LocalImageAppState extends State<LocalImageApp> {
  File? _imageFile;
  final ImagePicker _picker = ImagePicker();

  @override
  void initState() {
    super.initState();
    _loadImage();
  }

  Future<void> _loadImage() async {
    final File? image = await loadSavedImage();
    setState(() {
      _imageFile = image;
    });
  }

  Future<File> saveImageLocally(File imageFile) async {
    final directory = await getApplicationDocumentsDirectory();
    final String path = directory.path;

    // Générer un nom de fichier unique
    final String fileName =
        'saved_image_${DateTime.now().millisecondsSinceEpoch}.jpg';

    // Créer le chemin complet
    final String newPath = '$path/$fileName';

    // Copier le fichier dans le répertoire permanent
    final File newImage = await imageFile.copy(newPath);

    print('Image sauvegardée localement : $newPath');
    return newImage;
  }

  Future<File?> loadSavedImage() async {
    try {
      final directory = await getApplicationDocumentsDirectory();
      print("Le répertoire est récupéré : ${directory.path}");
      final String path = directory.path;

      // Rechercher des fichiers image dans le répertoire
      final List<FileSystemEntity> files =
          await Directory(path).list().toList();

      // Filtrer uniquement les fichiers images (par exemple .png, .jpg)
      final imageFiles = files.where((file) {
        return file is File &&
            (file.path.endsWith('.png') ||
                file.path.endsWith('.jpg') ||
                file.path.endsWith('.jpeg'));
      }).toList();

      if (imageFiles.isNotEmpty) {
        final String filePath =
            imageFiles.first.path; // Charger le premier fichier image trouvé
        print('Image trouvée localement : $filePath');
        return File(filePath);
      }

      print("Aucune image trouvée dans le répertoire local");
      return null;
    } catch (e) {
      print("Erreur lors du chargement de l'image : $e");
      return null;
    }
  }

  Future<void> _pickAndSaveImage() async {
    final XFile? pickedImage =
        await _picker.pickImage(source: ImageSource.gallery);
    if (pickedImage != null) {
      final File newImage = File(pickedImage.path);
      final File savedImage = await saveImageLocally(newImage);
      setState(() {
        _imageFile = savedImage;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Sauvegarder une image localement')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _imageFile != null
                ? Image.file(_imageFile!, width: 200, height: 200)
                : Text('Aucune image sauvegardée'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _pickAndSaveImage,
              child: Text('Choisir et sauvegarder une image'),
            ),
          ],
        ),
      ),
    );
  }
}

Vous pouvez ensuite adapter ce code selon vos besoins, par exemple pour récupérer une photo en particulier, plutôt que la première de la liste.

Sauvegarder une photo dans Firebase

La deuxième manière de stocker votre photo est de la sauvegarder dans le back-end de votre application, par exemple avec la solution Firebase Storage. Si vous ne savez pas comment mettre en place cette outil, j’ai rédigé un guide complet sur comment ajouter Firebase Storage à une application Flutter.

Si ce n’est pas déjà fait, vous devrez aussi installer Firebase sur votre projet Flutter.

Une fois, Firebase Storage ajouté à votre application, vous pouvez ensuite ajouter toute la logique pour récupérer une photo et la stocker dans votre back-end.

Future<void> _uploadToFirebase() async {
  if (_imageFile == null) return;  // Si aucune image n'a été choisie, on quitte la fonction

  try {
    final storageRef = FirebaseStorage.instance.ref();  // Accéder à Firebase Storage
    final imageRef = storageRef.child('images/${DateTime.now().millisecondsSinceEpoch}.jpg');  // Créer un nom unique pour l'image et vient la placer dans mon dossier /images
    final uploadTask = await imageRef.putFile(_imageFile!);  // Uploade l'image vers Firebase Storage
    final downloadUrl = await imageRef.getDownloadURL();  // Récupére l'URL de l'image uploadée

    setState(() {
      _uploadedImageUrl = downloadUrl;  // Met à jour l'URL de l'image uploadée et le stock dans une variable pour pouvoir retourner le récupérer dans Storage
    });

    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Image uploadée avec succès!')));
  } catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Erreur lors de l\'upload : $e')));
  }
}

Voici un peu plus d’explications :

Vérification de l’image : On commence par vérifier si _imageFile est null. Si c’est le cas, cela signifie que l’utilisateur n’a pas sélectionné d’image, donc la fonction s’arrête.

Référence à Firebase Storage :

  • FirebaseStorage.instance.ref() : Cela accède à la racine de Firebase Storage.
  • storageRef.child('images/${DateTime.now().millisecondsSinceEpoch}.jpg') : Cela crée un chemin unique pour l’image en utilisant un nom basé sur l’horodatage actuel (millisecondsSinceEpoch).

Uploader le fichier :

  • await imageRef.putFile(_imageFile!) : Cette méthode télécharge le fichier dans Firebase Storage à l’emplacement spécifié par imageRef.

Récupérer l’URL :

  • await imageRef.getDownloadURL() : Une fois l’image téléchargée, nous récupérons l’URL publique pour accéder à l’image.

Afficher un message :

  • Si le téléchargement réussit, un SnackBar est affiché pour indiquer que l’image a été téléchargée avec succès.
  • Si une erreur se produit, un message d’erreur est affiché dans un SnackBar.

Voici le code complet que vous pouvez adapter à vos besoins :

import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:tutoflutter/firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized(); // Initialisation des plugins
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  
    await FirebaseAppCheck.instance.activate(
    androidProvider: AndroidProvider.debug,
    appleProvider: AppleProvider.debug,
    webProvider: ReCaptchaV3Provider('Remplace_par_votre_clé')
  );

  await FirebaseAuth.instance.signInWithEmailAndPassword(
    email: "emailtest@test.fr",
    password: "123456",
  );
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FirebaseStorageExample(),
    );
  }
}

class FirebaseStorageExample extends StatefulWidget {
  @override
  _FirebaseStorageExampleState createState() => _FirebaseStorageExampleState();
}

class _FirebaseStorageExampleState extends State<FirebaseStorageExample> {
  final ImagePicker _picker = ImagePicker();
  File? _imageFile;
  String? _uploadedImageUrl;

  Future<void> _pickImage() async {
    final XFile? pickedImage = await _picker.pickImage(source: ImageSource.gallery);
    if (pickedImage != null) {
      setState(() {
        _imageFile = File(pickedImage.path);
      });
    }
  }

  Future<void> _uploadToFirebase() async {
    if (_imageFile == null) return;

    try {
      final storageRef = FirebaseStorage.instance.ref();
      final imageRef = storageRef.child('images/${DateTime.now().millisecondsSinceEpoch}.jpg');

      final uploadTask = await imageRef.putFile(_imageFile!);
      final downloadUrl = await imageRef.getDownloadURL();

      setState(() {
        _uploadedImageUrl = downloadUrl;
      });

      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Image uploadée avec succès!')));
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Erreur lors de l\'upload : $e')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Upload Firebase')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _imageFile != null
                ? Image.file(_imageFile!, width: 200, height: 200)
                : Text('Aucune image sélectionnée'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _pickImage,
              child: Text('Choisir une image'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _uploadToFirebase,
              child: Text('Uploader dans Firebase'),
            ),
            if (_uploadedImageUrl != null)
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text('Image URL: $_uploadedImageUrl'),
              ),
          ],
        ),
      ),
    );
  }
}

Lorsque vous sauvegardez une ressource dans Firebase Storage, donnez lui un nom que vous pourrez facilement retrouver, ou sauvegardez cette valeur dans Firebase Firestore. Par exemple, vous pouvez renommer la photo d’un utilisateur avec son ID, qui est très facile à retrouver avec auth.currentUser.uid.

Avatar de Pierre Courtois