December 01, 2015

How to Set Up Apache Cordova Facebook Plugin and Parse in Ionic Framework

This is an updated version of this previous article. I will keep it short and concise here. If you find it hard to follow, you can refer to the previous article as it comes with more in-depth elaboration.

Previous article can be found here. Sample codes are available in GitHub for reference.

Note that this article is meant for Ionic Framework V1


Step 1: Set up a Facebook App ID


Step 2: Clone the official Apache Cordova Facebook Plugin

Installing this plugin directly from Cordova Registry currently breaks the symlinks in FacebookSDK.framework CB-6092. Easiest solution for now is to just git clone this project and install it with Cordova CLI using the local clone.

Source: Github

Usually, we don’t have to do this to install a plugin. But a long-standing bug is causing some issue with the Facebook SDK if we add the plugin through command line like how we usually do.

No worries, the work around is easy.

git clone https://github.com/Wizcorp/phonegap-facebook-plugin.git

Remember the path to this plugin as we will need it shortly.


Step 3: Create a new Ionic App and install the Cordova Facebook Plugin

Now we are going to create a new Ionic App called demoapp.

$ ionic start demoapp blank
$ cd demoapp
$ ionic platform add ios
$ cordova -d plugin add /path/to/phonegap-facebook-plugin --variable APP_ID="12345678901234567890" --variable APP_NAME="demoapp"
$ cp plugins/phonegap-facebook-plugin/facebookConnectPlugin.js www/lib

Remember to replace APP_ID and APP_NAME with your own Facebook App credentials.


Step 4: Download Parse Javascript SDK


Step 5: Modify www/index.html

Step 5.1: Include Parse Javascript SDK

Now that we have the Parse SDK, let’s add it into our Ionic App. Open up www/index.html and you will see the following codes within <head></head>:

<!-- cordova script (this will be a 404 during development) -->
<script src="cordova.js"></script>

<!-- Parse Javascript SDK goes here -->

<!-- your app's js -->
<script src="js/app.js"></script>

Initialize and include the Parse SDK into <head></head>. Remember to replace the PARSE_APP_ID and PARSE_JAVASCRIPT_KEY with your own as provided by Parse.

<!-- Parse Javascript SDK goes here -->
<script src="lib/parse-1.6.12.min.js"></script>
<script>Parse.initialize("PARSE_APP_ID", "PARSE_JAVASCRIPT_KEY");</script>

Step 5.2: Inject Angular UI Router

Angular UI Router is what we will use to manage our different states. Think of states as different web pages of an Ionic App, similar to how websites are.

To use Angular UI Router, look for <ionpane></ionpane>, and replace the contents with the following codes:

<ion-pane>
  <div ui-view></div>
</ion-pane>

This way, the UI Router will manage the states and inject the necessary HTML codes into ui-view as necessary, which you will see later.

Step 5.3: Include Cordova Facebook Plugin

Right before </body>, or the body closing tag, add the following:

<div id="fb-root"></div>
<script src="lib/facebookConnectPlugin.js"></script>

The reason we put the Facebook Connect Plugin at the very last is to prevent a appendChild error, which you will see in your console if you put this plugin within <head></head>. Seems like it is required for fb-root to be loaded first before the plugin is added.

It is worth noting that this plugin itself contains the Facebook SDK, so there is no need to also include the Facebook Javascript SDK into your app.


Step 6: Create new HTML pages for our demoapp

Now we are starting to get into the codes. Let’s warm up and deal with some easy HTML codes first.

Step 6.1: New www/home.html page

This is our homepage. Since this is a demoapp, we have only very simple HTML that will be injected by the UI Router when needed.

On this page, we show a welcome message and a logout button. Only a logged in user can see this page (we will come to the authentication part later).

<ion-header-bar class="bar-stable">
    <h1 class="title">DemoApp</h1>
</ion-header-bar>
<ion-content>
    <div style="padding: 10px;">
        Welcome to DemoApp!
        <button class="button button-block button-positive" ng-click="logout()">
            Log out from Facebook
        </button>
    </div>
</ion-content>

Step 6.2: New www/login.html page

