Skip to content

SwiftfulThinking/SwiftfulDataManagersFirebase

Repository files navigation

Firebase for SwiftfulDataManagers

Add Firebase Firestore support to a Swift application through SwiftfulDataManagers framework.

See documentation in the parent repo: https://github.com/SwiftfulThinking/SwiftfulDataManagers

Setup

dependencies: [
    .package(url: "https://github.com/SwiftfulThinking/SwiftfulDataManagersFirebase.git", branch: "main")
]
import SwiftfulDataManagers
import SwiftfulDataManagersFirebase

Example Configuration

// DocumentSyncEngine — static path
let userSyncEngine = DocumentSyncEngine<UserModel>(
    remote: FirebaseRemoteDocumentService(collectionPath: { "users" }),
    managerKey: "user",
    enableLocalPersistence: true,
    logger: logManager
)

// DocumentSyncEngine — dynamic path
let settingsSyncEngine = DocumentSyncEngine<UserSettings>(
    remote: FirebaseRemoteDocumentService(
        collectionPath: { [weak authManager] in
            guard let uid = authManager?.currentUserId else { return nil }
            return "users/\(uid)/settings"
        }
    ),
    managerKey: "settings"
)

// CollectionSyncEngine — static path
let productsSyncEngine = CollectionSyncEngine<Product>(
    remote: FirebaseRemoteCollectionService(collectionPath: { "products" }),
    managerKey: "products",
    enableLocalPersistence: true,
    logger: logManager
)

// CollectionSyncEngine — dynamic path
let watchlistSyncEngine = CollectionSyncEngine<WatchlistItem>(
    remote: FirebaseRemoteCollectionService(
        collectionPath: { [weak authManager] in
            guard let uid = authManager?.currentUserId else { return nil }
            return "users/\(uid)/watchlist"
        }
    ),
    managerKey: "watchlist"
)

Example Actions

// DocumentSyncEngine
try await userSyncEngine.startListening(documentId: "user_123")
try await userSyncEngine.saveDocument(user)
try await userSyncEngine.updateDocument(data: ["name": "John"])
let user = userSyncEngine.currentDocument
let user = try await userSyncEngine.getDocumentAsync()
userSyncEngine.stopListening()

// CollectionSyncEngine
await productsSyncEngine.startListening()
try await productsSyncEngine.saveDocument(product)
try await productsSyncEngine.updateDocument(id: "product_123", data: ["price": 29.99])
let products = productsSyncEngine.currentCollection
let product = productsSyncEngine.getDocument(id: "product_123")
let results = try await productsSyncEngine.getDocumentsAsync(buildQuery: { query in
    query.where("category", isEqualTo: "electronics")
})
productsSyncEngine.stopListening()

Dynamic Collection Paths

Details (Click to expand)

Firebase services use closures for collection paths, supporting both static and dynamic paths:

Static Paths

// Simple collection
FirebaseRemoteDocumentService<UserModel>(
    collectionPath: { "users" }
)
// Creates: users/{documentId}

// Nested collection with hardcoded IDs
FirebaseRemoteCollectionService<CommentModel>(
    collectionPath: { "posts/post123/comments" }
)
// Creates: posts/post123/comments/{documentId}

Dynamic Paths

// Path depends on runtime value (e.g., current user)
let watchlistSyncEngine = CollectionSyncEngine<WatchlistItem>(
    remote: FirebaseRemoteCollectionService(
        collectionPath: { [weak authManager] in
            guard let uid = authManager?.currentUserId else { return nil }
            return "users/\(uid)/watchlist"
        }
    ),
    managerKey: "watchlist"
)

// Multiple nesting levels
let repliesSyncEngine = CollectionSyncEngine<ReplyModel>(
    remote: FirebaseRemoteCollectionService(
        collectionPath: {
            guard let postId = currentPostId,
                  let commentId = currentCommentId else {
                return nil
            }
            return "posts/\(postId)/comments/\(commentId)/replies"
        }
    ),
    managerKey: "replies"
)

Use cases:

  • User-specific subcollections (favorites, settings, posts)
  • Hierarchical data structures (comments, replies)
  • Scoped collections per entity
  • Engine initialization before authentication

Error handling: When the closure returns nil, operations will throw FirebaseServiceError.collectionPathNotAvailable. This allows engines to be created before the path is available (e.g., before login), and operations will automatically fail with a clear error until the path becomes available.

Firebase Firestore Setup

Details (Click to expand)

Firebase docs: https://firebase.google.com/docs/firestore

1. Enable Firestore in Firebase console

  • Firebase Console -> Build -> Firestore Database -> Create Database

2. Set Security Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow authenticated users to read/write their own documents
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }

    // Allow authenticated users to read all products, write if admin
    match /products/{document=**} {
      allow read: if request.auth != null;
      allow write: if request.auth != null && request.auth.token.admin == true;
    }

    // Add more rules as needed
  }
}

3. Add Firebase SDK to your project

dependencies: [
    .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.0.0"),
    .package(url: "https://github.com/SwiftfulThinking/SwiftfulDataManagersFirebase.git", branch: "main")
]

4. Initialize Firebase in your app

import Firebase

// In App init or AppDelegate
FirebaseApp.configure()

Streaming Updates Pattern

Details (Click to expand)

Document Streaming

FirebaseRemoteDocumentService provides real-time document updates:

func streamDocument(id: String) -> AsyncThrowingStream<T?, Error>

Collection Streaming

FirebaseRemoteCollectionService follows a hybrid pattern used by CollectionSyncEngine:

// 1. Bulk load all documents first
let collection = try await service.getCollection()

// 2. Stream individual updates/deletions
func streamCollectionUpdates() -> (
    updates: AsyncThrowingStream<T, Error>,
    deletions: AsyncThrowingStream<String, Error>
)

This pattern prevents unnecessary full collection re-fetches and efficiently handles individual document changes.

Parent Repo

Full documentation and examples: https://github.com/SwiftfulThinking/SwiftfulDataManagers

About

Firebase for SwiftfulDataManagers

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages