Sunday, 31 January 2016
Thursday, 21 January 2016
Keeping our Promises (and Callbacks)
Today we’re adding support for Promises in the Firebase JavaScript SDK. Our promises are A+ compatible and their use is entirely optional.
What Are Promises?
Promises are an alternative to callbacks. They improve readability, simplify error handling, and decouple tasks into composable units. A Promise is a task that may not have finished yet. When a Promise's task finishes successfully the Promise is "fulfilled", otherwise it is "rejected." You interact with a Promise by calling its then
method with callbacks that should be executed when the Promise is fulfilled or rejected.
Let's demonstrate the differences between callbacks and promises by building part of a blog webapp. Our first step is to fetch an article’s contents. Here is how it might look with callbacks:
ref.child('blogposts').child(id).once('value', function(snapshot) {
// The callback succeeded; do something with the final result.
renderBlog(snapshot.val());
}, function(error) {
// The callback failed.
console.error(error);
});
The Promise-based implementation is similar:
ref.child('blogposts').child(id).once('value').then(function(snapshot) {
// The Promise was "fulfilled" (it succeeded).
renderBlog(snapshot.val());
}, function(error) {
// The Promise was rejected.
console.error(error);
});
When your task has only one step, Promises and callbacks are almost identical. Promises shine when your task has multiple steps.
Promises are Composable
Promises are most useful when you compose them. The then
method returns a new Promise and that Promise’s return value comes from the functions passed to then. Let’s create a simple utility function that fetches a blog post and returns the JS Object, not the DataSnapshot
, at that location:
// Fetch a Blog Post by ID. Returns a Promise of an actual object, not a DataSnapshot.
function getArticlePromise(id) {
return ref.child('blogposts').child(id).once('value').then(function(snapshot) {
return snapshot.val();
});
}
Now we can use getArticlePromise()
and we get a Promise that does more than just fetch data from Firebase. This is especially useful when you want to transform Firebase data into a model in your application. You might also notice that we completely left error handling out of our sample--more about that later. Perhaps the greatest thing about then
is the way it handles Promises returned by the functions you pass to then
: if your function returned a Promise, the Promise returned by then
will resolve or reject with the same value as the Promise you return. That’s a bit dense, so let’s illustrate the idea with a code sample. We are going to expand our blog app to fetch an article and update a read counter. The callback sample starts to get complicated:
var articleRef = ref.child('blogposts').child(id);
articleRef.once('value', function(article) {
// The first callback succeeded; go to the second.
articleRef.child('readCount').transaction(function(current) {
// Increment readCount by 1, or set to 1 if it was undefined before.
return (current || 0) + 1;
}, function(error, committed, snapshot) {
if (error) {
// The fetch succeeded, but the update failed.
console.error(error);
} else {
renderBlog({
article: article.val(),
readCount: snapshot.val()
});
}
});
}, function(error) {
// The fetch failed.
console.error(error);
});
The code handles errors in many places and we start to see the "Pyramid of Doom," the code indents deeper with every subtask. The Promise version is shorter, its indentation is simpler, and it doesn't worry about error handling until the end:
var article;
var articleRef = ref.child('blogposts').child(id);
articleRef.once('value').then(function(snapshot) {
// The first promise succeeded. Save snapshot for later.
article = snapshot.val();
// By returning a Promise, we know the function passed to "then" below
// will execute after the transaction finishes.
return articleRef.child('readCount').transaction(function(current) {
// Increment readCount by 1, or set to 1 if it was undefined before.
return (current || 0) + 1;
});
}).then(function(readCountTxn) {
// All promises succeeded.
renderBlog({
article: article,
readCount: readCountTxn.snapshot.val()
});
}, function(error) {
// Something went wrong.
console.error(error);
});
Error Handling
When you "chain" Promises with the then
method, you can ignore errors until you are ready to handle them. Promises act like asynchronous try
/ catch
blocks. You handle a rejected Promise by passing a second function to then
. That second function is called instead of the first function if the Promise was rejected. If you don't pass a second function to then
, the first function isn't called and the Promise that then
returns is rejected with the same error that the previous Promise was rejected with.
Firebase also supports a shorthand catch
which only takes an error handler. catch
is not part of the A+ standard, but is part of the new JavaScript built-in and most Promise libraries. Let’s demonstrate error handling by creating a getProfilePicPromise()
utility:
// Returns a Promise of a Blob
function getProfilePicPromise(author) {
return fetch(author.profileUrl).catch(function() {
// By returning a new promise, we "recover" from errors in the first.
return fetch(defaultProfileUrl);
});
};
In this example, any failure to get the author's profile picture is handled by getting the default profile picture. If we successfully get the default profile picture, then getProfilePicPromise()
succeeds. Calling catch
or passing a second function to then
recovers from the error, just like a catch
block in synchronous code. Promises also have a version of "rethrowing" the error: you can literally throw an error or return a rejected Promise. To create a rejected Promise call Promise.reject(error)
.
Advanced Topics
The helper function Promise.all()
takes an array of objects which can be Promises or regular values; the Promise returned by all()
resolves to an array of the results of its inputs once they are all ready. We can use this to let our code do multiple things at once. Let’s expand our Promise-based sample once more by letting users "star" their favorite articles:
var getArticle = getArticlePromise(id);
// After we get the article, automatically fetch the profile picture
var getProfilePic = getArticle.then(function(article) {
return getProfilePicPromise(article.author);
});
// We can find out whether the article is starred without waiting on any other task.
var getIsStarred = false;
var authData = ref.getAuth();
if (authData) {
var isStarredRef = ref.child('userStars').child(authData.uid).child(id);
getIsStarred = isStarredRef.once('value').then(function(snapshot) {
return snapshot.val() != null;
});
}
// Run all the requests then render the results.
Promise.all([getArticle, getProfilePic, getIsStarred]).then(function(results) {
renderBlog({
article: results[0],
profilePic: results[1],
isStarred: results[2],
});
// We’ve fetched everything; increment the read count.
return ref.child('blogposts').child(id).child('readCount').transaction(function(current) {
return (current || 0) + 1;
});
});
This code sample fetches an article and a profile picture for the article’s author (with support for fetching a default image) in sequence. While that sequence is happening, we fetch whether the current user has starred the article in parallel. When all information is fetched, we increment a read counter. The callback-implementation is sufficiently more complicated and is left as an exercise for the reader.
Updating Your Code
If you copy the samples in this post, be aware that they use some newer JavaScript built-ins: the fetch API and the Promise class. Firebase APIs return a Promise that works in all browsers. If you want to create your own Promises, consider using a library like Q. These Promise libraries let you write code that works on browsers that don't have the official Promise class yet.
All functions in Firebase that fire a one-time event now accept both Promise and callback-style methods. The following tables can help you translate your code to the Promise version:
Firebase
Callbacks | Promises |
---|---|
auth(authToken, onComplete, onCancel) | /* Promise<AuthResult> */ auth(authToken) |
authWithCustomToken(authToken, onComplete, [options]) | /* Promise<AuthResult> */ authWithCustomToken(authToken, [options]) |
authAnonymously(onComplete, [options]) | /* Promise<AuthResult> */ authAnonymously([options]) |
authWithPassword(credentials, onComplete, [options]) | /* Promise<AuthResult> */ authWithPassword(credentials, [options]) |
authWithOAuthPopup(provider, credentials, onComplete, [options]) | /* Promise<AuthResult> */ authWithOAuthPopup(provider, [options]) |
authWithOAuthRedirect(provider, onComplete, [options]) | /* Promise<AuthResult> */ authWithOAuthRedirect(provider, [options]) |
authWithOAuthToken(provider, credentials, onComplete, [options]) | /* Promise<AuthResult> */ authWithOAuthToken(provider, credentials, [options]) |
set(value, onComplete) | /* Promise<> */ set(value) |
update(value, onComplete) | /* Promise<> */ update(value) |
remove(value, onComplete) | /* Promise<> */ remove() |
/* Firebase */ push(value, onComplete) | /* Firebase; which is also a Promise<Firebase> */ push(value) |
setWithPriority(value, priority, onComplete) | /* Promise<> */ setWithPriority(value, priority) |
setPriority(priority, onComplete) | /* Promise<> */ setPriority(priority) |
transaction(updateFunction, onComplete, [applyLocally]) | /* Promise<Object> */ transaction(updateFunction) |
createUser(credentials, onComplete) | /* Promise<AuthResult> */ createUser(credentials) |
changeEmail(credentials, onComplete) | /* Promise<> */ changeEmail(credentials) |
changePassword(credentials, onComplete) | /* Promise<> */ changePassword(credentials) |
removeUser(credentials, onComplete) | /* Promise<> */ removeUser(credentials) |
resetPassword(credentials, onComplete) | /* Promise<> */ resetPassword(credentials) |
Note: the result of the transaction
method is a Promise for an object with two fields: committed
and snapshot
. These map to the two parameters passed to the onComplete
callback.
Query
Callbacks | Promises |
---|---|
once(eventType, successCallback, failureCallback) | /* Promise<DataSnapshot> */ once(eventType) |
Firebase.onDisconnect()
Callbacks | Promises |
---|---|
set(value, onComplete) | /* Promise<> */ set(value) |
update(value, onComplete) | /* Promise<> */ update(value) |
remove(value, onComplete) | /* Promise<> */ remove() |
setWithPriority(value, priority, onComplete) | /* Promise<> */ setWithPriority(value, priority) |
cancel(onComplete) | /* Promise<> */ cancel() |
Thursday, 14 January 2016
Social login with Ionic
The best way to build a hybrid app is to deal with the underlying details of Cordova as little as possible. For this, Ionic is your best friend. Ionic abstracts the difficult parts of hybrid development into an easy to use SDK.
But, there still is one area of difficulty. Social login. Logging in with a social provider requires a popup or a redirect. The problem is, this doesn’t exist in the native world. It’s okay though, there’s another way, and it’s easy.
Setup
For this tutorial, 80% of the battle is just setting up. The actual code writing part is much easier.
If this is your first rodeo with Ionic, here’s a few steps to get you up and running. If you’re a seasoned Ionic veteran, you can skip this section.
Make sure your machine’s version of Node.js is above 4.x
. Using npm, download the following dependencies:
npm i -g ionic && cordova
npm i -g ios-deploy
After the install is done, you’ll create a new project.
Creating a project
Using the Ionic CLI, create a new project.
ionic start firebase-social-login tabs
One the setup is done, add both iOS and Android platforms:
ionic platform add android
ionic platform add ios
Installing dependencies
The last part of the setup is to add AngularFire and the InAppBrowser plugin. They’ll be more on the InAppBrowser plugin later.ionic plugin add cordova-plugin-inappbrowser
ionic add angularfire
Now that everything is installed, let’s make sure the app is able to run.
Build and run
To build for either iOS or Android run the following command:
ionic build android
# or for ios
ionic build ios
Then run the emulator/simulator:
ionic emulate android
# or for ios
ionic emulate ios
You should see the default project running on the emulated device.
If you want to run it in the browser, then you should totally use Ionic labs.
ionic serve --lab
Ionic labs is a nifty tool that iframes both the iOS and Android styles side-by-side. Which makes for awesome Firebase tutorials, by-the-way.
With the build setup done, let’s get Firebase up and running.
Firebase Setup
Open up www/index.html
. Add the following scripts between Ionic and Cordova.
<!-- Firebase & AngularFire -->
<script src="lib/firebase/firebase.js"></script>
<script src="lib/angularfire/dist/angularfire.min.js"></script>
After the scripts have been added, open www/js/app.js
.
Declare AngularFire in the dependency array:
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'firebase'])
The Firebase setup is all taken care of. We’re ready to move on to the fun part, dependency injection!
Dependency Injection and Firebase
Add the following two lines of code right below the module declaration:
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'firebase'])
.constant('FirebaseUrl', 'https://ionicle.firebaseio.com/')
.service('rootRef', ['FirebaseUrl', Firebase])
The rootRef
service is a neat trick to inject a reference into any Angular service, factory, controller, provider, or whatever. You’ll use this in the next step, when setting up authentication.
Authentication setup
Open up www/js/services.js
, and add the following code:
function Auth(rootRef, $firebaseAuth) {
return $firebaseAuth(rootRef);
}
Auth.$inject = ['rootRef', '$firebaseAuth'];
Make sure to declare this function as a factory in the services
angular module:
.factory('Auth', Auth);
This style of code is based off the Angular Styleguide, check out the Github repo for more details.
In the code above, you’re simply injecting the rootRef
and $firebaseAuth
services. Since rootRef is a Firebase database reference, it is passed into the $firebaseAuth
service, and returned from the function. The $inject
property is a shorthand for declaring dependencies to work with minification.
That’s all the authentication setup needed. It’s time to create an API key with a social provider.
Obtaining a Social API Key
Firebase authentication allows you to login users to your apps with social providers like Google, Twitter, Facebook, and Github.
This tutorial uses Google, but you can use another if you’d like.
Social login with Firebase requires you to get a set of API keys from a provider. See the Firebase documentation on User Authentication for more details on getting a key and setting it up in the dashboard.
Once you’ve added an API and a Secret key to the Firebase App Dashboard, let’s create the login page.
Controller
To create the login page, you need three things: a controller, a view, and a route.
To create the controller, open www/js/controllers.js
and add the following snippet:
function LoginCtrl(Auth, $state) {
this.loginWithGoogle = function loginWithGoogle() {
Auth.$authWithOAuthPopup('google')
.then(function(authData) {
$state.go('tab.dash');
});
};
}
LoginCtrl.$inject = ['Auth', '$state'];
Then register the controller with the module:
.controller('LoginCtrl', LoginCtrl);
The LoginCtrl will be used with controllerAs
syntax. This means you attach methods to the controller using this
rather than $scope
. The controller has a single method, loginWithGoogle
, that will move the user to the 'tab.dash'
route once they’re authenticated.
View
The view couldn’t be simpler. Underneath www/templates
, create a login.html
file and add the following code:
<ion-view view-title="Login">
<ion-content>
<div class="padding">
<button class="button button-block button-assertive" ng-click="ctrl.loginWithGoogle()">
</button>
</div>
</ion-content>
</ion-view>
The view uses a few components from Ionic’s SDK, like the "assertive" button. When the button is tapped, the loginWithGoogle()
method gets called.
That’s it for the view, let’s move onto the router.
Router
Open www/js/app.js
, and find the .config()
function. The .config()
function injects the $stateProvider
, which is used to tell the app which controllers to use for which routes.
Add the following route below:
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl as ctrl'
})
Notice that the route sets up the controller property to use controllerAs
syntax. This is what allows you to use the ctrl
variable in the login.html
template.
Using the InAppBrowser plugin
To use the InAppBrowser plugin, do absolutely nothing. Yep, by simply installing the plugin, everything is handled for you.
The problem is, that on mobile there is no analogous “popup” view. The authWithOAuthPopup()
method uses window.open()
to to open up a new popup window. When this happens, Cordova won’t know what to do. The InAppBrowser plugin fixes this by showing a web browser in your app when window.open()
is called.
So you can move on, because there’s nothing left to do here.
Build and run, again
Build and run the app for the emulator/simulator. You should the a basic login view. Tap the button to login. A browser window should popup, and let you login with a social account. After the authentication process completes, the app should move onto the dashboard view.
Get the code, and give it a star
Check out the completed app on Github. And, if you’re feeling generous, we would love a star. Feel free to fork the repo, and even send in a PR if you want.
Still having trouble?
If you’re running into issues, open up a question on Stackoverflow, we closely monitor the Firebase tag, or drop a post in our Google Group.
Monday, 11 January 2016
Getting started with NativeScript and Firebase
Jen Looper is a Developer Advocate at Telerik where she specializes in creating cross platform mobile apps. She's a multilingual multiculturalist with a passion for hardware hacking and learning new things every day.
NativeScript is Telerik’s open source runtime that allows you to build truly native cross-platform iOS and Android mobile apps with a single codebase using JavaScript, XML, and CSS. When building the application UI, developers use our libraries, which abstract the differences between the native platforms. To learn more, take a look at the video below, go through the documentation, take a Udemy course, or download an app built with NativeScript.
Firebase Plugin
Building NativeScript apps just got a lot easier thanks to the new Firebase Plugin by Plugin Master Eddy Verbruggen. This plugin brings the speed and realtime connectivity of Firebase to NativeScript.Let's get started by building a Groceries app!
Here it is in action with my iPhone feeding data to my simulator, and vice versa:
1. Setup
You’re going to need NativeScript installed on your computer, first and foremost. To do that on a Mac, you’ll need Homebrew and Node.js 0.10.x, or 0.12.x, or the 4.2.x stable official release; follow the installation instructions here.On a PC, installation instructions are here, and on Linux, installation instructions are here.
Currently, this app is designed to use Telerik’s backend services as its database tier. In this tutorial, I'll demonstrate how to create a new user, login using those credentials, and manage grocery lists, creating and deleting data using Firebase as the app’s backend.
You can follow along by forking the 'end' branch of the NativeScript Getting Started Guide's Groceries app, or check out the completed project here.
2. Connect Firebase to the app
The Groceries app is already set up with everything you need to get up and running quickly with a backend solution. Currently it assumes that you use Telerik backend services. To use Firebase instead, we first need to install the firebase plugin by executing the following command in the root of your app's folder:tns plugin add nativescript-plugin-firebaseYou'll note that a line has been added to package.json file in the root folder:
"nativescript-plugin-firebase": "^1.2.0"You can update the plugin version by editing package.json and run npm install in the root of the project when needed, to keep the plugin installation up to date with new features.
Now you can add the Firebase URL as the apiURL in your config file. In /app/shared/config.js, edit the apiUrl:
module.exports = {Next, add a method to initialize Firebase. In app/views/login/login.js, add a line to call user.init() at the end of the load() function, and then create the init() function in app/shared/view-models/user-view-model.js by adding this code above the login() function:
apiUrl: "https://incandescent-fire-8397.firebaseio.com/"
};
viewModel.init = function(){Since Firebase will replace the need to fetch data manually from another datasource, you can overwrite a require statement at the top; replace
firebase.init({
url: config.apiUrl
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
};
var fetchModule = require("fetch");
with var firebase = require("nativescript-plugin-firebase");
Now, you have initialized Firebase as your app's backend. If you run your app, you should see the console statements coming through, telling you the initialization was successful. The next step is to get your users set up.
3. Add registration and login
The Firebase plugin, right now, supports registration with a username and password and login with the same technique, as well as 'anonymous login'. We're going to use the former, so enable Email and Password authentication by checking the appropriate box in the Login & Auth tab of your Firebase dashboard:Replace the
login()
and register()
functions in app/shared/view-models/user-view-model.js
with the following functions:viewModel.login = function() {**Note**: You can delete the function
return firebase.login({
type: firebase.loginType.PASSWORD,
email: viewModel.get("email"),
password: viewModel.get("password")
}).then(
function (response) {
config.uid = response.uid
return response;
});
};
viewModel.register = function() {
return firebase.createUser({
email: viewModel.get("email"),
password: viewModel.get("password")
}).then(
function (response) {
console.log(response);
return response;
}
);
};
handleErrors()
from the code, as Firebase bubbles up helpful error messages such as email address already in use or incorrect passwords. In addition, you can remove the code to check for valid email addresses, as Firebase does that for you as well. If you do delete the email checking code, which is normally handled by an npm module, your register()
function in app/views/register/register.js
can be simplified to this: exports.register = function() {Then you can delete the completeRegistration() function entirely. Firebase makes these basis authentication tasks nice and easy! Go ahead and register for your app, a process that takes you from registration, to login, and over to your grocery list in this codebase.
user.register()
.then(function() {
dialogsModule
.alert("Your account was successfully created.")
.then(function() {
frameModule.topmost().navigate("views/login/login");
});
}).catch(function(error) {
dialogsModule.alert({
message: error,
okButtonText: "OK"
});
});
}
Check your Firebase app to see your registration in the Registered Users section of the Login & Auth tab of the Firebase dashboard:
Note, when you login, you save your user's ID as generated by Firebase. You'll use that to create user-specific content in the Groceries collection that we'll work on next.
4. Delete Groceries
We now need to set up a listener for Firebase to check for fresh data coming in and going out of your app to start creating personal grocery lists. Replace theload()
function in app/shared/view-models/grocery-list-view-model.js
with this code://to get the index of an item to be deleted and handle the deletion on the frontendThis function sets up a "child event listener" to check for data coming in and out of the
function indexOf(item) {
var match = -1;
this.forEach(function(loopItem, index) {
if (loopItem.id === item.key) {
match = index;
}
});
return match;
}
function GroceryListViewModel(items) {
var viewModel = new observableArrayModule.ObservableArray(items);
viewModel.indexOf = indexOf;
viewModel.load = function() {
var onChildEvent = function(result) {
var matches = [];
if (result.type === "ChildAdded") {
if (result.value.UID === config.uid) {
viewModel.push({
name: result.value.Name,
id: result.key
});
}
} else if (result.type === "ChildRemoved") {
matches.push(result);
matches.forEach(function(match) {
var index = viewModel.indexOf(match);
viewModel.splice(index, 1);
});
}
};
return firebase.addChildEventListener(onChildEvent, "/Groceries").then(
function() {
console.log("firebase.addChildEventListener added");
},
function(error) {
console.log("firebase.addChildEventListener error: " + error);
}
)
};
/Groceries
collection in Firebase. If there is no such collection, it will be created by default. When the app detects a "child event" such as data being added to the list when the app is loaded, the app will check whether the config.uid and the UID
of the data - e.g. to whom it belongs - match, and then allow the data to populate the list.Note: the Firebase plugin exposes a child event listener and a value event listener. Use the value event listener to test for overwritten data, and the child event listener to check for additions and deletions to a given collection.
Adding data to this list is really simple. Overwrite the
add()
function in app/shared/view-models/grocery-list-view-model.js
:viewModel.add = function(grocery) {Replace the
return firebase.push( '/Groceries', {
'Name': grocery,
'UID': config.uid
});
};
delete()
function with equally simple code:viewModel.delete = function(index) {The add and delete functions push or remove items, tagged with the user's uid (for addition) and delineated by the item's id (for deletion), providing a quick way to manage the snappy collection management inherent to Firebase. The listeners that you set up when loading the page handle reshuffling data on the client side when a child event is detected. It's really fast!
var id = viewModel.getItem(index).id;
return firebase.remove("/Groceries/"+id+"");
};
What's next?
It will be a nice enhancement to allow file uploads and more social logins like Twitter, Facebook, and Google authentication, to be handled by this plugin. In addition, if you leverage the value listener and check for edited items on the front end, an editing functionality could be added to this app. The sky’s the limit with a NativeScript app powered by Firebase!Angular 2: an MIT Open Source Licensed Framework
Open source licenses are meant to protect developers by making it clear how code can be used. We want developers to be confident that they can use, fork, modify, and extend Angular without worry.
At Google we prefer to license new projects under the Apache 2 license because we feel it gives the most rights to developers and creates a strong legal scaffold for projects to grow and thrive. However, what we've heard from users in the Angular community is that you prefer the MIT license. It's more widely used within JavaScript projects, it's shorter, and it’s better understood.
So, we're changing Angular back to the MIT license.
Happy coding.
Sunday, 10 January 2016
Improve Your Google Page Speed Insights Score
Read more »