Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Syncing Data With Firebase Using Ionic Framework

TwitterFacebookRedditLinkedInHacker News

Lately there has been a lot of buzz about Firebase and now that it has been bought by Google, it is probably only going to get bigger. Firebase has a very thorough datastore API making it very useful for applications that can leverage JavaScript.

Now you might be asking yourself, well can’t I use the Dropbox datastore API that you mentioned in one of your previous articles? You absolutely can, however, Firebase is a far better solution when creating a service because the data isn’t sandboxed to a specific user like Dropbox.

In this example, we’re going to create a simple Apache Cordova Android and iOS application using Ionic Framework.

There are a few things we’ll need in order to make this application successful:

With this in mind, let’s start by creating security rules for our Firebase application. In your Firebase dashboard add the following:

{
    "rules": {
        "users": {
            ".write": true,
            "$uid": {
                ".read": "auth != null && auth.uid == $uid"
            }
        }
    }
}

The above basically says, allow all users to write to the users node, but only allow a matching authenticated user to read from the matching node. We do this so anyone can create a user account in our application, but users can only read from their own account.

Now it is time to create a fresh Ionic project for Android and iOS:

ionic start FirebaseExample blank
cd FirebaseExample
ionic platform add android
ionic platform add ios

Note that if you’re not on a Mac, you cannot add and build for the iOS platform.

This project doesn’t require any Apache Cordova plugins, so we can go straight into adding the required Firebase libraries into our project. Download the Firebase JavaScript library and the AngularFire library and import them into your js directory. Open your index.html file and include the following lines:

<script src="js/firebase.js"></script>
<script src="js/angularfire.min.js"></script>

Open your www/js/app.js file and include the Firebase directive in your angular.module method. It will look something like this:

var exampleApp = angular.module('starter', ['ionic', 'firebase']);

We are going to follow the AngularFire documentation pretty closely at this point. It can get a little confusing, but I’m going to try to ease you into it.

If you aren’t already familiar with the AngularJS UI-Router, I suggest you read my previous post because it will be used going forward.

To keep things simple, we are going to have only two screens / states:

  • Sign in / register
  • Allow for password based use. If the username does not exist, create an account and sign in. Otherwise just sign in.
  • Todo list
  • Add items to the todo list and view them

For efficiency we are going to make one object instance for Firebase. Outside the AngularJS code declare a variable we’ll use for Firebase. In the end, you should have something that looks like this:

var exampleApp = angular.module('starter', ['ionic', 'firebase']);
var fb = null;

exampleApp.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        if(window.cordova && window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
        }
        if(window.StatusBar) {
            StatusBar.styleDefault();
        }
        fb = new Firebase("https://example.firebaseio.com/");
    });
});

The fb variable will be used throughout our application. As I mentioned earlier, we are going to be using the AngularJS UI-Router, so make sure you’ve reviewed it.

exampleApp.config(function($stateProvider, $urlRouterProvider) {
    $stateProvider
    .state('login', {
        url: '/login',
        templateUrl: 'templates/login.html',
        controller: 'LoginController'
    })
    .state('todo', {
        url: '/todo',
        templateUrl: 'templates/todo.html',
        controller: 'TodoController'
    });
    $urlRouterProvider.otherwise('/login');
});

The above code will set up the states that we plan to use.

Remember that at this point your index.html file should look something like this:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
        <title></title>
        <link href="lib/ionic/css/ionic.css" rel="stylesheet">
        <link href="css/style.css" rel="stylesheet">
        <script src="lib/ionic/js/ionic.bundle.js"></script>
        <script src="cordova.js"></script>
        <script src="js/firebase.js"></script>
        <script src="js/angularfire.min.js"></script>
        <script src="js/app.js"></script>
    </head>
    <body ng-app="starter">
        <ion-pane>
            <ion-nav-bar class="bar-stable">
            </ion-nav-bar>
            <ion-nav-view></ion-nav-view>
        </ion-pane>
    </body>
</html>

We’re now going to jump straight into the design of our UI states. Let’s start with our login screen, so open templates/login.html and make it look like the following:

<ion-view title="Sign In">
    <ion-content>
        <div class="padding-left padding-top padding-right">
            <h1>Welcome</h1>
        </div>
        <div>
            <div class="list list-inset">
                <label class="item item-input">
                    <input ng-model="username" type="text" placeholder="Username" />
                </label>
                <label class="item item-input">
                    <input ng-model="password" type="password" placeholder="Password" />
                </label>
            </div>
            <div class="padding">
                <button ng-click="login(username, password)" class="button button-block button-stable">Sign In</button>
                <button ng-click="register(username, password)" class="button button-block button-dark">Register</button>
                <p>
                    Sign into this application
                </p>
            </div>
        </div>
    </ion-content>
