Firebase: Totally Not Parse All Over Again (Unless It Is) (CocoaConf San Jose, Nov. 2016)
-
Upload
chris-adamson -
Category
Software
-
view
313 -
download
0
Transcript of Firebase: Totally Not Parse All Over Again (Unless It Is) (CocoaConf San Jose, Nov. 2016)
Firebase: Totally Not Parse All Over Again
(Unless It Is)Chris Adamson (@invalidname)
CocoaConf San Jose • November, 2016
Slides available at slideshare.net/invalidname Code available at github.com/invalidstream
Firebase
• Founded in 2011
• Offshoot of Envolve, a chat service that game developers started using to sync state across devices
• Main product is a realtime database
• Acquired by Google in October 2014
https://techcrunch.com/2016/05/18/google-turns-firebase-into-its-unified-platform-for-mobile-developers/
Firebase @ Google
• 470,000 developers using Firebase
• Arguably the star of Google I/O 2016
• Analytics (from developers of Google Analytics)
• Notifications (based on Google Cloud Messaging)
Realtime database
• Cloud-based NoSQL database
• Syncs instantly across devices, handles going offline
• Client SDKs for iOS and Android, REST for web
• Free tier supports 100 simultaneous users, 1GB storage
Getting Started
• Create app on firebase.google.com using your apps bundle identifier
• Download and add GoogleService-info.plist to your project
Getting Started
• Add the Firebase Cocoapod
• As with all things pod, remember to use .xcworkspace instead of .xcproj from now on
• Yes, it is possible to add the frameworks without Cocoapods
Getting Started
• Initialize Firebase in application(_: didFinishLaunchingWithOptions:)
import Firebase
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. FIRApp.configure() return true }
Wait, what the…
• Firebase I/O works with a local cache, which in turn syncs with the backend
• With good connectivity, syncing to backend and other devices is instantaneous
• When offline, you can keep working with your local db, which syncs when you’re back online
Tree-structured data• Your database is basically one big JSON tree
• You access branches and nodes by path
• e.g., /sessions/adamson-firebase/title
• You can query at a given location, but this is not a relational database.
• If you want to do a table join, you structured your data incorrectly. Prefer flatness.
Getting a Firebase Reference
• FIRDatabase.reference() returns root of tree as a FIRDatabaseReference
• Child returns the named child as a FIRDatabaseReference
firebaseSessions = FIRDatabase.database().reference(). child("sessions")
Observing Firebase
• All interactions with Firebase are asynchronous
• You don’t read the value of a location, you observe it for changes
• Contents of the child are passed to you on every change
Observe!
• eventType: the type of event you want to observe (value, child CRUD, etc)
• block: a closure to execute on these events
• returns a handle (dispose it in deinit, or earlier)
firebaseHandle = firebaseSessions?.observe( FIRDataEventType.value, with: { [weak self] (snapshot) in self?.parseSessionsFrom(snapshot) self?.tableView.reloadData() })
Parse!
• Observer block receives a FIRDataSnapshot for every change
• Immutable, fetch contents with .value()
• Value types: NSDictionary, NSArray (rare), NSString, NSNumber [or Swift equivalents]
Parse sessions listprivate func parseSessionsFrom(_ snapshot: FIRDataSnapshot) { guard let fbSessions = snapshot.value as? [String : Any] else { return } sessions.removeAll() for (id, value) in fbSessions { if let sessionDict = value as? [String : Any], let session = Session(id: id, dict: sessionDict) { sessions.append(session) } } }
Parse a sessioninit? (id: String, dict : [String : Any]) { guard let title = dict["title"] as? String, let speakerName = dict["speakerName"] as? String, let description = dict["description"] as? String else { return nil } self.id = id self.title = title self.speakerName = speakerName self.description = description }
Note: id is the node name (the key in the dictionary on the last slide)
Tip!
• FIRDatabaseReference.url can be pasted into your browser to view the Firebase console for that location in your db.
Creating a locationif let oldId = UserDefaults.standard.string(forKey: "userId") { firebaseUser = firebaseAttendees?.child(oldId) } else { firebaseUser = firebaseAttendees?.childByAutoId() let newId = firebaseUser?.key let firebaseUserName = firebaseUser?.child("name") firebaseUserName?.setValue("Foo Bar") UserDefaults.standard.setValue(newId, forKey: "userId") UserDefaults.standard.synchronize() }
Setting a value
let firebaseUserFavorites = firebaseUser?.child("favorites") let firebaseFavorite = firebaseUserFavorites?.child(sessionId) firebaseFavorite?.setValue(true)
Arrays in Firebase ಠ_ಠ
• A dictionary with numeric keys in order will be sent to your observer as an array rather than a dictionary
• Not as convenient as you’d think. Pretty much a Firebase anti-pattern
observeSingleEvent()favoriteTitles.removeAll() for (sessionId, _) in fbFavorites { firebaseSessions?.child(sessionId).child(“title").observeSingleEvent( of: FIRDataEventType.value, with: { [weak self] snapshot in if let title = snapshot.value as? String { self?.favoriteTitles.append( FavoriteItem(favoriteId: sessionId, title: title)) self?.tableView.reloadData() } }) }
Note: observeSingleEvent() does not return a handle for you to hold on to and dispose later
Authentication• Firebase provides an email + password system
• Can also use Google, Twitter, Facebook, or GitHub credentials
• Or roll your own and provide an OAuth token
Database rules
• Define per-branch access based on authentication
• Can require that user just be logged in, or that their Firebase user id matches the one that created the node
Default access
// These rules require authentication{ "rules": { ".read": "auth != null", ".write": "auth != null" }}
Public access
// These rules give anyone, even people who are not users of your app,// read and write access to your database{ "rules": { ".read": true, ".write": true }}
Private access: just make read/write false
User access// These rules grant access to a node matching the authenticated// user's ID from the Firebase auth token{ "rules": { "users": { "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" } } }}
MathElf
• “Über for high-school math tutoring”
• Students request help on a topic, are paired with a tutor in less than a minute
• Student and tutor work on problems via voice chat and a shared whiteboard
MathElf & Firebase
• Authentication and user financials are done through rev.com (parent company) backend, making REST calls to Firebase
• Basically everything in the whiteboard and session history is Firebase
Firebase “con”s
• Limited visibility when something goes wrong
• When things don’t sync, is it them or you?
• Single point of failure, owned and operated by a third party
• Could be Parse all over again
Takeaways• Firebase database-as-a-service is well-suited to
mobile apps
• Real-time sync, still works when offline
• Structure your data as flat JSON trees, not SQL-like tables
• All reads are asynchronous. Hope you like closures/blocks.
Firebase: Totally Not Parse All Over Again
(Unless It Is)Chris Adamson (@invalidname)
CocoaConf San Jose • November, 2016
Slides available at slideshare.net/invalidname Code available at github.com/invalidstream