Firebase provides a comprehensive backend solution for mobile apps, handling authentication, databases, storage, and more. This tutorial covers everything you need to integrate Firebase into your mobile applications.
Why Firebase?
- Quick setup - Minutes to integrate
- Scalable - Grows with your app
- Real-time - Live data synchronization
- Free tier - Generous free usage
- Cross-platform - iOS, Android, Web, Flutter
Firebase Services Overview
| Service | Purpose | Free Tier |
|---|---|---|
| Authentication | User sign-in | Unlimited users |
| Firestore | NoSQL database | 1GB storage, 50K reads/day |
| Realtime Database | JSON database | 1GB storage, 100 connections |
| Cloud Storage | File storage | 5GB storage |
| Cloud Functions | Serverless code | 2M invocations/month |
| Cloud Messaging | Push notifications | Unlimited |
| Analytics | User analytics | Unlimited |
| Crashlytics | Crash reporting | Unlimited |
Setting Up Firebase
1. Create Firebase Project
- Go to console.firebase.google.com
- Click "Create Project"
- Enter project name
- Enable/disable Google Analytics
- Create project
2. Add Firebase to Your App
For React Native:
npm install @react-native-firebase/app
npm install @react-native-firebase/auth
npm install @react-native-firebase/firestore
npm install @react-native-firebase/storage
For Flutter:
flutter pub add firebase_core
flutter pub add firebase_auth
flutter pub add cloud_firestore
flutter pub add firebase_storage
Configure:
# Flutter
flutterfire configure
# React Native - download google-services.json (Android)
# and GoogleService-Info.plist (iOS) from console
Firebase Authentication
Email/Password Authentication
React Native:
import auth from '@react-native-firebase/auth';
// Sign up
const signUp = async (email, password) => {
try {
const userCredential = await auth().createUserWithEmailAndPassword(
email,
password
);
console.log('User created:', userCredential.user.uid);
return userCredential.user;
} catch (error) {
if (error.code === 'auth/email-already-in-use') {
throw new Error('Email already in use');
}
if (error.code === 'auth/invalid-email') {
throw new Error('Invalid email address');
}
throw error;
}
};
// Sign in
const signIn = async (email, password) => {
try {
const userCredential = await auth().signInWithEmailAndPassword(
email,
password
);
return userCredential.user;
} catch (error) {
if (error.code === 'auth/wrong-password') {
throw new Error('Invalid password');
}
throw error;
}
};
// Sign out
const signOut = async () => {
await auth().signOut();
};
// Listen to auth state
const unsubscribe = auth().onAuthStateChanged(user => {
if (user) {
console.log('User signed in:', user.email);
} else {
console.log('User signed out');
}
});
Flutter:
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Sign up
Future<User?> signUp(String email, String password) async {
try {
final credential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return credential.user;
} on FirebaseAuthException catch (e) {
if (e.code == 'email-already-in-use') {
throw Exception('Email already in use');
}
throw e;
}
}
// Sign in
Future<User?> signIn(String email, String password) async {
try {
final credential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return credential.user;
} on FirebaseAuthException catch (e) {
if (e.code == 'wrong-password') {
throw Exception('Invalid password');
}
throw e;
}
}
// Sign out
Future<void> signOut() async {
await _auth.signOut();
}
// Auth state stream
Stream<User?> get authStateChanges => _auth.authStateChanges();
}
Social Authentication (Google)
React Native:
import auth from '@react-native-firebase/auth';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
// Configure
GoogleSignin.configure({
webClientId: 'YOUR_WEB_CLIENT_ID',
});
const signInWithGoogle = async () => {
// Get user's ID token
const { idToken } = await GoogleSignin.signIn();
// Create Firebase credential
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign in with credential
return auth().signInWithCredential(googleCredential);
};
Flutter:
import 'package:google_sign_in/google_sign_in.dart';
Future<UserCredential> signInWithGoogle() async {
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
return await FirebaseAuth.instance.signInWithCredential(credential);
}
Cloud Firestore
Basic Operations
React Native:
import firestore from '@react-native-firebase/firestore';
// Reference to collection
const usersRef = firestore().collection('users');
// Add document (auto-generated ID)
const addUser = async (userData) => {
const docRef = await usersRef.add({
...userData,
createdAt: firestore.FieldValue.serverTimestamp(),
});
return docRef.id;
};
// Set document (specific ID)
const setUser = async (userId, userData) => {
await usersRef.doc(userId).set({
...userData,
createdAt: firestore.FieldValue.serverTimestamp(),
});
};
// Get single document
const getUser = async (userId) => {
const doc = await usersRef.doc(userId).get();
if (doc.exists) {
return { id: doc.id, ...doc.data() };
}
return null;
};
// Get all documents
const getAllUsers = async () => {
const snapshot = await usersRef.get();
return snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}));
};
// Update document
const updateUser = async (userId, updates) => {
await usersRef.doc(userId).update({
...updates,
updatedAt: firestore.FieldValue.serverTimestamp(),
});
};
// Delete document
const deleteUser = async (userId) => {
await usersRef.doc(userId).delete();
};
Flutter:
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreService {
final CollectionReference users =
FirebaseFirestore.instance.collection('users');
// Add document
Future<String> addUser(Map<String, dynamic> userData) async {
final docRef = await users.add({
...userData,
'createdAt': FieldValue.serverTimestamp(),
});
return docRef.id;
}
// Get single document
Future<Map<String, dynamic>?> getUser(String userId) async {
final doc = await users.doc(userId).get();
if (doc.exists) {
return {'id': doc.id, ...doc.data() as Map<String, dynamic>};
}
return null;
}
// Get all documents
Future<List<Map<String, dynamic>>> getAllUsers() async {
final snapshot = await users.get();
return snapshot.docs.map((doc) => {
'id': doc.id,
...doc.data() as Map<String, dynamic>,
}).toList();
}
// Update document
Future<void> updateUser(String userId, Map<String, dynamic> updates) async {
await users.doc(userId).update({
...updates,
'updatedAt': FieldValue.serverTimestamp(),
});
}
// Delete document
Future<void> deleteUser(String userId) async {
await users.doc(userId).delete();
}
}
Queries
// React Native
const usersRef = firestore().collection('users');
// Where clause
const activeUsers = await usersRef
.where('isActive', '==', true)
.get();
// Multiple conditions
const filteredUsers = await usersRef
.where('age', '>=', 18)
.where('age', '<=', 65)
.get();
// Order by
const sortedUsers = await usersRef
.orderBy('createdAt', 'desc')
.limit(10)
.get();
// Compound query
const results = await usersRef
.where('city', '==', 'New York')
.where('isActive', '==', true)
.orderBy('name')
.limit(20)
.get();
// Array contains
const taggedPosts = await firestore()
.collection('posts')
.where('tags', 'array-contains', 'firebase')
.get();
// In query
const specificUsers = await usersRef
.where('status', 'in', ['active', 'pending'])
.get();
Real-time Listeners
// React Native - Listen to document
useEffect(() => {
const unsubscribe = firestore()
.collection('users')
.doc(userId)
.onSnapshot(doc => {
if (doc.exists) {
setUser({ id: doc.id, ...doc.data() });
}
}, error => {
console.error('Error:', error);
});
return () => unsubscribe();
}, [userId]);
// Listen to collection
useEffect(() => {
const unsubscribe = firestore()
.collection('messages')
.where('chatId', '==', chatId)
.orderBy('createdAt', 'desc')
.limit(50)
.onSnapshot(snapshot => {
const messages = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}));
setMessages(messages);
});
return () => unsubscribe();
}, [chatId]);
Flutter:
// Stream builder for real-time data
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('messages')
.where('chatId', isEqualTo: chatId)
.orderBy('createdAt', descending: true)
.limit(50)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final messages = snapshot.data!.docs.map((doc) {
return Message.fromFirestore(doc);
}).toList();
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
return MessageTile(message: messages[index]);
},
);
},
)
Cloud Storage
Upload Files
React Native:
import storage from '@react-native-firebase/storage';
const uploadImage = async (userId, uri) => {
const filename = `users/${userId}/profile.jpg`;
const reference = storage().ref(filename);
// Upload file
const task = reference.putFile(uri);
// Monitor progress
task.on('state_changed', snapshot => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log(`Upload is ${progress}% done`);
});
// Wait for completion
await task;
// Get download URL
const downloadURL = await reference.getDownloadURL();
return downloadURL;
};
// Upload from base64
const uploadBase64 = async (path, base64String) => {
const reference = storage().ref(path);
await reference.putString(base64String, 'base64');
return await reference.getDownloadURL();
};
Flutter:
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';
class StorageService {
final FirebaseStorage _storage = FirebaseStorage.instance;
Future<String> uploadImage(String userId, File file) async {
final ref = _storage.ref('users/$userId/profile.jpg');
final uploadTask = ref.putFile(file);
// Monitor progress
uploadTask.snapshotEvents.listen((event) {
final progress = event.bytesTransferred / event.totalBytes;
print('Upload progress: ${(progress * 100).toStringAsFixed(0)}%');
});
// Wait for completion
await uploadTask;
// Get download URL
return await ref.getDownloadURL();
}
Future<void> deleteImage(String path) async {
await _storage.ref(path).delete();
}
}
Cloud Messaging (Push Notifications)
Setup and Token
React Native:
import messaging from '@react-native-firebase/messaging';
// Request permission (iOS)
const requestPermission = async () => {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Permission granted');
}
};
// Get FCM token
const getFCMToken = async () => {
const token = await messaging().getToken();
console.log('FCM Token:', token);
// Save to your backend
return token;
};
// Listen for token refresh
messaging().onTokenRefresh(token => {
console.log('Token refreshed:', token);
// Update on your backend
});
Handle Notifications
// Foreground messages
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('Foreground message:', remoteMessage);
// Show local notification
Alert.alert(
remoteMessage.notification?.title || 'Notification',
remoteMessage.notification?.body
);
});
return unsubscribe;
}, []);
// Background/quit messages
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Background message:', remoteMessage);
});
// Notification opened app
messaging().onNotificationOpenedApp(remoteMessage => {
console.log('Notification opened app:', remoteMessage);
// Navigate to specific screen
});
// App opened from quit state
messaging()
.getInitialNotification()
.then(remoteMessage => {
if (remoteMessage) {
console.log('App opened from quit:', remoteMessage);
// Navigate to specific screen
}
});
Flutter:
import 'package:firebase_messaging/firebase_messaging.dart';
class NotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
Future<void> initialize() async {
// Request permission
final settings = await _messaging.requestPermission();
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// Get token
final token = await _messaging.getToken();
print('FCM Token: $token');
// Listen for token refresh
_messaging.onTokenRefresh.listen((token) {
print('Token refreshed: $token');
});
// Foreground messages
FirebaseMessaging.onMessage.listen((message) {
print('Foreground message: ${message.notification?.title}');
});
// Background/terminated messages
FirebaseMessaging.onMessageOpenedApp.listen((message) {
print('Notification opened app: ${message.data}');
});
}
}
}
Firebase Analytics
// React Native
import analytics from '@react-native-firebase/analytics';
// Log event
await analytics().logEvent('purchase', {
item_id: 'SKU_123',
item_name: 'Product Name',
value: 29.99,
currency: 'USD',
});
// Log screen view
await analytics().logScreenView({
screen_name: 'Home',
screen_class: 'HomeScreen',
});
// Set user property
await analytics().setUserProperty('user_type', 'premium');
// Set user ID
await analytics().setUserId('user_123');
Flutter:
import 'package:firebase_analytics/firebase_analytics.dart';
final analytics = FirebaseAnalytics.instance;
// Log event
await analytics.logEvent(
name: 'purchase',
parameters: {
'item_id': 'SKU_123',
'item_name': 'Product Name',
'value': 29.99,
'currency': 'USD',
},
);
// Log screen view
await analytics.logScreenView(
screenName: 'Home',
screenClass: 'HomeScreen',
);
Security Rules
Firestore Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only read/write their own data
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// Posts - anyone can read, only author can write
match /posts/{postId} {
allow read: if true;
allow create: if request.auth != null;
allow update, delete: if request.auth != null
&& request.auth.uid == resource.data.authorId;
}
// Messages - only participants can read/write
match /chats/{chatId}/messages/{messageId} {
allow read, write: if request.auth != null
&& request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.participants;
}
}
}
Storage Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// User profile images
match /users/{userId}/profile.jpg {
allow read: if true;
allow write: if request.auth != null
&& request.auth.uid == userId
&& request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
// Chat attachments
match /chats/{chatId}/{fileName} {
allow read, write: if request.auth != null;
}
}
}
Best Practices
1. Offline Persistence
// Enable offline persistence (React Native - enabled by default)
firestore().settings({
persistence: true,
cacheSizeBytes: firestore.CACHE_SIZE_UNLIMITED,
});
// Flutter
FirebaseFirestore.instance.settings = Settings(
persistenceEnabled: true,
cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);
2. Batch Operations
// Batch writes
const batch = firestore().batch();
batch.set(userRef, userData);
batch.update(statsRef, { count: firestore.FieldValue.increment(1) });
batch.delete(oldRef);
await batch.commit();
3. Transactions
// Atomic transaction
await firestore().runTransaction(async transaction => {
const doc = await transaction.get(accountRef);
const newBalance = doc.data().balance - amount;
if (newBalance < 0) {
throw new Error('Insufficient funds');
}
transaction.update(accountRef, { balance: newBalance });
});
Conclusion
Firebase provides a powerful, scalable backend for mobile apps. Start with Authentication and Firestore, then add other services as needed. The real-time capabilities and generous free tier make it ideal for most mobile applications.
Need help integrating Firebase? Contact Hevcode for professional mobile app development with Firebase backend.