Today we released new versions of our Android (2.4.0), iOS (2.4.0), and JavaScript (2.3.0) clients. You can read the individual changelogs for details (Android, iOS, web), but we wanted to expand on three highly requested features shipping with this release: atomic multi-location writes, ordering queries by deep paths, and iOS 9 bitcode support. Let's jump in!
Firebase has always supported updating multiple children of a node atomically. For example, let's assume the /user path in our Firebase database contains the following data:
/user
{ "user": { "name": "Samantha", "age": 25 } }
Using the pre-existing atomic update support, we can change the user's age and add a city without updating their name:
age
city
name
var ref = new Firebase("https://.firebaseio.com"); var userRef = ref.child("user"); var newUserData = { "age": 30, "city": "Provo, UT" }; ref.child("user").update(newUserData);
Firebase ref = Firebase(url: "https://<YOUR-FIREBASE-APP>.firebaseio.com"); Firebase userRef = ref.child("user"); Map newUserData = new HashMap(); newUserData.put("age", 30); newUserData.put("city", "Provo, UT"); userRef.updateChildren(newUserData);
Firebase *ref = [[Firebase alloc] initWithUrl:@"https://<YOUR-FIREBASE-APP>.firebaseio.com"]; Firebase *userRef = [ref childByAppendingPath: @"user"]; NSDictionary *newUserData = @{ @"age": 30, @"city": "Provo, UT" }; [userRef updateChildValues: newUserData];
let ref = Firebase(url: "https://<YOUR-FIREBASE-APP>.firebaseio.com") let userRef = ref.childByAppendingPath("user") let newUserData = ["age": 30, "city": "Provo, UT"] userRef.updateChildValues(newUserData)
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com"); // Generate a new push ID for the new post var newPostRef = ref.child("posts").push(); var newPostKey = newPostRef.key(); // Create the data we want to update var updatedUserData = {}; updatedUserData["user/posts/" + newPostKey] = true; updatedUserData["posts/" + newPostKey] = { title: "New Post", content: "Here is my new post!" }; // Do a deep-path update ref.update(updatedUserData, function(error) { if (error) { console.log("Error updating data:", error); } });
Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com"); // Generate a new push ID for the new post Firebase newPostRef = ref.child("posts").push(); String newPostKey = newPostRef.getKey(); // Create the data we want to update Map newPost = new HashMap(); newPost.put("title", "New Post"); newPost.put("content", "Here is my new post!"); Map updatedUserData = new HashMap(); updatedUserData.put("users/posts/" + newPostKey, true); updatedUserData.put("posts/" + newPostKey, newPost); // Do a deep-path update ref.updateChildren(updatedUserData, new Firebase.CompletionListener() { @Override public void onComplete(FirebaseError firebaseError, Firebase firebase) { if (firebaseError != null) { System.out.println("Error updating data: " + firebaseError.getMessage()); } } });
Firebase *ref = [[Firebase alloc] initWithUrl: @"https://<YOUR-FIREBASE-APP>.firebaseio.com"]; // Generate a new push ID for the new post Firebase *newPostRef = [[ref childByAppendingPath:@"posts"] childByAutoId]; NSString *newPostKey = newPostRef.key; NSString *updateUserPath = [NSString stringWithFormat:@"users/posts/%@", newPostKey]; NSString *updatePostPath = [NSString stringWithFormat:@"posts/%@", newPostKey]; // Create the data we want to update NSMutableDictionary *updatedUserData = [[NSMutableDictionary alloc] init]; [updatedUserData setValue:[NSNumber numberWithBool:YES] forKey:updateUserPath]; [updatedUserData setValue:@{@"title": @"New Post", @"content": @"Here is my new post!"} forKey:updatePostPath]; // Do a deep-path update [ref updateChildValues:updatedUserData withCompletionBlock:^(NSError *error, Firebase *ref) { if (error) { NSLog(@"Error updating data: %@", error.debugDescription); } }];
let ref = Firebase(url: "https://<YOUR-FIREBASE-APP>.firebaseio.com") // Generate a new push ID for the new post let newPostRef = ref.childByAppendingPath("posts").childByAutoId() let newPostKey = newPostRef.key // Create the data we want to update let updatedUserData = ["users/posts/\(newPostKey)": true, "posts/\(newPostKey)": ["title": "New Post", "content": "Here is my new post!"]] // Do a deep-path update ref.updateChildValues(updatedUserData, withCompletionBlock: { (error, ref) -> Void in if (error) { print("Error updating data: \(error.description)") } })
Deep path updates let you write cleaner code and easily denormalize data across multiple nodes in your Firebase database.
Up until now, we have been able to order queries by direct children. Here's a snippet of the dinosaur data stored in our database of dinosaur facts:
{ "lambeosaurus": { "height" : 2.1, "length" : 12.5, "weight": 5000 }, "stegosaurus": { "height" : 4, "length" : 9, "weight" : 2500 } }
To read all dinosaurs ordered by height, we can order nodes by a common child key:
var ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs"); ref.orderByChild("height").on("child_added", function(snapshot) { var dinoName = snapshot.key(); var dinoData = snapshot.val(); console.log(dinoName + " was " + dinoData.height + " meters tall"); });
Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs"); Query queryRef = ref.orderByChild("height"); queryRef.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot snapshot, String previousChild) { DinosaurFacts facts = snapshot.getValue(DinosaurFacts.class); System.out.println(snapshot.getKey() + " was " + facts.getHeight() + " meters tall"); } // .... });
Firebase *ref = [[Firebase alloc] initWithUrl:@"https://dinosaur-facts.firebaseio.com/dinosaurs"]; [[ref queryOrderedByChild:@"height"] observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) { NSLog(@"%@ was %@ meters tall", snapshot.key, snapshot.value[@"height"]); }];
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs") ref.queryOrderedByChild("height").observeEventType(.ChildAdded, withBlock: { snapshot in if let height = snapshot.value["height"] as? Double { println("\(snapshot.key) was \(height) meters tall") } })
This works great, but sometimes we want to order by descendants further down in our data tree. Let's say our dinosaur data actually looked like this:
{ "lambeosaurus": { "dimensions": { "height" : 2.1, "length" : 12.5, "weight": 5000 } }, "stegosaurus": { "dimensions": { "height" : 4, "length" : 9, "weight" : 2500 } } }
Previously there was no way to sort these dinosaurs based on any of their dimensions since they are stored too many levels deep. Now we can use a deep path query!
var ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs"); ref.orderByChild("dimensions/height").on("child_added", function(snapshot) { var dinoName = snapshot.key(); var dinoData = snapshot.val(); console.log(dinoName + " was " + dinoData.dimensions.height + " meters tall"); });
Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs"); Query queryRef = ref.orderByChild("dimensions/height"); queryRef.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot snapshot, String previousChild) { DinosaurFacts facts = snapshot.getValue(DinosaurFacts.class); System.out.println(snapshot.getKey() + " was " + facts.getHeight() + " meters tall"); } // .... });
Firebase *ref = [[Firebase alloc] initWithUrl:@"https://dinosaur-facts.firebaseio.com/dinosaurs"]; [[ref queryOrderedByChild:@"dimensions/height"] observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) { NSLog(@"%@ was %@ meters tall", snapshot.key, snapshot.value[@"dimensions"][@"height"]); }];
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs") ref.queryOrderedByChild("dimensions/height").observeEventType(.ChildAdded, withBlock: { snapshot in if let height = snapshot.childSnapshotForPath("dimensions/height").value as? Double { println("\(snapshot.key) was \(height) meters tall") } })
The ability to order by deep paths gives you much more freedom in structuring your data.
The Firebase iOS client is now compiled with bitcode support, a new iOS 9 feature. Bitcode is an "intermediate" language that Apple uses to represent your iOS and watchOS apps. According to Apple, “including bitcode will allow [us] to re-optimize your app binary in the future without the need to submit a new version of your app to the store.” This enables you to ship smaller apps through app thinning, as well as design for future products, decreasing development time and increasing the number of platforms upon which your app runs.
On top of the new features mentioned above, we have also fixed a handful of bugs across each of the clients. Memory leaks, cache inconsistencies, and crashes have been tracked down and fixed in today's releases.
All of these new features and bug fixes are now available in version 2.4.0 of the mobile clients and version 2.3.0 of the JavaScript client. See the installation and setup guides for each platform (Android, iOS, web) to learn how to get started with the latest clients.
We'd love to hear what you think of this release. Post any feedback or submit bug reports in our Google Group. Lastly, if you've built anything, don't be shy! Share your apps with us on Twitter @Firebase. We're excited to see what you build.