Firebase avec Flutter
Gad Ntenta

Gad Ntenta

Développeur Full Stack

Firebase avec Flutter

FlutterFirebaseBackendAuthentication

Intégrez Firebase dans votre application Flutter pour l'authentification, la base de données et plus encore.

Firebase avec Flutter

Introduction

Firebase est une plateforme complète qui offre de nombreux services pour le développement d'applications. Dans cet article, nous allons explorer comment intégrer et utiliser Firebase dans vos applications Flutter.

Configuration de Firebase

1. Installation des Dépendances

Dans votre pubspec.yaml :

dependencies:
  firebase_core: ^2.15.0
  firebase_auth: ^4.7.2
  cloud_firestore: ^4.8.4
  firebase_storage: ^11.2.5
  firebase_messaging: ^14.6.5

2. Initialisation de Firebase

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

Authentification

1. Service d'Authentification

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // État de l'authentification
  Stream<User?> get authStateChanges => _auth.authStateChanges();

  // Connexion avec email et mot de passe
  Future<UserCredential> signInWithEmailAndPassword(
    String email,
    String password,
  ) async {
    try {
      return await _auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      throw _handleAuthException(e);
    }
  }

  // Inscription avec email et mot de passe
  Future<UserCredential> createUserWithEmailAndPassword(
    String email,
    String password,
  ) async {
    try {
      return await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      throw _handleAuthException(e);
    }
  }

  // Déconnexion
  Future<void> signOut() async {
    await _auth.signOut();
  }

  // Gestion des erreurs
  Exception _handleAuthException(FirebaseAuthException e) {
    switch (e.code) {
      case 'user-not-found':
        return Exception('Aucun utilisateur trouvé avec cet email.');
      case 'wrong-password':
        return Exception('Mot de passe incorrect.');
      case 'email-already-in-use':
        return Exception('Cet email est déjà utilisé.');
      default:
        return Exception('Une erreur est survenue.');
    }
  }
}

