SlideShare ist ein Scribd-Unternehmen logo
1 von 140
Downloaden Sie, um offline zu lesen
Peter Friese | Developer Advocate, Firebase
Charlo!e Liang | So"ware Engineer, Firebase
 +
Firebase for Apple Developers
@charlo!eCLiang @pete#riese
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Architecture
For Data-Driven Apps
Photo by Lance Anderson on Unsplash
#one-book-a-week-challenge
Drill-down navigation

Three-column layout (iPad / Mac)

Single source of truth

Driving factors
UI always in sync
UI always in sync
BookShelfView
BookEditView
BookDetailsView
BookRowView
Source of Truth
books: [Book]
@ObservableObject
@Binding
Book
Book
Book
Challenge #1
This needs to be a binding
But this isn’t a list of bindings
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
!
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
Learn more
h"ps://pete#riese.dev/swi!ui-list-item-bindings-behind-the-scenes/
How to update only
when the user commits?
Challenge #2
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
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
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
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
Solution 2
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
Learn more about
Building SwiftUI Components
h"ps://www.youtube.com/c/pete#riese
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Run with confidence Engage users
Develop apps faster
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
#protip: Put it
beneath Asset.xcasset
Don’t forget to add
to all the targets!
Swift Package Manager
now officially supported!
h"ps://github.com/%rebase/%rebase-ios-sdk
!
Application Lifecycle
SwiftUI 2
Photo by Thor Alvis on Unsplash
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")
}
}
}
}
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")
}
}
}
}
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")
}
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()
}
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
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
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
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
Watch the scene phase

Handle deep links

Continue user activities
Do more with the new life cycle
Watch the scene phase

Handle deep links

Continue user activities
Learn more
pete#riese.dev/ultimate-guide-to-swi!ui2-application-lifecycle/
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Firestore
One-Time Fetches
Offline Mode
Effortless Syncing
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
Collection
Sub-Collection
Top level
collections
“books” collection a single book document
struct Book: Identifiable {
var id = String?
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
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
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
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
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
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
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
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?
(Yes, we can)
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
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)
}
}
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)
}
}
BookShelfView
BookEditView
BookDetailsView
BookRowView
Source of Truth
books: [Book]
@ObservableObject
@Binding
Book
Book
Book
Review: Architecture
BookShelfView
BookDetailsView
BookRowView
Source of Truth
books: [Book]
@ObservableObject
@Binding
Book
Book
Review: Architecture
Snapshot Listener
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
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
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
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
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
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
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
Learn more
h"ps://pete#riese.dev/%restore-codable-the-comprehensive-guide/
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
!% 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
!% 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
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&enditlevsen
Thanks to
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Driving User Engagement
Photo by ROBIN WORRALL on Unsplash
Driving User Engagement
Noti%cations Share Extensions WidgetKit
Notification Service Extension
Noti%cation Service Extension + Firebase Messaging
Be!er visualisation of noti%cations

Available in watchOS as well
Watch the scene phase

Handle deep links

Continue user activities
Learn more
h"ps://%rebase.blog/posts/2019/09/fcm-image-noti%cation
Firebase + WidgetKit
Mini travel blog

How to initialise Firebase