This is our login page. It comes with a login button that will trigger the Facebook Login action when clicked.

<ion-header-bar class="bar-stable">
    <h1 class="title">Login</h1>
</ion-header-bar>
<ion-content>
    <div style="padding: 10px; height: 100%;">
          <button class="button button-block button-positive" ng-click="login()">
            Log in with Facebook
          </button>
    </div>
</ion-content>

Step 7: Update www/js/app.js

Here, we are going to start delving into the Javascript codes.

Step 7.1: Add authentication checking for each state

Add $rootScope and $state into the .run() method.

.run(function($ionicPlatform, $rootScope, $state) {
    ...
}

The app will be listening to every state change, and check for authentication. If a state requires authentication (by checking data.authenticate) and the user is not logged in (!Parse.User.current()), then the user will be redirected to a state called login, which we will define shortly.

Here, toState.data.authenticate is a boolean parameter that is defined by us under the state configuration section that comes later; Parse.User.current() is a Parse function that checks if a user is currently logged in.

.run(function($ionicPlatform, $rootScope, $state) {
    ...

    // UI Router Authentication Check
    $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
          if (toState.data.authenticate && !Parse.User.current()) {
            // User isn’t authenticated
            $state.transitionTo("login");
            event.preventDefault();
          }
    });
}

Step 7.2: Add state configurations

Next, we will add the state configurations right after the .run() method.

Here we defined 3 states, ie root, home and login, which are accessible via localhost:8100, localhost:8100/#/home, localhost:8100/#/login. Because of the additional /#/ hash in the URL, I like to add a redirection for my root URL to my home URL, which you will see later.

Otherwise, the configurations are very straight forward. We define the controller and view file that we want Angular UI Router to inject for each state. We also define our own custom parameter under data.authenticate, which is used to define if authentication is required for each of the state.

Remember our authentication checking added above? toState.data.authenticate looks for this parameter to determine if a state requires authentication before it is displayed.