2. Interface de Connexion

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _authService = AuthService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Connexion')),
      body: Form(
        key: _formKey,
        child: Padding(
          padding: EdgeInsets.all(16.0),
          child: Column(
            children: [
              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(labelText: 'Email'),
                validator: (value) {
                  if (value?.isEmpty ?? true) {
                    return 'Veuillez entrer votre email';
                  }
                  return null;
                },
              ),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(labelText: 'Mot de passe'),
                obscureText: true,
                validator: (value) {
                  if (value?.isEmpty ?? true) {
                    return 'Veuillez entrer votre mot de passe';
                  }
                  return null;
                },
              ),
              ElevatedButton(
                onPressed: _signIn,
                child: Text('Se connecter'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _signIn() async {
    if (_formKey.currentState?.validate() ?? false) {
      try {
        await _authService.signInWithEmailAndPassword(
          _emailController.text,
          _passwordController.text,
        );
        Navigator.pushReplacementNamed(context, '/home');
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(e.toString())),
        );
      }
    }
  }
}

Firestore

1. Service de Base de Données

class FirestoreService {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  // Collection des utilisateurs
  CollectionReference get users => _firestore.collection('users');

  // Ajouter un utilisateur
  Future<void> addUser(UserModel user) async {
    try {
      await users.doc(user.id).set(user.toMap());
    } catch (e) {
      throw Exception('Erreur lors de l'ajout de l'utilisateur');
    }
  }

  // Récupérer un utilisateur
  Future<UserModel?> getUser(String id) async {
    try {
      final doc = await users.doc(id).get();
      if (doc.exists) {
        return UserModel.fromMap(doc.data() as Map<String, dynamic>);
      }
      return null;
    } catch (e) {
      throw Exception('Erreur lors de la récupération de l'utilisateur');
    }
  }

  // Mettre à jour un utilisateur
  Future<void> updateUser(String id, Map<String, dynamic> data) async {
    try {
      await users.doc(id).update(data);
    } catch (e) {
      throw Exception('Erreur lors de la mise à jour de l'utilisateur');
    }
  }

  // Supprimer un utilisateur
  Future<void> deleteUser(String id) async {
    try {
      await users.doc(id).delete();
    } catch (e) {
      throw Exception('Erreur lors de la suppression de l'utilisateur');
    }
  }
}

2. Modèle de Données

class UserModel {
  final String id;
  final String name;
  final String email;
  final DateTime createdAt;

  UserModel({
    required this.id,
    required this.name,
    required this.email,
    required this.createdAt,
  });

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'createdAt': createdAt.toIso8601String(),
    };
  }

  factory UserModel.fromMap(Map<String, dynamic> map) {
    return UserModel(
      id: map['id'],
      name: map['name'],
      email: map['email'],
      createdAt: DateTime.parse(map['createdAt']),
    );
  }
}

Stockage

1. Service de Stockage

class StorageService {
  final FirebaseStorage _storage = FirebaseStorage.instance;

  // Référence au dossier des images
  Reference get imagesRef => _storage.ref().child('images');

  // Uploader une image
  Future<String> uploadImage(File file, String userId) async {
    try {
      final ref = imagesRef.child('$userId/${DateTime.now()}.jpg');
      await ref.putFile(file);
      return await ref.getDownloadURL();
    } catch (e) {
      throw Exception('Erreur lors de l'upload de l'image');
    }
  }

  // Supprimer une image
  Future<void> deleteImage(String url) async {
    try {
      final ref = _storage.refFromURL(url);
      await ref.delete();
    } catch (e) {
      throw Exception('Erreur lors de la suppression de l'image');
    }
  }
}

2. Interface de Téléchargement

class ImageUploadWidget extends StatefulWidget {
  @override
  _ImageUploadWidgetState createState() => _ImageUploadWidgetState();
}

class _ImageUploadWidgetState extends State<ImageUploadWidget> {
  final _storageService = StorageService();
  File? _image;
  bool _isUploading = false;

  Future<void> _pickImage() async {
    final picker = ImagePicker();
    final pickedFile = await picker.pickImage(source: ImageSource.gallery);

    if (pickedFile != null) {
      setState(() {
        _image = File(pickedFile.path);
      });
    }
  }

  Future<void> _uploadImage() async {
    if (_image == null) return;

    setState(() {
      _isUploading = true;
    });

    try {
      final url = await _storageService.uploadImage(
        _image!,
        FirebaseAuth.instance.currentUser!.uid,
      );
      // Mettre à jour le profil utilisateur avec l'URL de l'image
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(e.toString())),
      );
    } finally {
      setState(() {
        _isUploading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (_image != null) ...[
          Image.file(_image!, height: 200),
          SizedBox(height: 16),
        ],
        ElevatedButton(
          onPressed: _isUploading ? null : _pickImage,
          child: Text('Choisir une image'),
        ),
        if (_image != null)
          ElevatedButton(
            onPressed: _isUploading ? null : _uploadImage,
            child: _isUploading
                ? CircularProgressIndicator()
                : Text('Télécharger'),
          ),
      ],
    );
  }
}

Notifications Push

1. Configuration des Notifications

class NotificationService {
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  Future<void> initialize() async {
    // Demander la permission
    NotificationSettings settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      // Obtenir le token
      String? token = await _messaging.getToken();
      print('FCM Token: $token');

      // Configurer les handlers
      FirebaseMessaging.onMessage.listen(_handleMessage);
      FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageOpenedApp);
    }
  }

  void _handleMessage(RemoteMessage message) {
    print('Message reçu: ${message.notification?.title}');
    // Afficher une notification locale
  }

  void _handleMessageOpenedApp(RemoteMessage message) {
    print('Message ouvert: ${message.notification?.title}');
    // Naviguer vers la page appropriée
  }
}

2. Utilisation dans l'Application

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  final notificationService = NotificationService();
  await notificationService.initialize();

  runApp(MyApp());
}

Meilleures Pratiques

1. Sécurité

  • Utilisez les règles de sécurité Firestore
  • Validez les données côté serveur
  • Gérez les tokens de manière sécurisée
  • Utilisez l'authentification à deux facteurs

2. Performance

  • Mettez en cache les données
  • Utilisez la pagination
  • Optimisez les requêtes
  • Gérer la connexion internet

3. Tests

  • Testez les services Firebase
  • Testez l'authentification
  • Testez les règles de sécurité
  • Testez les notifications

Conclusion

Firebase offre une suite complète d'outils pour le développement d'applications Flutter. En suivant ces bonnes pratiques et en utilisant les services appropriés, vous pouvez créer des applications robustes et évolutives.

N'oubliez pas de :

  • Sécuriser vos données
  • Optimiser les performances
  • Tester vos implémentations
  • Suivre les mises à jour de Firebase

Avec ces connaissances, vous êtes prêt à intégrer Firebase dans vos applications Flutter !