How to access Firebase services
Post
Image
ShareExtension Widget
iOS App
Architecture
class ShareViewController: SLComposeServiceViewController {
override func didSelectPost() {
!" get data from share controller context
Task {
!" write image data to Cloud Storage
let _ = try await ref.putDataAsync(self.imageData as Data)
let url = try await ref.downloadURL()
!" write post data to Firestore
let post = Post(description: description !# "", url: url.absoluteString)
try db.collection("Posts").document("post").setData(from: post)
!" refresh widgets and return to share controller
WidgetCenter.shared.reloadAllTimelines()
extensionContext!$completeRequest(returningItems: [])
}
}
Firebase in a ShareViewController
class ShareViewController: SLComposeServiceViewController {
override func didSelectPost() {
!" get data from share controller context
Task {
!" write image data to Cloud Storage
let _ = try await ref.putDataAsync(self.imageData as Data)
let url = try await ref.downloadURL()
!" write post data to Firestore
let post = Post(description: description !# "", url: url.absoluteString)
try db.collection("Posts").document("post").setData(from: post)
!" refresh widgets and return to share controller
WidgetCenter.shared.reloadAllTimelines()
extensionContext!$completeRequest(returningItems: [])
}
}
Firebase in a ShareViewController
class ShareViewController: SLComposeServiceViewController {
override func didSelectPost() {
!" get data from share controller context
Task {
!" write image data to Cloud Storage
let _ = try await ref.putDataAsync(self.imageData as Data)
let url = try await ref.downloadURL()
!" write post data to Firestore
let post = Post(description: description !# "", url: url.absoluteString)
try db.collection("Posts").document("post").setData(from: post)
!" refresh widgets and return to share controller
WidgetCenter.shared.reloadAllTimelines()
extensionContext!$completeRequest(returningItems: [])
}
}
Firebase in a ShareViewController
class ShareViewController: SLComposeServiceViewController {
override func didSelectPost() {
!" get data from share controller context
Task {
!" write image data to Cloud Storage
let _ = try await ref.putDataAsync(self.imageData as Data)
let url = try await ref.downloadURL()
!" write post data to Firestore
let post = Post(description: description !# "", url: url.absoluteString)
try db.collection("Posts").document("post").setData(from: post)
!" refresh widgets and return to share controller
WidgetCenter.shared.reloadAllTimelines()
extensionContext!$completeRequest(returningItems: [])
}
}
Firebase in a ShareViewController
class ShareViewController: SLComposeServiceViewController {
override func didSelectPost() {
!" get data from share controller context
Task {
!" write image data to Cloud Storage
let _ = try await ref.putDataAsync(self.imageData as Data)
let url = try await ref.downloadURL()
!" write post data to Firestore
let post = Post(description: description !# "", url: url.absoluteString)
try db.collection("Posts").document("post").setData(from: post)
!" refresh widgets and return to share controller
WidgetCenter.shared.reloadAllTimelines()
extensionContext!$completeRequest(returningItems: [])
}
}
Firebase in a ShareViewController
class ShareViewController: SLComposeServiceViewController {
override func didSelectPost() {
!" get data from share controller context
Task {
!" write image data to Cloud Storage
let _ = try await ref.putDataAsync(self.imageData as Data)
let url = try await ref.downloadURL()
!" write post data to Firestore
let post = Post(description: description !# "", url: url.absoluteString)
try db.collection("Posts").document("post").setData(from: post)
!" refresh widgets and return to share controller
WidgetCenter.shared.reloadAllTimelines()
extensionContext!$completeRequest(returningItems: [])
}
}
Firebase in a ShareViewController
Post
Image
ShareExtension Widget
iOS App
Architecture
Post
Image
func getTimeline(for configuration: ConfigurationIntent, in context: Context,
completion: @escaping (Timeline<Entry>) !" Void) {
if FirebaseApp.app() !& nil { FirebaseApp.configure() }
Task {
let (image, text) = try await PostRepository.getPost(imageName: “image.png")
let entry = SimpleEntry(
date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!,
configuration: configuration,
image: image,
text: text
)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Firebase in a Widget
func getTimeline(for configuration: ConfigurationIntent, in context: Context,
completion: @escaping (Timeline<Entry>) !" Void) {
if FirebaseApp.app() !& nil { FirebaseApp.configure() }
Task {
let (image, text) = try await PostRepository.getPost(imageName: “image.png")
let entry = SimpleEntry(
date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!,
configuration: configuration,
image: image,
text: text
)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Firebase in a Widget
func getTimeline(for configuration: ConfigurationIntent, in context: Context,
completion: @escaping (Timeline<Entry>) !" Void) {
if FirebaseApp.app() !& nil { FirebaseApp.configure() }
Task {
let (image, text) = try await PostRepository.getPost(imageName: “image.png")
let entry = SimpleEntry(
date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!,
configuration: configuration,
image: image,
text: text
)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Firebase in a Widget
func getTimeline(for configuration: ConfigurationIntent, in context: Context,
completion: @escaping (Timeline<Entry>) !" Void) {
if FirebaseApp.app() !& nil { FirebaseApp.configure() }
Task {
let (image, text) = try await PostRepository.getPost(imageName: “image.png")
let entry = SimpleEntry(
date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!,
configuration: configuration,
image: image,
text: text
)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Firebase in a Widget
func getTimeline(for configuration: ConfigurationIntent, in context: Context,
completion: @escaping (Timeline<Entry>) !" Void) {
if FirebaseApp.app() !& nil { FirebaseApp.configure() }
Task {
let (image, text) = try await PostRepository.getPost(imageName: “image.png")
let entry = SimpleEntry(
date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!,
configuration: configuration,
image: image,
text: text
)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Firebase in a Widget
import FirebaseStorage
import FirebaseStorageSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
struct PostRepository {
static func getPost(imageName: String) async throws !" (UIImage, String) {
let ref = Storage.storage().reference().child(imageName)
let data = try await ref.data(maxSize: 20 * 1024 * 2048)
let image = UIImage(data: data)!
let db = Firestore.firestore()
let docRef = db.collection("Posts").document("post")
let document = try await docRef.getDocument()
let post = try document.data(as: Post.self)
return (image, post.description)
Firebase in a Widget
import FirebaseStorage
import FirebaseStorageSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
struct PostRepository {
static func getPost(imageName: String) async throws !" (UIImage, String) {
let ref = Storage.storage().reference().child(imageName)
let data = try await ref.data(maxSize: 20 * 1024 * 2048)
let image = UIImage(data: data)!
let db = Firestore.firestore()
let docRef = db.collection("Posts").document("post")
let document = try await docRef.getDocument()
let post = try document.data(as: Post.self)
return (image, post.description)
Firebase in a Widget
import FirebaseStorage
import FirebaseStorageSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
struct PostRepository {
static func getPost(imageName: String) async throws !" (UIImage, String) {
let ref = Storage.storage().reference().child(imageName)
let data = try await ref.data(maxSize: 20 * 1024 * 2048)
let image = UIImage(data: data)!
let db = Firestore.firestore()
let docRef = db.collection("Posts").document("post")
let document = try await docRef.getDocument()
let post = try document.data(as: Post.self)
return (image, post.description)
Firebase in a Widget
import FirebaseStorage
import FirebaseStorageSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
struct PostRepository {
static func getPost(imageName: String) async throws !" (UIImage, String) {
let ref = Storage.storage().reference().child(imageName)
let data = try await ref.data(maxSize: 20 * 1024 * 2048)
let image = UIImage(data: data)!
let db = Firestore.firestore()
let docRef = db.collection("Posts").document("post")
let document = try await docRef.getDocument()
let post = try document.data(as: Post.self)
return (image, post.description)
Firebase in a Widget
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Authentication
Photo by Conve&Kit on Unsplash
Authentication
Photo by Eduardo Soares on Unsplash
Authentication
Photo by Victor Freitas on Unsplash

Sign in the user

Update the data model

Secure users’ data
How to implement
Firebase Authentication?
Anonymous Authentication
“Guest” accounts, rather
func signIn() {
registerStateListener()
if Auth.auth().currentUser !& nil {
Auth.auth().signInAnonymously()
}
}
Anonymous Authentication
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
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
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
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
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
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
All books are stored in one single collection
Which user do
they belong to?
let query = db.collection("books")
.whereField("userId",
isEqualTo: self.userId)
query
.addSnapshotListener { [weak self] (querySnapsho
guard let documents = querySnapshot!$documents els
Signed in user
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
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Combine
auth!$signInAnonymously()
let user = auth!$currentUser
print("User signed in with user ID: (user!$uid)")
This might be nil
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
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)")
}
@Published var user: User?
!!'
auth!$signInAnonymously()
.map{ $0.user }
.replaceError(with: nil)
.assign(to: &$user)
Even better
Launched with Firebase 8.9.0
h"ps://github.com/%rebase/%rebase-ios-sdk/projects/3
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
async/await
Photo by Stephen H on Unsplash
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
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
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
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
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
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
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
Learn more
h"ps://pete#riese.dev/async-await-in-swi!
h"ps://www.youtube.com/watch?v=sEKw2BMcQtQ
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Architecture Swi!UI 2 Life Cycle
Firebase Firestore Authentication Combine async/await Completion block
WidgetKit
Register now - it’s free!
h"ps://io.google/2022/
Google I/O 2022
Thanks! @pete#riese
h"ps://github.com/pete#riese/BookShelf
Code
@charlo"eCLiang
Credits
%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& 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&esy h"ps://%rebase.google.com/brand-guidelines
Firebase logos cou&esy h"ps://%rebase.google.com/brand-guidelines
Thanks!
Hea& by Roman from the Noun Project
Dashboard by BomSymbols from the Noun Project
The End.

Weitere ähnliche Inhalte

Was ist angesagt?

iOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core DataiOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core DataChris Mar
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code OrganizationRebecca Murphey
 
Hibernate Tutorial for beginners
Hibernate Tutorial for beginnersHibernate Tutorial for beginners
Hibernate Tutorial for beginnersrajkamal560066
 
Rapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and FirebaseRapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and FirebasePeter Friese
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackNelson Glauber Leal
 
Advanced Django ORM techniques
Advanced Django ORM techniquesAdvanced Django ORM techniques
Advanced Django ORM techniquesDaniel Roseman
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using RoomNelson Glauber Leal
 
Property wrapper and how to use them with mvvm in swift ui i copy
Property wrapper and how to use them with mvvm in swift ui i copyProperty wrapper and how to use them with mvvm in swift ui i copy
Property wrapper and how to use them with mvvm in swift ui i copyWannitaTolaema
 
Drupal 7 Entity & Entity API
Drupal 7 Entity & Entity APIDrupal 7 Entity & Entity API
Drupal 7 Entity & Entity API均民 戴
 
jQuery Fundamentals
jQuery FundamentalsjQuery Fundamentals
jQuery FundamentalsGil Fink
 

Was ist angesagt? (20)

jQuery
jQueryjQuery
jQuery
 
iOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core DataiOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core Data
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code Organization
 
jQuery
jQueryjQuery
jQuery
 
Hibernate Tutorial for beginners
Hibernate Tutorial for beginnersHibernate Tutorial for beginners
Hibernate Tutorial for beginners
 
Rapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and FirebaseRapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and Firebase
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com Jetpack
 
Advanced Django ORM techniques
Advanced Django ORM techniquesAdvanced Django ORM techniques
Advanced Django ORM techniques
 
jQuery
jQueryjQuery
jQuery
 
Django Pro ORM
Django Pro ORMDjango Pro ORM
Django Pro ORM
 
BVJS
BVJSBVJS
BVJS
 
DOM and Events
DOM and EventsDOM and Events
DOM and Events
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
 
Property wrapper and how to use them with mvvm in swift ui i copy
Property wrapper and how to use them with mvvm in swift ui i copyProperty wrapper and how to use them with mvvm in swift ui i copy
Property wrapper and how to use them with mvvm in swift ui i copy
 
JQuery introduction
JQuery introductionJQuery introduction
JQuery introduction
 
jQuery Introduction
jQuery IntroductionjQuery Introduction
jQuery Introduction
 
J Query Public
J Query PublicJ Query Public
J Query Public
 
Drupal 7 Entity & Entity API
Drupal 7 Entity & Entity APIDrupal 7 Entity & Entity API
Drupal 7 Entity & Entity API
 
jQuery Fundamentals
jQuery FundamentalsjQuery Fundamentals
jQuery Fundamentals
 
J query1
J query1J query1
J query1
 

Ähnlich wie Firebase for Apple Developers - SwiftHeroes

Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackNelson Glauber Leal
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...Corley S.r.l.
 
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드영욱 김
 
Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackNelson Glauber Leal
 
813 LAB Library book sorting Note that only maincpp can .pdf
813 LAB Library book sorting Note that only maincpp can .pdf813 LAB Library book sorting Note that only maincpp can .pdf
813 LAB Library book sorting Note that only maincpp can .pdfsastaindin
 
Link.javaLink.javapackage com.bookstore.domain.model;import .docx
Link.javaLink.javapackage com.bookstore.domain.model;import .docxLink.javaLink.javapackage com.bookstore.domain.model;import .docx
Link.javaLink.javapackage com.bookstore.domain.model;import .docxSHIVA101531
 
properties-how-do-i - Transcript.pdf
properties-how-do-i - Transcript.pdfproperties-how-do-i - Transcript.pdf
properties-how-do-i - Transcript.pdfShaiAlmog1
 
Introduction to javascript and yoolkui
Introduction to javascript and yoolkuiIntroduction to javascript and yoolkui
Introduction to javascript and yoolkuiKhou Suylong
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneRafael Felix da Silva
 
Building Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsBuilding Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsPeter Friese
 
Simpler Core Data with RubyMotion
Simpler Core Data with RubyMotionSimpler Core Data with RubyMotion
Simpler Core Data with RubyMotionStefan Haflidason
 
cake phptutorial
cake phptutorialcake phptutorial
cake phptutorialice27
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design PatternsStefano Fago
 
Building DSLs with Groovy
Building DSLs with GroovyBuilding DSLs with Groovy
Building DSLs with GroovySten Anderson
 

Ähnlich wie Firebase for Apple Developers - SwiftHeroes (20)

Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...
 
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
 
Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
 
813 LAB Library book sorting Note that only maincpp can .pdf
813 LAB Library book sorting Note that only maincpp can .pdf813 LAB Library book sorting Note that only maincpp can .pdf
813 LAB Library book sorting Note that only maincpp can .pdf
 
EMF Tips n Tricks
EMF Tips n TricksEMF Tips n Tricks
EMF Tips n Tricks
 
Link.javaLink.javapackage com.bookstore.domain.model;import .docx
Link.javaLink.javapackage com.bookstore.domain.model;import .docxLink.javaLink.javapackage com.bookstore.domain.model;import .docx
Link.javaLink.javapackage com.bookstore.domain.model;import .docx
 
JNDI
JNDIJNDI
JNDI
 
properties-how-do-i - Transcript.pdf
properties-how-do-i - Transcript.pdfproperties-how-do-i - Transcript.pdf
properties-how-do-i - Transcript.pdf
 
Introduction to javascript and yoolkui
Introduction to javascript and yoolkuiIntroduction to javascript and yoolkui
Introduction to javascript and yoolkui
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com Backbone
 
Javascript.ppt
Javascript.pptJavascript.ppt
Javascript.ppt
 
Building Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsBuilding Reusable SwiftUI Components
Building Reusable SwiftUI Components
 
Simpler Core Data with RubyMotion
Simpler Core Data with RubyMotionSimpler Core Data with RubyMotion
Simpler Core Data with RubyMotion
 
cake phptutorial
cake phptutorialcake phptutorial
cake phptutorial
 
Scala on Your Phone
Scala on Your PhoneScala on Your Phone
Scala on Your Phone
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design Patterns
 
Java script
Java scriptJava script
Java script
 
Save Repository From Save
Save Repository From SaveSave Repository From Save
Save Repository From Save
 
Building DSLs with Groovy
Building DSLs with GroovyBuilding DSLs with Groovy
Building DSLs with Groovy
 

Mehr von Peter Friese

Building Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsBuilding Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsPeter Friese
 
Firebase & SwiftUI Workshop
Firebase & SwiftUI WorkshopFirebase & SwiftUI Workshop
Firebase & SwiftUI WorkshopPeter Friese
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in SwiftPeter Friese
 
Building Apps with SwiftUI and Firebase
Building Apps with SwiftUI and FirebaseBuilding Apps with SwiftUI and Firebase
Building Apps with SwiftUI and FirebasePeter Friese
 
Rapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and FirebaseRapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and FirebasePeter Friese
 
6 Things You Didn't Know About Firebase Auth
6 Things You Didn't Know About Firebase Auth6 Things You Didn't Know About Firebase Auth
6 Things You Didn't Know About Firebase AuthPeter Friese
 
Five Things You Didn't Know About Firebase Auth
Five Things You Didn't Know About Firebase AuthFive Things You Didn't Know About Firebase Auth
Five Things You Didn't Know About Firebase AuthPeter Friese
 
Building High-Quality Apps for Google Assistant
Building High-Quality Apps for Google AssistantBuilding High-Quality Apps for Google Assistant
Building High-Quality Apps for Google AssistantPeter Friese
 
Building Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on Google Building Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on Google Peter Friese
 
Building Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on GoogleBuilding Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on GooglePeter Friese
 
What's new in Android Wear 2.0
What's new in Android Wear 2.0What's new in Android Wear 2.0
What's new in Android Wear 2.0Peter Friese
 
Google Fit, Android Wear & Xamarin
Google Fit, Android Wear & XamarinGoogle Fit, Android Wear & Xamarin
Google Fit, Android Wear & XamarinPeter Friese
 
Introduction to Android Wear
Introduction to Android WearIntroduction to Android Wear
Introduction to Android WearPeter Friese
 
Google Play Services Rock
Google Play Services RockGoogle Play Services Rock
Google Play Services RockPeter Friese
 
Introduction to Android Wear
Introduction to Android WearIntroduction to Android Wear
Introduction to Android WearPeter Friese
 
Google+ for Mobile Apps on iOS and Android
Google+ for Mobile Apps on iOS and AndroidGoogle+ for Mobile Apps on iOS and Android
Google+ for Mobile Apps on iOS and AndroidPeter Friese
 
Cross-Platform Authentication with Google+ Sign-In
Cross-Platform Authentication with Google+ Sign-InCross-Platform Authentication with Google+ Sign-In
Cross-Platform Authentication with Google+ Sign-InPeter Friese
 
Bring Back the Fun to Testing Android Apps with Robolectric
Bring Back the Fun to Testing Android Apps with RobolectricBring Back the Fun to Testing Android Apps with Robolectric
Bring Back the Fun to Testing Android Apps with RobolectricPeter Friese
 
Do Androids Dream of Electric Sheep
Do Androids Dream of Electric SheepDo Androids Dream of Electric Sheep
Do Androids Dream of Electric SheepPeter Friese
 
Java based Cross-Platform Mobile Development
Java based Cross-Platform Mobile DevelopmentJava based Cross-Platform Mobile Development
Java based Cross-Platform Mobile DevelopmentPeter Friese
 

Mehr von Peter Friese (20)

Building Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsBuilding Reusable SwiftUI Components
Building Reusable SwiftUI Components
 
Firebase & SwiftUI Workshop
Firebase & SwiftUI WorkshopFirebase & SwiftUI Workshop
Firebase & SwiftUI Workshop
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in Swift
 
Building Apps with SwiftUI and Firebase
Building Apps with SwiftUI and FirebaseBuilding Apps with SwiftUI and Firebase
Building Apps with SwiftUI and Firebase
 
Rapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and FirebaseRapid Application Development with SwiftUI and Firebase
Rapid Application Development with SwiftUI and Firebase
 
6 Things You Didn't Know About Firebase Auth
6 Things You Didn't Know About Firebase Auth6 Things You Didn't Know About Firebase Auth
6 Things You Didn't Know About Firebase Auth
 
Five Things You Didn't Know About Firebase Auth
Five Things You Didn't Know About Firebase AuthFive Things You Didn't Know About Firebase Auth
Five Things You Didn't Know About Firebase Auth
 
Building High-Quality Apps for Google Assistant
Building High-Quality Apps for Google AssistantBuilding High-Quality Apps for Google Assistant
Building High-Quality Apps for Google Assistant
 
Building Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on Google Building Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on Google
 
Building Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on GoogleBuilding Conversational Experiences with Actions on Google
Building Conversational Experiences with Actions on Google
 
What's new in Android Wear 2.0
What's new in Android Wear 2.0What's new in Android Wear 2.0
What's new in Android Wear 2.0
 
Google Fit, Android Wear & Xamarin
Google Fit, Android Wear & XamarinGoogle Fit, Android Wear & Xamarin
Google Fit, Android Wear & Xamarin
 
Introduction to Android Wear
Introduction to Android WearIntroduction to Android Wear
Introduction to Android Wear
 
Google Play Services Rock
Google Play Services RockGoogle Play Services Rock
Google Play Services Rock
 
Introduction to Android Wear
Introduction to Android WearIntroduction to Android Wear
Introduction to Android Wear
 
Google+ for Mobile Apps on iOS and Android
Google+ for Mobile Apps on iOS and AndroidGoogle+ for Mobile Apps on iOS and Android
Google+ for Mobile Apps on iOS and Android
 
Cross-Platform Authentication with Google+ Sign-In
Cross-Platform Authentication with Google+ Sign-InCross-Platform Authentication with Google+ Sign-In
Cross-Platform Authentication with Google+ Sign-In
 
Bring Back the Fun to Testing Android Apps with Robolectric
Bring Back the Fun to Testing Android Apps with RobolectricBring Back the Fun to Testing Android Apps with Robolectric
Bring Back the Fun to Testing Android Apps with Robolectric
 
Do Androids Dream of Electric Sheep
Do Androids Dream of Electric SheepDo Androids Dream of Electric Sheep
Do Androids Dream of Electric Sheep
 
Java based Cross-Platform Mobile Development
Java based Cross-Platform Mobile DevelopmentJava based Cross-Platform Mobile Development
Java based Cross-Platform Mobile Development
 

Kürzlich hochgeladen

Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 

Kürzlich hochgeladen (20)

Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 

Firebase for Apple Developers - SwiftHeroes

  • 1. Peter Friese | Developer Advocate, Firebase Charlo!e Liang | So"ware Engineer, Firebase  + Firebase for Apple Developers @charlo!eCLiang @pete#riese
  • 2. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 3. Architecture For Data-Driven Apps Photo by Lance Anderson on Unsplash
  • 5.
  • 6. Drill-down navigation Three-column layout (iPad / Mac) Single source of truth Driving factors 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
  • 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
  • 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"ps://www.youtube.com/c/pete#riese
  • 21. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 22.
  • 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.
  • 26.
  • 27.
  • 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"ps://github.com/%rebase/%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#riese.dev/ultimate-guide-to-swi!ui2-application-lifecycle/
  • 43. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 45.
  • 46.
  • 51. struct Book: Identifiable { var id = String? 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?
  • 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.
  • 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&enditlevsen Thanks to
  • 78. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 79. Driving User Engagement Photo by ROBIN WORRALL on Unsplash
  • 80. Driving User Engagement Noti%cations Share Extensions WidgetKit
  • 81. Notification Service Extension Noti%cation Service Extension + Firebase Messaging Be!er visualisation of noti%cations Available in watchOS as well
  • 82. Watch the scene phase Handle deep links Continue user activities Learn more h"ps://%rebase.blog/posts/2019/09/fcm-image-noti%cation
  • 83. Firebase + WidgetKit Mini travel blog How to initialise Firebase How to access Firebase services
  • 85. class ShareViewController: SLComposeServiceViewController { override func didSelectPost() { !" get data from share controller context Task { !" write image data to Cloud Storage let _ = try await ref.putDataAsync(self.imageData as Data) let url = try await ref.downloadURL() !" write post data to Firestore let post = Post(description: description !# "", url: url.absoluteString) try db.collection("Posts").document("post").setData(from: post) !" refresh widgets and return to share controller WidgetCenter.shared.reloadAllTimelines() extensionContext!$completeRequest(returningItems: []) } } Firebase in a ShareViewController
  • 86. class ShareViewController: SLComposeServiceViewController { override func didSelectPost() { !" get data from share controller context Task { !" write image data to Cloud Storage let _ = try await ref.putDataAsync(self.imageData as Data) let url = try await ref.downloadURL() !" write post data to Firestore let post = Post(description: description !# "", url: url.absoluteString) try db.collection("Posts").document("post").setData(from: post) !" refresh widgets and return to share controller WidgetCenter.shared.reloadAllTimelines() extensionContext!$completeRequest(returningItems: []) } } Firebase in a ShareViewController
  • 87. class ShareViewController: SLComposeServiceViewController { override func didSelectPost() { !" get data from share controller context Task { !" write image data to Cloud Storage let _ = try await ref.putDataAsync(self.imageData as Data) let url = try await ref.downloadURL() !" write post data to Firestore let post = Post(description: description !# "", url: url.absoluteString) try db.collection("Posts").document("post").setData(from: post) !" refresh widgets and return to share controller WidgetCenter.shared.reloadAllTimelines() extensionContext!$completeRequest(returningItems: []) } } Firebase in a ShareViewController
  • 88. class ShareViewController: SLComposeServiceViewController { override func didSelectPost() { !" get data from share controller context Task { !" write image data to Cloud Storage let _ = try await ref.putDataAsync(self.imageData as Data) let url = try await ref.downloadURL() !" write post data to Firestore let post = Post(description: description !# "", url: url.absoluteString) try db.collection("Posts").document("post").setData(from: post) !" refresh widgets and return to share controller WidgetCenter.shared.reloadAllTimelines() extensionContext!$completeRequest(returningItems: []) } } Firebase in a ShareViewController
  • 89. class ShareViewController: SLComposeServiceViewController { override func didSelectPost() { !" get data from share controller context Task { !" write image data to Cloud Storage let _ = try await ref.putDataAsync(self.imageData as Data) let url = try await ref.downloadURL() !" write post data to Firestore let post = Post(description: description !# "", url: url.absoluteString) try db.collection("Posts").document("post").setData(from: post) !" refresh widgets and return to share controller WidgetCenter.shared.reloadAllTimelines() extensionContext!$completeRequest(returningItems: []) } } Firebase in a ShareViewController
  • 90. class ShareViewController: SLComposeServiceViewController { override func didSelectPost() { !" get data from share controller context Task { !" write image data to Cloud Storage let _ = try await ref.putDataAsync(self.imageData as Data) let url = try await ref.downloadURL() !" write post data to Firestore let post = Post(description: description !# "", url: url.absoluteString) try db.collection("Posts").document("post").setData(from: post) !" refresh widgets and return to share controller WidgetCenter.shared.reloadAllTimelines() extensionContext!$completeRequest(returningItems: []) } } Firebase in a ShareViewController
  • 92. func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) !" Void) { if FirebaseApp.app() !& nil { FirebaseApp.configure() } Task { let (image, text) = try await PostRepository.getPost(imageName: “image.png") let entry = SimpleEntry( date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!, configuration: configuration, image: image, text: text ) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } Firebase in a Widget
  • 93. func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) !" Void) { if FirebaseApp.app() !& nil { FirebaseApp.configure() } Task { let (image, text) = try await PostRepository.getPost(imageName: “image.png") let entry = SimpleEntry( date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!, configuration: configuration, image: image, text: text ) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } Firebase in a Widget
  • 94. func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) !" Void) { if FirebaseApp.app() !& nil { FirebaseApp.configure() } Task { let (image, text) = try await PostRepository.getPost(imageName: “image.png") let entry = SimpleEntry( date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!, configuration: configuration, image: image, text: text ) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } Firebase in a Widget
  • 95. func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) !" Void) { if FirebaseApp.app() !& nil { FirebaseApp.configure() } Task { let (image, text) = try await PostRepository.getPost(imageName: “image.png") let entry = SimpleEntry( date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!, configuration: configuration, image: image, text: text ) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } Firebase in a Widget
  • 96. func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) !" Void) { if FirebaseApp.app() !& nil { FirebaseApp.configure() } Task { let (image, text) = try await PostRepository.getPost(imageName: “image.png") let entry = SimpleEntry( date: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!, configuration: configuration, image: image, text: text ) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } Firebase in a Widget
  • 97. import FirebaseStorage import FirebaseStorageSwift import FirebaseFirestore import FirebaseFirestoreSwift struct PostRepository { static func getPost(imageName: String) async throws !" (UIImage, String) { let ref = Storage.storage().reference().child(imageName) let data = try await ref.data(maxSize: 20 * 1024 * 2048) let image = UIImage(data: data)! let db = Firestore.firestore() let docRef = db.collection("Posts").document("post") let document = try await docRef.getDocument() let post = try document.data(as: Post.self) return (image, post.description) Firebase in a Widget
  • 98. import FirebaseStorage import FirebaseStorageSwift import FirebaseFirestore import FirebaseFirestoreSwift struct PostRepository { static func getPost(imageName: String) async throws !" (UIImage, String) { let ref = Storage.storage().reference().child(imageName) let data = try await ref.data(maxSize: 20 * 1024 * 2048) let image = UIImage(data: data)! let db = Firestore.firestore() let docRef = db.collection("Posts").document("post") let document = try await docRef.getDocument() let post = try document.data(as: Post.self) return (image, post.description) Firebase in a Widget
  • 99. import FirebaseStorage import FirebaseStorageSwift import FirebaseFirestore import FirebaseFirestoreSwift struct PostRepository { static func getPost(imageName: String) async throws !" (UIImage, String) { let ref = Storage.storage().reference().child(imageName) let data = try await ref.data(maxSize: 20 * 1024 * 2048) let image = UIImage(data: data)! let db = Firestore.firestore() let docRef = db.collection("Posts").document("post") let document = try await docRef.getDocument() let post = try document.data(as: Post.self) return (image, post.description) Firebase in a Widget
  • 100. import FirebaseStorage import FirebaseStorageSwift import FirebaseFirestore import FirebaseFirestoreSwift struct PostRepository { static func getPost(imageName: String) async throws !" (UIImage, String) { let ref = Storage.storage().reference().child(imageName) let data = try await ref.data(maxSize: 20 * 1024 * 2048) let image = UIImage(data: data)! let db = Firestore.firestore() let docRef = db.collection("Posts").document("post") let document = try await docRef.getDocument() let post = try document.data(as: Post.self) return (image, post.description) Firebase in a Widget
  • 101. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 103. Authentication Photo by Eduardo Soares on Unsplash
  • 104. Authentication Photo by Victor Freitas on Unsplash
  • 105.
  • 106. Sign in the user Update the data model Secure users’ data How to implement Firebase Authentication?
  • 108. func signIn() { registerStateListener() if Auth.auth().currentUser !& nil { Auth.auth().signInAnonymously() } } Anonymous Authentication
  • 109.
  • 110. 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
  • 111. 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
  • 112. 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
  • 113. 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
  • 114. 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
  • 115. 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
  • 116. All books are stored in one single collection Which user do they belong to?
  • 117. let query = db.collection("books") .whereField("userId", isEqualTo: self.userId) query .addSnapshotListener { [weak self] (querySnapsho guard let documents = querySnapshot!$documents els Signed in user
  • 118. 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
  • 119. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 121. auth!$signInAnonymously() let user = auth!$currentUser print("User signed in with user ID: (user!$uid)") This might be nil
  • 122. 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
  • 123. 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)") } @Published var user: User? !!' auth!$signInAnonymously() .map{ $0.user } .replaceError(with: nil) .assign(to: &$user) Even better
  • 124. Launched with Firebase 8.9.0 h"ps://github.com/%rebase/%rebase-ios-sdk/projects/3
  • 125. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 127. 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
  • 128. 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
  • 129. 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
  • 130. 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
  • 131. 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
  • 132. 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
  • 133. 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
  • 135. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 136. Architecture Swi!UI 2 Life Cycle Firebase Firestore Authentication Combine async/await Completion block WidgetKit
  • 137. Register now - it’s free! h"ps://io.google/2022/ Google I/O 2022
  • 139. Credits %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& 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&esy h"ps://%rebase.google.com/brand-guidelines Firebase logos cou&esy h"ps://%rebase.google.com/brand-guidelines Thanks! Hea& by Roman from the Noun Project Dashboard by BomSymbols from the Noun Project