.run(function($ionicPlatform, $rootScope, $state) {
    ...
};

.config(function($stateProvider, $urlRouterProvider){
    $stateProvider
        .state('root', {
            url: '',
            controller: 'rootCtrl',
            data: {
                authenticate: false
            }
        })
        .state('home', {
            url: '/home',
            templateUrl: 'home.html',
            controller: 'homeCtrl',
            data: {
               authenticate: true
            }
        })
        .state('login', {
            url: '/login',
            templateUrl: 'login.html',
            controller: 'loginCtrl',
            data: {
                authenticate: false
            }
        })
    ;

    // Send to login if the URL was not found
    $urlRouterProvider.otherwise('/login');
})

Step 8: Add in the controllers for our states

These codes should continue to go at the bottom for www/js/app.js

Step 8.1: Add in the new rootCtrl

As mentioned, root state doesn’t do much but redirect users to the home state. I redirect it as such because I noticed some oddities when dealing with root URL in Angular UI Router. Ie, if a user is logged in and is redirected to the root URL, the URL as shown in the browser does not change.

Not sure if it is an intended feature, but I prefer to minimize surprises.

.controller('rootCtrl', ['$state', function($state) {
    $state.go('home');
}])

Step 8.2: Add in the new homeCtrl

The homeCtrl is a very simple controller. The one thing that we have to manage here is the logout action. When a user clicks the logout button, Parse.User.logOut() will be triggered, which will immediately invalidate Parse.User.current() that is used in the .run() method to check for authentication.

Note that while we have invalidated user’s Parse session, user’s Facebook session remains active. If a user wants to login again, he will not be prompted for password and will be logged in immediately.

It is upto you if this is an intended behaviour. You may use facebookConnectPlugin.logout() to terminate the Facebook session too if you desire.

.controller('homeCtrl', ['$scope', '$state', function($scope, $state) {
      $scope.logout = function() {
          console.log('Logout');
          Parse.User.logOut();
          // facebookConnectPlugin.logout();
          $state.go('login');
      };
}])

Step 8.3: Add in the new loginCtrl

The login controller is where all the magic of Facebook Login is happening. If you want a step-by-step breakdown of what each line of codes does in this section, feel free to refer to my previous article — How to set up Facebook Connect Plugin and Parse.com in Ionic / Phonegap.

IMPORTANT: Remember to replace FACEBOOK_APP_ID with your own Facebook App ID

.controller('loginCtrl', ['$scope', '$state', function($scope, $state) {
    var fbLogged = new Parse.Promise();

    var fbLoginSuccess = function(response) {
        if (!response.authResponse){
            fbLoginError("Cannot find the authResponse");
            return;
        }
        var expDate = new Date(
            new Date().getTime() + response.authResponse.expiresIn * 1000
        ).toISOString();

        var authData = {
            id: String(response.authResponse.userID),
            access_token: response.authResponse.accessToken,
            expiration_date: expDate
        }
        fbLogged.resolve(authData);
        fbLoginSuccess = null;
        console.log(response);
    };

    var fbLoginError = function(error){
        fbLogged.reject(error);
    };

    $scope.login = function() {
        console.log('Login');
        if (!window.cordova) {
            facebookConnectPlugin.browserInit('FACEBOOK_APP_ID');
        }
        facebookConnectPlugin.login(['email'], fbLoginSuccess, fbLoginError);

        fbLogged.then( function(authData) {
            console.log('Promised');
            return Parse.FacebookUtils.logIn(authData);
        })
        .then( function(userObject) {
            var authData = userObject.get('authData');
            facebookConnectPlugin.api('/me', null,
                function(response) {
                    console.log(response);
                    userObject.set('name', response.name);
                    userObject.set('email', response.email);
                    userObject.save();
                },
                function(error) {
                    console.log(error);
                }
            );
            facebookConnectPlugin.api('/me/picture', null,
                function(response) {
                    userObject.set('profilePicture', response.data.url);
                    userObject.save();
                },
                function(error) {
                    console.log(error);
                }
            );
            $state.go('home');
        }, function(error) {
            console.log(error);
        });
    };
}])

Step 9: Let’s start Ionic

Now that we are up this far, we are ready to start Ionic and check our progress. In terminal, at your app’s root directory, type:

$ ionic serve

It should bring up your browser and loads http://localhost:8100, but since we are not authenticated, the app is supposed to redirect you to http://localhost:8100/#/login, which is our Login page. Is it working for you?

Now click the login button!


Step 10: Common errors

Error 1: “TypeError: Cannot read property ‘browserInit’ of undefined”

If you follow this guide, you are guaranteed to hit this error message when clicking on the login button during your browser test. Reason is that the Cordova plugin is not available when the app is launched in browser.

An easy work around is to change this line of codes in www/lib/facebookConnectPlugin.js (should be line 12 but please double-check):

if (cordova.platformId == "browser") {

To:

if (!window.cordova || cordova.platformId == "browser") {

Basically, if we can’t find Cordova under window.cordova, we are going to assume that this is the browser and start executing the codes below the conditions.

Error 2: “Given URL is not allowed by the Application configuration”

The full error would be as followed:

Given URL is not allowed by the Application configuration: One or more of the given URLs is not allowed by the App’s settings. It must match the Website URL or Canvas URL, or the domain must be a subdomain of one of the App’s domains.

This is a simple fix. This is basically Facebook telling you that your Facebook App is not setup correctly. So let’s set it up.

Try again now and this error should go away!

Error 3: “Uncaught ReferenceError: module is not defined”

This error does not cause any problem. If it annoys you however, open up www/lib/facebookConnectPlugin.js and comment out the 2 instances of these:

module.exports = facebookConnectPlugin;

The error would go away then.


Step 11: Update Facebook App settings when building for iOS

When you are finally building this into an iOS app, you will need to add additional settings in Facebook for Developers for this to work.

There was supposed to be a screenshot here but I lost it upon migrating to this new blog sorry.


The end…

That should be all. Leave a comment below and tell me if this worked for you.

***

Hello! My name is Jian Jye and I work on Laravel projects as my main job. If my article was helpful to you, a shoutout on Twitter would be awesome! I'm also available for hire if you need any help with Laravel. Contact me.