Backend2024-01-0515 min read
Firebase Integration with Flutter: Complete Guide
By Hasnain Makada
Firebase Integration with Flutter: Complete Guide
Firebase provides a comprehensive backend-as-a-service platform that integrates seamlessly with Flutter. This guide covers everything you need to know about integrating Firebase services into your Flutter applications.
1. Setting Up Firebase
Install Firebase CLI
npm install -g firebase-tools
firebase login
Initialize Firebase in Your Project
# In your Flutter project root
firebase init
# Install FlutterFire CLI
dart pub global activate flutterfire_cli
# Configure Firebase for Flutter
flutterfire configure
Add Firebase Dependencies
dependencies:
firebase_core: ^2.24.2
firebase_auth: ^4.15.3
cloud_firestore: ^4.13.6
firebase_storage: ^11.5.6
firebase_messaging: ^14.7.10
firebase_analytics: ^10.7.4
2. Firebase Authentication
Basic Setup
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Get current user
User? get currentUser => _auth.currentUser;
// Auth state changes stream
Stream<User?> get authStateChanges => _auth.authStateChanges();
}
Email/Password Authentication
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Sign up with email and password
Future<UserCredential?> signUpWithEmailAndPassword(
String email,
String password,
) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return result;
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Sign in with email and password
Future<UserCredential?> signInWithEmailAndPassword(
String email,
String password,
) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return result;
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Sign out
Future<void> signOut() async {
await _auth.signOut();
}
// Handle authentication exceptions
String _handleAuthException(FirebaseAuthException e) {
switch (e.code) {
case 'weak-password':
return 'The password provided is too weak.';
case 'email-already-in-use':
return 'The account already exists for that email.';
case 'user-not-found':
return 'No user found for that email.';
case 'wrong-password':
return 'Wrong password provided for that user.';
default:
return 'An error occurred. Please try again.';
}
}
}
Google Sign-In Integration
import 'package:google_sign_in/google_sign_in.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Future<UserCredential?> signInWithGoogle() async {
try {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return null;
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Sign in to Firebase with the Google credential
return await _auth.signInWithCredential(credential);
} catch (e) {
throw 'Google sign-in failed: $e';
}
}
}
3. Cloud Firestore Database
Basic CRUD Operations
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Create document
Future<void> createUser(String uid, Map<String, dynamic> userData) async {
try {
await _db.collection('users').doc(uid).set(userData);
} catch (e) {
throw 'Failed to create user: $e';
}
}
// Read document
Future<DocumentSnapshot> getUser(String uid) async {
try {
return await _db.collection('users').doc(uid).get();
} catch (e) {
throw 'Failed to get user: $e';
}
}
// Update document
Future<void> updateUser(String uid, Map<String, dynamic> data) async {
try {
await _db.collection('users').doc(uid).update(data);
} catch (e) {
throw 'Failed to update user: $e';
}
}
// Delete document
Future<void> deleteUser(String uid) async {
try {
await _db.collection('users').doc(uid).delete();
} catch (e) {
throw 'Failed to delete user: $e';
}
}
// Stream of documents
Stream<QuerySnapshot> getUsersStream() {
return _db.collection('users').snapshots();
}
}
Advanced Queries
class FirestoreService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Complex query with multiple conditions
Future<List<QueryDocumentSnapshot>> getFilteredUsers({
required String city,
required int minAge,
int limit = 10,
}) async {
try {
QuerySnapshot querySnapshot = await _db
.collection('users')
.where('city', isEqualTo: city)
.where('age', isGreaterThanOrEqualTo: minAge)
.orderBy('age')
.limit(limit)
.get();
return querySnapshot.docs;
} catch (e) {
throw 'Failed to get filtered users: $e';
}
}
// Pagination
Future<List<QueryDocumentSnapshot>> getUsersPaginated({
DocumentSnapshot? lastDocument,
int limit = 10,
}) async {
try {
Query query = _db
.collection('users')
.orderBy('createdAt', descending: true)
.limit(limit);
if (lastDocument != null) {
query = query.startAfterDocument(lastDocument);
}
QuerySnapshot querySnapshot = await query.get();
return querySnapshot.docs;
} catch (e) {
throw 'Failed to get paginated users: $e';
}
}
}
4. Firebase Storage
File Upload and Download
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';
class StorageService {
final FirebaseStorage _storage = FirebaseStorage.instance;
// Upload file
Future<String> uploadFile(File file, String path) async {
try {
Reference ref = _storage.ref().child(path);
UploadTask uploadTask = ref.putFile(file);
// Monitor upload progress
uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
double progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
print('Upload progress: ${progress.toStringAsFixed(2)}%');
});
TaskSnapshot snapshot = await uploadTask;
String downloadUrl = await snapshot.ref.getDownloadURL();
return downloadUrl;
} catch (e) {
throw 'Failed to upload file: $e';
}
}
// Upload with metadata
Future<String> uploadFileWithMetadata(
File file,
String path,
Map<String, String> metadata,
) async {
try {
Reference ref = _storage.ref().child(path);
SettableMetadata settableMetadata = SettableMetadata(
customMetadata: metadata,
contentType: 'image/jpeg',
);
UploadTask uploadTask = ref.putFile(file, settableMetadata);
TaskSnapshot snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
} catch (e) {
throw 'Failed to upload file with metadata: $e';
}
}
// Delete file
Future<void> deleteFile(String path) async {
try {
Reference ref = _storage.ref().child(path);
await ref.delete();
} catch (e) {
throw 'Failed to delete file: $e';
}
}
}
5. Push Notifications with FCM
Setup and Configuration
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin _localNotifications =
FlutterLocalNotificationsPlugin();
Future<void> initialize() async {
// Request permission
NotificationSettings settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
// Get FCM token
String? token = await _messaging.getToken();
print('FCM Token: $token');
// Initialize local notifications
await _initializeLocalNotifications();
// Handle background messages
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Handle foreground messages
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
// Handle notification taps
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageOpenedApp);
}
}
Future<void> _initializeLocalNotifications() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings();
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await _localNotifications.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
);
}
void _handleForegroundMessage(RemoteMessage message) {
print('Handling a foreground message: ${message.messageId}');
_showLocalNotification(message);
}
void _handleMessageOpenedApp(RemoteMessage message) {
print('Message clicked!');
// Navigate to specific screen
}
Future<void> _showLocalNotification(RemoteMessage message) async {
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
channelDescription: 'This channel is used for important notifications.',
importance: Importance.high,
priority: Priority.high,
);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await _localNotifications.show(
message.hashCode,
message.notification?.title,
message.notification?.body,
platformChannelSpecifics,
);
}
void _onDidReceiveNotificationResponse(
NotificationResponse notificationResponse) {
// Handle notification tap
}
}
// Top-level function for background message handling
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message: ${message.messageId}');
}
6. Firebase Analytics
Basic Analytics Implementation
import 'package:firebase_analytics/firebase_analytics.dart';
class AnalyticsService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
// Set user properties
Future<void> setUserProperties({
required String userId,
required String userType,
}) async {
await _analytics.setUserId(id: userId);
await _analytics.setUserProperty(name: 'user_type', value: userType);
}
// Log custom events
Future<void> logCustomEvent(String eventName, Map<String, Object> parameters) async {
await _analytics.logEvent(name: eventName, parameters: parameters);
}
// Log screen views
Future<void> logScreenView(String screenName) async {
await _analytics.logScreenView(screenName: screenName);
}
// Log purchases
Future<void> logPurchase({
required String itemId,
required String itemName,
required double value,
required String currency,
}) async {
await _analytics.logPurchase(
currency: currency,
value: value,
parameters: {
'item_id': itemId,
'item_name': itemName,
},
);
}
}
7. Security Rules
Firestore Security Rules Example
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only access their own documents
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Public posts that anyone can read, but only authenticated users can write
match /posts/{postId} {
allow read: if true;
allow write: if request.auth != null;
}
// Private posts that only the owner can access
match /private_posts/{postId} {
allow read, write: if request.auth != null &&
request.auth.uid == resource.data.ownerId;
}
}
}
Firebase Storage Security Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Users can only upload to their own folder
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Public images that anyone can read
match /public/{allPaths=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
Conclusion
Firebase provides a powerful, scalable backend solution for Flutter applications. This guide covered the essential services:
- Authentication - Secure user management
- Firestore - Real-time NoSQL database
- Storage - File storage and management
- Cloud Messaging - Push notifications
- Analytics - User behavior tracking
- Security Rules - Data protection
Start with the services that match your immediate needs and expand as your application grows. Remember to implement proper error handling, security rules, and monitor your usage to optimize costs and performance.
Tags:
FlutterFirebaseBackendAuthentication