</ion-view>

For simplicity purposes we are going to be using Firebase username and password authentication which is why there is a username and password field rather than a Facebook or Twitter authentication button. Based on the state configuration and the login(email, password) and register(email, password) methods found in the login.html file, we will end up with the following in our app.js file:

exampleApp.controller("LoginController", function($scope, $firebaseAuth, $location) {

    $scope.login = function(username, password) {
        var fbAuth = $firebaseAuth(fb);
        fbAuth.$authWithPassword({
            email: username,
            password: password
        }).then(function(authData) {
            $location.path("/todo");
        }).catch(function(error) {
            console.error("ERROR: " + error);
        });
    }

    $scope.register = function(username, password) {
        var fbAuth = $firebaseAuth(fb);
        fbAuth.$createUser({email: username, password: password}).then(function() {
            return fbAuth.$authWithPassword({
                email: username,
                password: password
            });
        }).then(function(authData) {
            $location.path("/todo");
        }).catch(function(error) {
            console.error("ERROR " + error);
        });
    }

});

The register(username, password) method follows the official AngularFire documentation pretty much exactly. The login(username, password) method is just a stripped version of the register method that we used. It just doesn’t contain the $createUser code.

Essentially what the register method does is create a user to the application and then sign that user in. The login method just signs the user in.

When the application has redirected from the login state to the todo state, the user will see a list of items defined with the following UI code in the templates/todo.html file.

<ion-view title="Todo" ng-init="list()">
    <ion-nav-buttons side="right">
        <button class="right button button-icon icon ion-plus" ng-click="create()"></button>
    </ion-nav-buttons>
    <ion-content>
        <div class="list">
            <div ng-repeat="todo in data.todos" class="item">
                {{todo.title}}
            </div>
        </div>
    </ion-content>
</ion-view>

Upon first launch, the user won’t have any items in there TODO list. This is why we have the create() method one of our navigation buttons. We’ll get into this more, soon. In the mean time, add the following controller to your app.js file.

exampleApp.controller("TodoController", function($scope, $firebaseObject, $ionicPopup) {

    $scope.list = function() {

    }

    $scope.create = function() {

    }

});

Both these methods are used in the templates/todo.html file where list() initializes the Firebase data and create() adds to the list. Let’s start by looking at the list() method since it is the first thing fired when the screen displays.

$scope.list = function() {
    fbAuth = fb.getAuth();
    if(fbAuth) {
        var syncObject = $firebaseObject(fb.child("users/" + fbAuth.uid));
        syncObject.$bindTo($scope, "data");
    }
}

Before trying to get any data, we need to validate that we are signed into an existing Firebase account. This was done using the getAuth() method, however, notice that I didn’t use the AngularFire version of this method. It is up to you, but there does exist a $firebaseAuth.$getAuth() version of this method. Either will work fine.

If the user is signed in, we need to get the unique id found in the fbAuth.uid property. As per the official AngularFire documentation, we are going to create an object out of the users/auth.uid that will be synchronized consistently in two directions. The auto-synchronizing object we create will be found as $scope.data and can be used like any AngularJS object.

Now let’s take a look at the create() method for adding new items to the list:

$scope.create = function() {
    $ionicPopup.prompt({
        title: 'Enter a new TODO item',
        inputType: 'text'
    })
    .then(function(result) {
        if(result !== "") {
            if($scope.data.hasOwnProperty("todos") !== true) {
                $scope.data.todos = [];
            }
            $scope.data.todos.push({title: result});
        } else {
            console.log("Action not completed");
        }
    });
}

For simplicity we are only going to use an $ionicPopup rather than creating a new screen for adding list items. By default, when you create a new user, no data will exist in the database until you have something to save. The goal is to have our database structure look like the following:

{
    example: {
        users: {
            auth.uid: {
                todos: [
                    0: {
                        title: "value"
                    },
                    1: {
                        title: "value"
                    }
                ]
            },
            auth.uid: {

            }
        }
    }
}

Per the auto-synchronizing object that we created, $scope.data represents example/users/auth.uid, but like I said, first time users don’t even have anything in the database yet beyond this. This is why we check to see if the todos property exists and if it doesn’t, make a blank array out of it. Then we can proceed in pushing new array elements to it.

Just like that you have data that can be synchronized across devices and platforms. Everything is handled for you with the Firebase library.

A video version of this article can be seen below.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.