$30 off During Our Annual Pro Sale. View Details »

🍏 + 🔥 = ❤️ (Firebase for Apple Developers)

🍏 + 🔥 = ❤️ (Firebase for Apple Developers)

In this talk, I am going to walk you through the building blocks for creating modern applications on Apple’s platforms using SwiftUI and Firebase.

We will cover the following topics:

- Setting up an architecture for data-driven applications
- What’s new in SwiftUI’s application life cycle
- Syncing data in realtime across multiple devices with Cloud Firestore
- Mapping Firestore documents in a type-safe way using the Codable protocol
- Using Combine to fetch data from remote APIs and Firebase
- How async/await (new in Swift 5.5) will affect the way we write asynchronous code (plus some surprises in Firebase's code base)

Firebase is always evolving, so I will also talk about how we’re making use of Apple’s latest technologies in our own code base, for example

- Combine
- async/await
- SwiftUI view modifiers

No matter if you’re a seasoned Firebase user or just wondering what it is all about, you should leave this session with a deeper understanding of what Firebase it and how you can use it your apps on Apple’s platforms.

Peter Friese

May 20, 2021
Tweet

More Decks by Peter Friese

Other Decks in Technology

Transcript

  1. Peter Friese | Firebase Developer Advocate | @pete rf riese

     + Firebase for Apple Developers
  2. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  3. Architecture For Data-Driven Apps Photo by Lance Anderson on Unsplash

  4. #one-book-a-week-challenge

  5. None
  6. Drill-down navigation Three-column layout (iPad / Mac) Single source of

    truth Driving factors UI always in sync
  7. UI always in sync

  8. BookShelfView BookEditView BookDetailsView BookRowView Source of Truth books: [Book] @ObservableObject

    @Binding Book Book Book
  9. Challenge #1 This needs to be a binding But this

    isn’t a list of bindings
  10. struct BookShelfView: View { @Binding var bookShelf: BookShelf var body:

    some View { List { ForEach(Array(bookShelf.books.enumerated()), id: \.element.id) { index, item in BookRowView(book: $bookShelf.books[index]) } .onDelete { indexSet in bookShelf.books.remove(atOffsets: indexSet) } } .navigationTitle(bookShelf.title) } } Solution #1: iterate over enumerated items 🤔
  11. struct BookShelfView: View { @Binding var bookShelf: BookShelf var body:

    some View { List { ForEach($bookShelf.books) { $book in BookRowView(book: $book) } .onDelete { indexSet in bookShelf.books.remove(atOffsets: indexSet) } } .navigationTitle(bookShelf.title) } } Solution #1: use list bindings Everything is now bindable
  12. Learn more h tt ps://pete rf riese.dev/swi ft ui-list-item-bindings-behind-the-scenes/

  13. How to update only when the user commits? Challenge #2

  14. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  15. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  16. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  17. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  18. Solution 2

  19. class BookEditViewModel: ObservableObject { @Published var book: Book @Published var

    isISBNValid: Bool = true init(book: Book) { self.book = book self.$book .map { checkISBN(isbn: $0.isbn) } .assign(to: &$isISBNValid) } } Solution 2: use inner @ObservableObject Bonus: use Combine to perform validation
  20. Learn more about Building SwiftUI Components h tt ps://www.youtube.com/c/pete rf

    riese
  21. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  22. None
  23. Run with confidence Engage users Develop apps faster

  24. Run with confidence Crashlytics Performance Monitoring Test Lab App Distribution

    Engage users Analytics Predictions Cloud Messaging Remote Config A/B Testing Dynamic Links In-app Messaging Develop apps faster Auth Cloud Functions Cloud Firestore Hosting ML Kit Realtime Database Cloud Storage bit.ly/what-is-firebase Extensions Machine Learning
  25. None
  26. None
  27. None
  28. #protip: Put it beneath Asset.xcasset

  29. Don’t forget to add to all the targets!

  30. Swift Package Manager now officially supported! h tt ps://github.com/ fi

    rebase/ fi rebase-ios-sdk
  31. 🤔

  32. Application Lifecycle SwiftUI 2 Photo by Thor Alvis on Unsplash

  33. SwiftUI 2: No more AppDelegate! import SwiftUI @main struct BookShelfApp:

    App { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") } } } }
  34. SwiftUI 2: No more AppDelegate! import SwiftUI @main struct BookShelfApp:

    App { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") } } } }
  35. Solution 1: use initialiser import SwiftUI @main struct BookShelfApp: App

    { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") }
  36. Solution 1: use initialiser import SwiftUI @main struct BookShelfApp: App

    { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") } init() { FirebaseApp.configure() }
  37. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  38. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  39. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  40. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  41. Watch the scene phase Handle deep links Continue user activities

    Do more with the new life cycle
  42. Watch the scene phase Handle deep links Continue user activities

    Learn more pete rf riese.dev/ultimate-guide-to-swi ft ui2-application-lifecycle/
  43. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  44. Firestore

  45. None
  46. None
  47. One-Time Fetches Offline Mode Effortless Syncing

  48. bird_type: airspeed: coconut_capacity: isNative: icon: vector: distances_traveled: "swallow" 42.733 0.62

    false <binary data> { x: 36.4255, y: 25.1442, z: 18.8816 } [42, 39, 12, 42] Document
  49. Collection Sub-Collection

  50. Top level collections “books” collection a single book document

  51. struct Book: Identifiable { var id = UUID().uuidString var shelfId:

    String? var userId: String? var title: String var author: String var isbn: String var pages: Int var isRead: Bool = false var coverEditionKey: String? } Data Model
  52. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  53. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  54. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  55. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  56. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  57. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  58. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } Can we do better?
  59. (Yes, we can)

  60. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  61. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { } } } Mapping data using Codable if let document = document { do { self.book = try document.data(as: Book.self) } catch { print(error) } }
  62. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { } } } Mapping data using Codable if let document = document { do { self.book = try document.data(as: Book.self) } catch { print(error) } }
  63. BookShelfView BookEditView BookDetailsView BookRowView Source of Truth books: [Book] @ObservableObject

    @Binding Book Book Book Review: Architecture
  64. BookShelfView BookDetailsView BookRowView Source of Truth books: [Book] @ObservableObject @Binding

    Book Book Review: Architecture Snapshot Listener
  65. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  66. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  67. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  68. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  69. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  70. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  71. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  72. None
  73. Learn more h tt ps://pete rf riese.dev/ fi restore-codable-the-comprehensive-guide/

  74. import Foundation import Combine import Firebase import FirebaseFirestoreSwift import os

    class BookStore: ObservableObject { // MARK: - Dependencies var db = Firestore.firestore() // MARK: - Publishers @Published var user: User? @Published var books = [Book]() // MARK: - Private attributes @Published private var userId: String = "unknown" private var listenerRegistration: ListenerRegistration? private var cancellables = Set<AnyCancellable>() Fetching a collection of documents
  75. // A Book value could not be initialized from the

    DocumentSnapshot. switch error { case DecodingError.typeMismatch(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.valueNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.keyNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.dataCorrupted(let key): self ? . logger.debug("\(error.localizedDescription): \ (key.debugDescription)") default: self ? . logger.debug("Error decoding document: \ (error.localizedDescription)") } return nil } } } } Fetching a collection of documents
  76. // A Book value could not be initialized from the

    DocumentSnapshot. switch error { case DecodingError.typeMismatch(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.valueNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.keyNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.dataCorrupted(let key): self ? . logger.debug("\(error.localizedDescription): \ (key.debugDescription)") default: self ? . logger.debug("Error decoding document: \ (error.localizedDescription)") } return nil } } } } Fetching a collection of documents about 100 lines of code
  77. struct BookShelfView: View { @FirestoreQuery( collectionPath: "books", predicates: [ .where("userId",

    isEqualTo: userId), ] ) var books: Result<[Book], Error> @State var userId = "F18EBA5E" var body: some View { List(books) { book in Text(book.title) } } } Firestore Property Wrapper Firebase 8.9.0 @FloWritesCode @mo rt enditlevsen Thanks to
  78. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  79. Authentication Photo by Conve rt Kit on Unsplash

  80. Authentication Photo by Eduardo Soares on Unsplash

  81. Authentication Photo by Victor Freitas on Unsplash

  82. 

  83. Sign in the user Update the data model Secure users’

    data How to implement Firebase Authentication?
  84. Anonymous Authentication “Guest” accounts, rather

  85. func signIn() { registerStateListener() if Auth.auth().currentUser == nil { Auth.auth().signInAnonymously()

    } } Anonymous Authentication
  86. None
  87. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  88. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  89. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  90. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  91. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple Firebase SDK
  92. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple Firebase SDK
  93. All books are stored in one single collection Which user

    do they belong to?
  94. let query = db.collection("books") .whereField("userId", isEqualTo: self.userId) query .addSnapshotListener {

    [weak self] (querySnapsho guard let documents = querySnapshot ?. documents els Signed in user
  95. rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match

    /{document=**} { allow create: if request.auth != null; allow read, update, delete: if request.auth != null && resource.data.userId = = request.auth.uid; } } } Security Rules Only signed-in users can create new documents Only owners may read and modify a document
  96. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  97. Combine

  98. auth ?. signInAnonymously() let user = auth ?. currentUser print("User

    signed in with user ID: \(user ? . uid)") This might be nil
  99. auth ?. signInAnonymously() let user = auth ?. currentUser print("User

    signed in with user ID: \(user ? . uid)") auth ?. signInAnonymously { result, error in guard let result = result else { return } print("User signed in with user ID: \(result.user.uid)") } Do this instead
  100. auth . .. auth ?. signInAnonymously() .map{ $0.user } .replaceError(with:

    nil) .assign(to: &$user) Even better
  101. Launching with Firebase 8.9.0 h tt ps://github.com/ fi rebase/ fi

    rebase-ios-sdk/projects/3
  102. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  103. async/await Photo by Stephen H on Unsplash

  104. extension ArticleAnalyser { func process(url: String, completion: @escaping (Article) ->

    Void) { self.fetchArticle(from: url) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let html): self.extractTitle(from: html) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let title): self.extractText(from: html) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let text): self.extractImage(from: url) { result in Problem: Callback Pyramid of Doom
  105. extension AsyncArticleAnalyser { func process(url: String) async throws -> Article

    { let htmlText = try await fetchArticle(from: url) let text = try await extractText(from: htmlText) let title = try await extractTitle(from: htmlText) let imageUrl = try await extractImage(from: url) let tags = await inferTags(from: text) return Article(url: url, title: title, tags: tags, imageUrlString: imageUrl) } } Solution: Use async/await Swift 5.5
  106. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  107. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  108. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  109. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  110. auth ?. signInAnonymously { result, error in guard let result

    = result else { return } print("User signed in with user ID: \(result.user.uid)") } do { let result = try await Auth.auth().signIn(withEmail: email, password: password) print("User signed in with user ID: \(result.user.uid)") } catch { print(error) } Works with Firebase, too! Callback-style
  111. Learn more h tt ps://pete rf riese.dev/async-await-in-swi f h tt

    ps://www.youtube.com/watch?v=sEKw2BMcQtQ
  112. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  113. Architecture Swi ft UI 2 Life Cycle Firebase Firestore Authentication

    Combine async/await Completion block
  114. Register now - it’s free! h tt ps:// fi rebase.google.com/summit

  115. Thanks! Peter Friese h tt p://pete rf riese.dev @pete rf

    riese 
 youtube.com/c/PeterFriese/ Follow me h tt ps://github.com/pete rf riese/BookShelf Code
  116. Credits fi nish by Megan Chown from the Noun Project

    Time by Nikita Kozin from the Noun Project pipe by Komkrit Noenpoempisut from the Noun Project Passpo rt by ProSymbols from the Noun Project spiral by Alexander Skowalsky from the Noun Project Architecture by Ervin Bolat from the Noun Project Firebase logos cou rt esy h tt ps:// fi rebase.google.com/brand-guidelines Firebase logos cou rt esy h tt ps:// fi rebase.google.com/brand-guidelines Thanks! Hea rt by Roman from the Noun Project
  117. The End.