Cloud Firestore Data Recording Process
Complete guide to writing and reading user data to Cloud Firestore database with Firebase integration
Swift Tutorial #8 — Cloud Firestore Data Recording Process
Note:
This tutorial covers writing and reading user data to Cloud Firestore database. Cloud Firestore is a NoSQL document database that provides real-time data synchronization and offline support.
Overview
The Cloud Firestore service provided by Firebase quickly performs writing and reading operations of user data to the database.
In the code block I will show as an example, we will ensure that the user logged in with Firebase Auth creates a collection in the database containing their email, the URL of the profile photo file they selected in Firebase Storage, the text they entered in the TextField, and the timestamp.
Reference:
- Swift — CocoaPods — Firebase Integration
- Firebase User Registration
- Firebase User Login
- Firebase User Redirect
- Profile Photo Selection
- Firebase Storage Upload
- UIAlert Action Function
Step 1: Firebase Console Setup
Go to the Firebase console and activate Cloud Firestore. In the options it suggests, while read and write operations cannot be performed in production mode, the rule you set for reading and writing operations will be valid until the date you specify in test mode.
Step 2: Creating Data Directory Variable
First, let's assign a variable to use Cloud Firestore.
let firestoreDatabase = Firestore.firestore()
Let's create an array to add the data we want to add. In the next stage, we will show this array as data. Since this data array is requested to be in String: Any format, we add as [String: Any] at the end.
let firestoreDatabase = Firestore.firestore()
// Create data dictionary
let dataDictionary = [
"userEmail": Auth.auth().currentUser?.email ?? "",
"profileImageURL": "https://firebasestorage.googleapis.com/...",
"userText": textField.text ?? "",
"timestamp": FieldValue.serverTimestamp()
] as [String: Any]
Step 3: Adding Data to Cloud Firestore Collection
We can perform the data recording operation by showing the collection name and data with collection.addDocument in the database variable.
firestoreDatabase.collection("users").addDocument(data: dataDictionary) { error in
if let error = error {
print("Error adding document: \(error)")
} else {
print("Document added successfully")
}
}
Step 4: Complete Implementation
Here's a complete implementation for Cloud Firestore data recording:
import UIKit
import Firebase
import FirebaseFirestore
class FirestoreViewController: UIViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var profileImageView: UIImageView!
@IBOutlet weak var saveButton: UIButton!
private let firestoreDatabase = Firestore.firestore()
private var profileImageURL: String?
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
loadUserData()
}
private func setupUI() {
saveButton.setTitle("Save to Firestore", for: .normal)
saveButton.backgroundColor = .systemBlue
saveButton.setTitleColor(.white, for: .normal)
saveButton.layer.cornerRadius = 8
// Configure text fields
nameTextField.placeholder = "Enter your name"
emailTextField.placeholder = "Enter your email"
// Load current user email
if let user = Auth.auth().currentUser {
emailTextField.text = user.email
}
}
@IBAction func saveButtonTapped(_ sender: Any) {
saveUserDataToFirestore()
}
private func saveUserDataToFirestore() {
guard let name = nameTextField.text, !name.isEmpty else {
showAlert(title: "Error", message: "Please enter your name")
return
}
guard let email = emailTextField.text, !email.isEmpty else {
showAlert(title: "Error", message: "Please enter your email")
return
}
// Show loading state
saveButton.isEnabled = false
saveButton.setTitle("Saving...", for: .normal)
// Create data dictionary
var dataDictionary: [String: Any] = [
"name": name,
"email": email,
"timestamp": FieldValue.serverTimestamp(),
"lastUpdated": FieldValue.serverTimestamp()
]
// Add profile image URL if available
if let profileImageURL = profileImageURL {
dataDictionary["profileImageURL"] = profileImageURL
}
// Add user ID if available
if let userID = Auth.auth().currentUser?.uid {
dataDictionary["userID"] = userID
}
// Save to Firestore
firestoreDatabase.collection("users").addDocument(data: dataDictionary) { [weak self] error in
DispatchQueue.main.async {
self?.saveButton.isEnabled = true
self?.saveButton.setTitle("Save to Firestore", for: .normal)
if let error = error {
self?.showAlert(title: "Error", message: "Failed to save data: \(error.localizedDescription)")
} else {
self?.showAlert(title: "Success", message: "Data saved to Firestore successfully!")
}
}
}
}
private func loadUserData() {
// Load existing user data if available
if let userID = Auth.auth().currentUser?.uid {
firestoreDatabase.collection("users")
.whereField("userID", isEqualTo: userID)
.order(by: "timestamp", descending: true)
.limit(to: 1)
.getDocuments { [weak self] snapshot, error in
if let error = error {
print("Error loading user data: \(error)")
return
}
if let document = snapshot?.documents.first {
let data = document.data()
DispatchQueue.main.async {
self?.nameTextField.text = data["name"] as? String
self?.emailTextField.text = data["email"] as? String
if let imageURL = data["profileImageURL"] as? String {
self?.loadProfileImage(from: imageURL)
}
}
}
}
}
}
private func loadProfileImage(from url: String) {
// Load profile image from URL
// This would typically use a library like SDWebImage or Kingfisher
print("Loading profile image from: \(url)")
}
private func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
}
}
Step 5: Advanced Firestore Features
Real-time Data Synchronization
private func setupRealTimeListener() {
guard let userID = Auth.auth().currentUser?.uid else { return }
firestoreDatabase.collection("users")
.whereField("userID", isEqualTo: userID)
.addSnapshotListener { [weak self] snapshot, error in
if let error = error {
print("Error listening for updates: \(error)")
return
}
guard let documents = snapshot?.documents else {
print("No documents found")
return
}
for document in documents {
let data = document.data()
print("Document updated: \(document.documentID)")
// Handle real-time updates
self?.updateUI(with: data)
}
}
}
private func updateUI(with data: [String: Any]) {
DispatchQueue.main.async {
self.nameTextField.text = data["name"] as? String
self.emailTextField.text = data["email"] as? String
}
}
Batch Operations
private func performBatchOperations() {
let batch = firestoreDatabase.batch()
// Add multiple documents
let userData1 = ["name": "John", "email": "john@example.com"]
let userData2 = ["name": "Jane", "email": "jane@example.com"]
let doc1Ref = firestoreDatabase.collection("users").document()
let doc2Ref = firestoreDatabase.collection("users").document()
batch.setData(userData1, forDocument: doc1Ref)
batch.setData(userData2, forDocument: doc2Ref)
batch.commit { error in
if let error = error {
print("Error performing batch operation: \(error)")
} else {
print("Batch operation completed successfully")
}
}
}
Querying Data
private func queryUserData() {
// Query by email
firestoreDatabase.collection("users")
.whereField("email", isEqualTo: "user@example.com")
.getDocuments { snapshot, error in
if let error = error {
print("Error querying data: \(error)")
return
}
for document in snapshot?.documents ?? [] {
print("User: \(document.data())")
}
}
// Query with multiple conditions
firestoreDatabase.collection("users")
.whereField("name", isGreaterThan: "A")
.whereField("timestamp", isGreaterThan: Date().addingTimeInterval(-86400)) // Last 24 hours
.order(by: "timestamp", descending: true)
.limit(to: 10)
.getDocuments { snapshot, error in
// Handle query results
}
}
Step 6: Data Models
User Model
struct User {
let id: String
let name: String
let email: String
let profileImageURL: String?
let timestamp: Date
let lastUpdated: Date
init(id: String, name: String, email: String, profileImageURL: String? = nil, timestamp: Date = Date(), lastUpdated: Date = Date()) {
self.id = id
self.name = name
self.email = email
self.profileImageURL = profileImageURL
self.timestamp = timestamp
self.lastUpdated = lastUpdated
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = [
"name": name,
"email": email,
"timestamp": timestamp,
"lastUpdated": lastUpdated
]
if let profileImageURL = profileImageURL {
dict["profileImageURL"] = profileImageURL
}
return dict
}
static func fromDocument(_ document: QueryDocumentSnapshot) -> User? {
let data = document.data()
guard let name = data["name"] as? String,
let email = data["email"] as? String else {
return nil
}
let profileImageURL = data["profileImageURL"] as? String
let timestamp = (data["timestamp"] as? Timestamp)?.dateValue() ?? Date()
let lastUpdated = (data["lastUpdated"] as? Timestamp)?.dateValue() ?? Date()
return User(
id: document.documentID,
name: name,
email: email,
profileImageURL: profileImageURL,
timestamp: timestamp,
lastUpdated: lastUpdated
)
}
}
Step 7: Error Handling
Comprehensive Error Handling
enum FirestoreError: Error {
case noUserAuthenticated
case invalidData
case networkError(Error)
case permissionDenied
case documentNotFound
var localizedDescription: String {
switch self {
case .noUserAuthenticated:
return "No user is currently authenticated"
case .invalidData:
return "Invalid data provided"
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .permissionDenied:
return "Permission denied to access Firestore"
case .documentNotFound:
return "Document not found"
}
}
}
private func handleFirestoreError(_ error: Error) {
let firestoreError: FirestoreError
if let error = error as? FirestoreError {
firestoreError = error
} else {
firestoreError = .networkError(error)
}
showAlert(title: "Firestore Error", message: firestoreError.localizedDescription)
}
Step 8: Security Rules
Firestore Security Rules
Configure your Firestore security rules in the Firebase Console:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to read and write their own data
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Allow authenticated users to create documents in users collection
match /users/{document=**} {
allow create: if request.auth != null;
allow read, update, delete: if request.auth != null &&
request.auth.uid == resource.data.userID;
}
// Public read access for certain collections
match /public/{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
Note:
Cloud Firestore is a powerful NoSQL database that provides real-time synchronization and offline support. Remember to implement proper security rules, handle errors gracefully, and optimize for performance when working with large datasets.