Anda di halaman 1dari 26

CreatingaFirebasePoweredEndtoEndIonicApplication

sitepoint.com /creatingfirebasepoweredendendionicapplication/
6/27/2014
Technologyhascomealongwaysincemankindusedrockstostartafire.TherewasatimewhentheInternetwasmeanttoserveHypertextdocumentsacross
afewmachines.Buttoday,wehavereachedacomplexstatewhereyourheartrateismonitoredbyadeviceandthentransmittedovertoyourcomputer.Andiftheheart
rateisnotnormal,youmightevenseeanAmbulancewaitingattheendofyoursprint.

Thisisprettymuchhowwelivethesedays.Andtopowersuchamazingfeatures,weneedtohaveamazingtechnologies.Inthispost,wearegoingtodiscusstwosuch
bleedingedgetechnologies,theIonicFrameworkandFirebase.

WhatistheIonicFramework?

IonicisapowerfulAngularJSdrivenmobilewebframeworkthatmakesbuildinghybridmobileapplicationeasy.Notonlydoesithavethepoweroftwowaydatabinding,
ithasanawesomeinterfaceforworkingwithRESTfulAPIs.ThismakesIonicaperfectmatchfordevelopingappsandkeepingtheminsyncacrossdevices.

WhatisFirebase?

Thereweretimeswhenprovisioningamachinetodeployasimplewebsitewouldtakeweeks.Then,alongcameAmazon.YousimplytellAmazonwhatkindofsystem
youwant,anditprovidesaserverforyou.Next,wehaveseentheriseofHeroku,whichprovidesaPaaS(PlatformAsAService)tohostyourapplications.Thisallowed
developerstofocusmoreontheapplication,insteadofworryingaboutprovisioninganddeployingtheapplication.Andfinally,wehaveFirebase,whichisaselfsufficient
ServerasaServicedrivenbyaNoSQLdatastore.InFirebase,allyouneedtodoisdefineacollectionofdata,andFirebasewilltakecareofexposingitasRESTful
APIforyou.

BucketlistApplication

IhavewrittenanarticlenamedIonicRestifyMongoDBAnEndtoEndHybridApp ,whichexplainshowtobuildanendtoendhybridappusingRestifyandMongoDBas
theAPIserverandIonicasthehybridclient.Inthispost,wewillseehowwecancompletelyeliminatetheAPIserverlayerwithFirebase.

TheBucketlistapplicationwearegoingtobuildwillhaveanauthenticationlayer,allowingtheuserstoregisterandlogin.Onceauthenticated,theuserisgiventheoption
tocreateanewbucketlistitem.

Theprimaryviewoftheapplicationshowsalistofincompleteitemsandasecondaryviewtoshowthelistofcompleteditems.Theuserwillhaveanoptiontomarkan
itemascompleteordeleteit.

Beforewestartbuildingtheapplication,youshould:

ApplicationArchitecture
Ourapplicationwillprimarilyconsistoftwolayers.Thefirstistheclient(inourcase,theIonicApp,butthiscouldbeanyotherclientthatcanconsumeaRESTfulAPI),
andthesecondistheserver(Firebase).

Asyoucanseefromtheabovediagram,ontheclientsidewehaveanAngularfirelayerwhichinteractswithFirebaseandactsasaservicelayerfortheIonicapplication.
ItisthislayerthatgivesthepowertokeepthedatainsyncbetweenFirebaseandourIonicclient.

OntheFirebaseend,wewillconfigureasimplelogintotakecareoftheauthentication.

OurIonicapplicationwillhavefivekeycontrollers:

1.Signupcontroller
2.Signincontroller
3.Createnewitemcontroller
4.Showincompleteitemscontroller
5.Showcompleteditemscontroller

Apartfromthat,wewillhaveacoupleofmethodsthatwilltakecareofmarkinganitemascompleteanddeletetheitem.

DesigningtheDataStructure

Firebaseisideallyusedforrealtimedatasynchronization,wheremultipleclientsacrosstheglobeareexpectedtoseethesamedataatalmostthesamemoment.This
isnotthecasewithourapp.Wearereallynotlookingforamultidevicesync.AllweneedisforFirebasetotakecareofmanagingourbucketlistdataforus.

TheawesomepartofFirebaseisthatitprovidesanauthenticationAPIoutofbox.Allweneedtodoisenableitandincludetheclient,andFirebasewilltakecareofthe
restforus.

Forthebucketlistcollection,weneedarelationbetweentheuserandabucketlistItem,kindoflikeaforeignkey.Thiswillenableustoshowbucketlistitemscreatedonly
bytheuser.

Asamplebuckletlistcollectionisshownbelow:

"BucketListCollection":
[{
"item": "test",
"isCompleted": false,
"user": "test@bla.com",
"created": 1400801853144,
"updated": 1400801853144
}, {
"item": "tes message",
"isCompleted": false,
"user": "test@bla.com",
"created": 1401008504927,
"updated": 1401008504927
}, {
"item": "Just to check",
"isCompleted": true,
"user": "test@bla.com",
"created": 1401008534451,
"updated": 1401008534451
}, ....]
IntheabovesampleJSON,theuserkeyholdsthelinkbetweentheloggedinuserandtheiritems.So,whenwefetchthedata,wefetchtherecordsthatmatchthe
loggedinuser.AndthisishowwerepresentthequeryusingaRESTfulendpoint:

https://bucketlist-app.firebaseio.com/bucketList/test@bla.com

Unfortunately,thereisnoeasywaytoimplementthisinFirebase.

AsperthisStackOverflowpost,therearethreeways:

Uselocationnamesandprioritiesintelligently.
Doclientsidequerying.
Runaseparateserver.

TheseapproacheswerekindofanoverkillforasimpleAPI.Then,IstumbledacrossthisStackOverflowpost,thatmentionshowyoucanflipthedatastructuretobe
moreusercentricthanfeaturecentric.SoIchangedtheappdatastructureasshownbelow.

"test@bla,com" : [{
"item": "test",
"isCompleted": false,
"created": 1400801853144,
"updated": 1400801853144
}, {
"item": "tes message",
"isCompleted": false,
"created": 1401008504927,
"updated": 1401008504927
}....]

"test2@bla,com" : [{
"item": "test2",
"isCompleted": false,
"created": 14008012853144,
"updated": 14008012853144
}, {
"item": "tes message2",
"isCompleted": false,
"created": 14010028504927,
"updated": 14010028504927
}....]

Now,everyuserhastheirowncollection,ratherthanacommonbucketlistcollection,whichmakesmoresenseinourapplication.So,wewillbeusingthisstructurefor
managingourdata.AndourURLswilllooklikethis:
https://bucketlist-app.firebaseio.com/test@bla,com

Note:Iamnot100%sureifalargeuserbasewouldaffecttheoverallresponsetimeforasinglequery(moreusers=morecollections).

SetupFirebase

Wehaveagoodideaastowhereweareheadedto.OurfirststepwouldbetosetupaFirebaseaccount,createanewFirebaseapplicationinstance,andconfigurethe
authenticationforit.

NavigatetoFirebase.comandcreateanewaccountifyoudonothaveone.Next,navigatetotheAccountspageandcreateanewapp.Providethedesirednameand
URL.Oncetheappiscreated,clickontheappnametonavigatetothedataandconfigurationpage.Thisisabirdseyeviewofthebackend.Feelfreetobrowsearound
beforeyoucontinue.

Next,wewillsetupauthenticationforourapplication.ClickontheSimpleLogintabonthelefthandsideofthepage,andinthemaincontentareayouwillseethe
availableoptions.UndertheAuthenticationProviderssectionclickonEmailandPasswordandthenChecktheEnabledcheckbox.ThiswillsetuptheSimpleLoginfor
us.

SetupanIonicProject

Next,wewillscaffoldanewIonicapplicationfromablanktemplateusingtheIoniccommandlineinterface(CLI).CreateanewfoldernamedmyIonicFireAppand
openterminal/prompthere.FirstwewillinstallCordovaandIonic.Executethefollowingcommand:

$ npm i -g cordova ionic

Next,wewillscaffoldanewIonicapp.Generally,Iliketokeepmycodeorganized.Sincethisisatestappandwearenotgoingtouseanyversioncontroltomanage
developmentandproduction,wewillcreatetwofolders,myIonicFireApp/devandmyIonicFireApp/prod.Thisstepisoptionalandtotallyapreference.Next,cd
intothedevfolder(ifyouhavecreatedone)andrunthefollowingcommand:

$ ionic start bucketListApp blank

bucketListAppisthenameoftheapplication.ThiswillscaffoldtheIonic+PhoneGaptemplateforus.Oncethesetupisdone,thefirstorderofbusinessistomove
theconfig.xmlfromthebucketListAppfoldertowwwfolder(APhoneGapbuildrequirement).

Nextopenupconfig.xmlinyourfavoriteeditorandupdatethewidgetID,name,description,andauthorfields.Thesewillbethemetadataforyourapp,whenitrun
throughPhonegapBuild.Theupdatedfilewouldlooklike:
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.ionicfire.bucketlist" version="0.0.1" xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>BucketList App</name>
<description>An Awesome App</description>
<author email="hi@bucketlist.com" href="http://bucketlist.com/">Arvind Ravulavaru</author>
<content src="index.html" />
<access origin="*" />
<preference name="fullscreen" value="true" />
<preference name="webviewbounce" value="false" />
<preference name="UIWebViewBounce" value="false" />
<preference name="DisallowOverscroll" value="true" />

<preference name="BackupWebStorage" value="none" />


<feature name="StatusBar">
<param name="ios-package" value="CDVStatusBar" onload="true" />
</feature>
</widget>

RefertoPhoneGap3CLISetuponMacandWindowstocompletelyunderstandandsetupPhoneGaponWindowsandMac.

ToaddiOSplatformsupport(Maconly),runthefollowingcommand:

$ ionic platform add ios

ToaddAndroidplatformsupport,runthefollowingcommand:

$ ionic platform add android

Next,wewillbuildtheapp,byrunning:

$ ionic platform build ios

or

$ ionic platform build ios

Next,toemulatetheapp,execute:
$ ionic emulate ios

or

$ ionic emulate android

Youcanusetheaboveapproachtotestyourcode.But,youneedtobuildthecodefortherespectiveplatformseverytimeyoumakechangestothecodeinwwwfolder.

Givenmylaziness,Iwillneverdothat.TheIonicprojectcomepackedwithGulpsupport.Letustakeadvantageofthat.Backintheterminal,executethefollowing
command:

$ npm install

Thiswillinstallallthedependencieslistedinpackage.json.Next,installgulpconnectusingthecommand:

$ npm install gulp-connect --save

Then,openupgulfile.js,presentattherootofbucketListAppfolderandreplaceitwiththefollowingcode:

var gulp = require('gulp');


var gutil = require('gulp-util');
var bower = require('bower');
var concat = require('gulp-concat');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var sh = require('shelljs');
var connect = require('gulp-connect');

var paths = {
sass: ['./scss/**/*.scss'],
www : ['www/**/*.*']
};

gulp.task('default', ['sass']);
gulp.task('serve', ['connect', 'watch']);

gulp.task('sass', function(done) {
gulp.src('./scss/ionic.app.scss')
.pipe(sass())
.pipe(sass())
.pipe(gulp.dest('./www/css/'))
.pipe(minifyCss({
keepSpecialComments: 0
}))
.pipe(rename({ extname: '.min.css' }))
.pipe(gulp.dest('./www/css/'))
.on('end', done);
});

gulp.task('reload', function () {
return gulp.src(['www/index.html'])
.pipe(connect.reload());
});

gulp.task('watch', function() {

gulp.watch([paths.www], ['reload']);
});

gulp.task('install', ['git-check'], function() {


return bower.commands.install()
.on('log', function(data) {
gutil.log('bower', gutil.colors.cyan(data.id), data.message);
});
});

gulp.task('git-check', function(done) {
if (!sh.which('git')) {
console.log(
' ' + gutil.colors.red('Git is not installed.'),
'\n Git, the version control system, is required to download Ionic.',
'\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
'\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
);
process.exit(1);
}
done();
});

gulp.task('connect', function() {
connect.server({
root: 'www',
port: '1881',
livereload: true
});
});
Backintheterminal,run:

$ gulp serve

Thiswillspinupaserver.Now,allyouneedtodoisopenhttp://localhost:1881andobserve!.

Notethatcordova.jswillbea404duringdevelopment.And,sinceweaddedlivereloadsupport,allyouneedtodoismakechangesandswitchtoyourbrowsertosee
thechanges.

Note:Ifyouarebuildinganappwithnativepluginslikecontactsorcamera,thisapproachwillnotwork!Youneedtodeploytheapptothedevicetotestit.

OurIonicappsetupisdone.letusgetbuildingtheactualapp.

IonicandFirebase

Thefirstthingwearegoingtodoisopenwww/index.htmlandaddtherequiredFirebase,AngularFireandFirebasesimpleloginJavaScriptreferences.

<script src="https://cdn.firebase.com/v0/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/0.5.0/angularfire.min.js"></script>
<script src="https://cdn.firebase.com/v0/firebase-simple-login.js"></script>

TheyarepointedtotheCDN,butyoucandownloadthefilesandserverthemlocallytoo.Nextupdatetheng-appdirectivevalueonthebodytagfromstarterto
bucketList.Thiswillbeourmodulename.Finally,wewilladdBackbuttonsupport.Addthefollowingcodetothepagebody:

<ion-nav-bar class="bar-stable nav-title-slide-ios7">


<ion-nav-back-button class="button-icon icon ion-chevron-left">
Back
</ion-nav-back-button>
</ion-nav-bar>

Thecompletedwww/index.htmlwilllooklike:
<!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="https://cdn.firebase.com/v0/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/0.5.0/angularfire.min.js"></script>
<script src="https://cdn.firebase.com/v0/firebase-simple-login.js"></script>

<script src="cordova.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-app="bucketList" animation="slide-left-right-ios7">
<ion-nav-bar class="bar-stable nav-title-slide-ios7">
<ion-nav-back-button class="button-icon icon ion-chevron-left">
Back
</ion-nav-back-button>
</ion-nav-bar>
<ion-nav-view></ion-nav-view>
</body>
</html>

Noticethatwehaveaddedareferencetocontrollers.js.Wewillresolvethatinamoment.Ifyougobacktothebrowserandcheckthedeveloperconsoleyouwillseea
coupleof404sandanUncaught objecterror.TheUncaught objecterrorisbecause,wehaveupdatedtheng-appdirectiveinindex.htmlbutnotin
www/js/app.js.Youcankillthegulptask,aswearegoingtomakequiteafewchanges.Onceeverythingisdone,wecanrelaunchtheserver.

Openwww/js/app.jsinyourfavoriteeditor.First,letusupdatethemodulename.Thenwewilladdacoupleofdependencies.Updatetheexistingmoduledeclaration
with:

angular.module('bucketList', ['ionic', 'firebase', 'bucketList.controllers'])

Theprimarydependencyisionic,nextfirebase,andfinallythecontrollers.

Todevelopourapplication,wearegoingusetwopairsofiontabscomponent.ThefirstsetoftabswillbeusedtoshowLogin&Registerscreensandthesecondsetof
tabswillbeusedtoshowincompletebucketlistitemsandcompletedbucketlistitemsscreens.
Wearegoingtowrapourtabsinanotherabstracttabtogainmorecontrol.Thiswillbringourtotalroutescounttosix.Insidetherunmethod,wewillinjectacoupleof
variablesandmethodsintothe$rootScopevariable.ThatwouldincludetheFirebaseinstanceURL,acheckSession,logoutandloadersforbetterUX.Thefinal
app.jswouldbe

angular.module('bucketList', ['ionic', 'firebase', 'bucketList.controllers'])

.run(function($ionicPlatform, $rootScope, $firebaseAuth, $firebase, $window, $ionicLoading) {


$ionicPlatform.ready(function() {

if (window.cordova && window.cordova.plugins.Keyboard) {


cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
StatusBar.styleDefault();
}

$rootScope.userEmail = null;
$rootScope.baseUrl = 'https://bucketlist-app.firebaseio.com/';
var authRef = new Firebase($rootScope.baseUrl);
$rootScope.auth = $firebaseAuth(authRef);

$rootScope.show = function(text) {
$rootScope.loading = $ionicLoading.show({
content: text ? text : 'Loading..',
animation: 'fade-in',
showBackdrop: true,
maxWidth: 200,
showDelay: 0
});
};

$rootScope.hide = function() {
$ionicLoading.hide();
};

$rootScope.notify = function(text) {
$rootScope.show(text);
$window.setTimeout(function() {
$rootScope.hide();
}, 1999);
};
$rootScope.logout = function() {
$rootScope.auth.$logout();
$rootScope.checkSession();
};

$rootScope.checkSession = function() {
$rootScope.checkSession = function() {
var auth = new FirebaseSimpleLogin(authRef, function(error, user) {
if (error) {

$rootScope.userEmail = null;
$window.location.href = '#/auth/signin';
} else if (user) {

$rootScope.userEmail = user.email;
$window.location.href = ('#/bucket/list');
} else {

$rootScope.userEmail = null;
$window.location.href = '#/auth/signin';
}
});
}
});
})

.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('auth', {
url: "/auth",
abstract: true,
templateUrl: "templates/auth.html"
})
.state('auth.signin', {
url: '/signin',
views: {
'auth-signin': {
templateUrl: 'templates/auth-signin.html',
controller: 'SignInCtrl'
}
}
})
.state('auth.signup', {
url: '/signup',
views: {
'auth-signup': {
templateUrl: 'templates/auth-signup.html',
controller: 'SignUpCtrl'
}
}
})
.state('bucket', {
url: "/bucket",
abstract: true,
templateUrl: "templates/bucket.html"
})
.state('bucket.list', {
url: '/list',
url: '/list',
views: {
'bucket-list': {
templateUrl: 'templates/bucket-list.html',
controller: 'myListCtrl'
}
}
})
.state('bucket.completed', {
url: '/completed',
views: {
'bucket-completed': {
templateUrl: 'templates/bucket-completed.html',
controller: 'completedCtrl'
}
}
})
$urlRouterProvider.otherwise('/auth/signin');
});

NoticethatweinitializetheFirebaseAuthserviceusingthiscode:

$rootScope.baseUrl = 'https://bucketlist-app.firebaseio.com/';
var authRef = new Firebase($rootScope.baseUrl);
$rootScope.auth = $firebaseAuth(authRef);

DonotforgettoreplacebaseURLwithyourFirebaseInstance

Now,letsbuildcontrollers.js.Createanewfileatwww/jsandnameitcontrollers.js.Asthenamesuggests,thisfilewillholdallthecontrollers.Next,creatanewfolder
namedtemplates.Wewillpopulateeachtemplateaswegoalong.

First,wehavetheSignupcontroller.Letscreatetherequiredtemplatesfirst.Createanewfilenamedauth.htmlinthetemplatesfolder.Thiswillbetheabstracttabfor
theSigninandSignuptabs.Fillitwiththefollowingcode:

<ion-tabs class="tabs-icon-top">
<ion-tab title="Sign In" icon-on="ion-ios7-locked"
icon-off="ion-ios7-locked-outline" href="#/auth/signin">
<ion-nav-view name="auth-signin"></ion-nav-view>
</ion-tab>
<ion-tab title="Sign Up" icon-on="ion-ios7-personadd"
icon-off="ion-ios7-personadd-outline" href="#/auth/signup">
<ion-nav-view name="auth-signup"></ion-nav-view>
</ion-tab>
</ion-tabs>
Next,letsaddtheSignuptemplate.Createanewfilenamedauthsignup.htmlinsidethetemplatesfolderandaddthefollowingcode:

<ion-header-bar class="bar-positive">
<h1 class="title">Sign Up</h1>
</ion-header-bar>
<ion-content class="has-header padding">
<div class="list">
<label class="item item-input">
<span class="input-label">Email</span>
<input type="text" ng-model="user.email">
</label>
<label class="item item-input">
<span class="input-label">Password</span>
<input type="password" ng-model="user.password">
</label>
<label class="item item-input">
<button class="button button-block button-positive" ng-click="createUser()">
Sign Up
</button>
</label>
</div>
</ion-content>

Whentheuserclickssubmit,wecallcreateuser().Thecontrollerlookslikethis:
angular.module('bucketList.controllers', [])
.controller('SignUpCtrl', [
'$scope', '$rootScope', '$firebaseAuth', '$window',
function ($scope, $rootScope, $firebaseAuth, $window) {
$scope.user = {
email: "",
password: ""
};
$scope.createUser = function () {
var email = this.user.email;
var password = this.user.password;

if (!email || !password) {
$rootScope.notify("Please enter valid credentials");
return false;
}
$rootScope.show('Please wait.. Registering');
$rootScope.auth.$createUser(email, password, function (error, user) {
if (!error) {
$rootScope.hide();
$rootScope.userEmail = user.email;
$window.location.href = ('#/bucket/list');
}
else {
$rootScope.hide();
if (error.code == 'INVALID_EMAIL') {
$rootScope.notify('Invalid Email Address');
}
else if (error.code == 'EMAIL_TAKEN') {
$rootScope.notify('Email Address already taken');
}
else {
$rootScope.notify('Oops something went wrong. Please try again later');
}
}
});
}
}
])

Thingstonotice:

1.$rootScope.show(),$rootScope.hide(),and$rootScope.notify()aredefinedinapp.jstoshowtheloadingoverlay.
2.$rootScope.auth.$createUser()isresponsibleforinteractingwithFirebaseandcreatinganewuser.
3.NoticethevariouserrorsmessagethatisreturnedbyFirebase.Youcanfindtheentirelisthere.
4.Onsuccessfulregistration,wewillredirecttheusertoourprimaryview.

NextupistheSignincontroller.Createanewfilenamedauthsignin.htmlinsidethetemplatesfolderandaddthefollowingmarkup:

<ion-header-bar class="bar-positive">
<h1 class="title">Sign In</h1>
</ion-header-bar>
<ion-content class="has-header padding">
<div class="list">
<label class="item item-input">
<span class="input-label">Email</span>
<input type="text" ng-model="user.email">
</label>
<label class="item item-input">
<span class="input-label">Password</span>
<input type="password" ng-model="user.password">
</label>
<label class="item item-input">
<button class="button button-block button-positive" ng-click="validateUser()">Sign In</button>
</label>
</div>
</ion-content>

Whentheuserclickssubmit,wecallthevalidateUser().Thecontrollerwouldbe(continuingfromabove):
.controller('SignInCtrl', [
'$scope', '$rootScope', '$firebaseAuth', '$window',
function ($scope, $rootScope, $firebaseAuth, $window) {

$rootScope.checkSession();
$scope.user = {
email: "",
password: ""
};
$scope.validateUser = function () {
$rootScope.show('Please wait.. Authenticating');
var email = this.user.email;
var password = this.user.password;
if (!email || !password) {
$rootScope.notify("Please enter valid credentials");
return false;
}
$rootScope.auth.$login('password', {
email: email,
password: password
})
.then(function (user) {
$rootScope.hide();
$rootScope.userEmail = user.email;
$window.location.href = ('#/bucket/list');
}, function (error) {
$rootScope.hide();
if (error.code == 'INVALID_EMAIL') {
$rootScope.notify('Invalid Email Address');
}
else if (error.code == 'INVALID_PASSWORD') {
$rootScope.notify('Invalid Password');
}
else if (error.code == 'INVALID_USER') {
$rootScope.notify('Invalid User');
}
else {
$rootScope.notify('Oops something went wrong. Please try again later');
}
});
}
}
])

Thingstonotice:

1.$rootScope.auth.$login()isresponsiblefortheFirebaseauthentication.
2.$rootScope.auth.$login()returnsapromise,whichwillberesolvedoncetherequestiscompleted.
3.Onsuccessfulauthentication,wewillredirecttoourprimaryview.

Next,letsbuildtheprimaryviewoftheapp.Createanewfilenamedbucket.htmlinsidethetemplatesfolderandaddthefollowingcode:

<ion-tabs class="tabs-icon-top">
<ion-tab title="My List" icon-on="ion-ios7-browsers"
icon-off="ion-ios7-browsers-outline" href="#/bucket/list">
<ion-nav-view name="bucket-list"></ion-nav-view>
</ion-tab>
<ion-tab title="Completed" icon-on="ion-ios7-checkmark"
icon-off="ion-ios7-checkmark-outline" href="#/bucket/completed">
<ion-nav-view name="bucket-completed"></ion-nav-view>
</ion-tab>
</ion-tabs>

Thisistheabstractviewthatholdsourbucketlistcomplete&incompleteviews.Next,createanewfilenamedbucketlist.htmlinsidethetemplatesfolderandaddthe
followingcode:

<ion-header-bar class="bar-positive">
<button class="button button-clear" ng-click="newTask()">New</button>
<h1 class="title">My Bucket List</h1>
<button class="button button-clear" ng-click="logout()">Logout</button>
</ion-header-bar>
<ion-content class="has-header padding" has-tabs="true" on-refresh="onRefresh()">
<div class="card" ng-repeat="item in list" id="{{item.key}}" >
<div class="item item-text-wrap">
<span>{{ item.item }}</span>
<br/> <br/>
<p class="actions padding">
<i class="ion-checkmark-circled icon-actions margin" ng-click="markCompleted('{{item.key}}')"></i>
<i class="ion-trash-b icon-actions margin" ng-click="deleteItem('{{item.key}}')"></i>
</p>
</div>
</div>
<div class="card" >
<div class="item item-text-wrap" ng-show="noData">
<span>
No Items in your bucket List. Click <a href="javascript:" ng-click="newTask()">Here</a> and create one
</span>
</div>
</div>
</ion-content>
Thingstonotice:

1.WehaveaddedaNewbuttontotheheader.Thiswillopenapopup,whereusercanentertheitemdescriptionandcreateit.
2.ThebodyoftheviewrendersacardthatwillshowtheitemdescriptionandaDeleteandMarkasCompletedicons.

Thecontrollerlookslikethis:

.controller('myListCtrl', function($rootScope, $scope, $window, $ionicModal, $firebase) {


$rootScope.show("Please wait... Processing");
$scope.list = [];
var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));
bucketListRef.on('value', function(snapshot) {
var data = snapshot.val();

$scope.list = [];

for (var key in data) {


if (data.hasOwnProperty(key)) {
if (data[key].isCompleted == false) {
data[key].key = key;
$scope.list.push(data[key]);
}
}
}

if ($scope.list.length == 0) {
$scope.noData = true;
} else {
$scope.noData = false;
}
$rootScope.hide();
});

$ionicModal.fromTemplateUrl('templates/newItem.html', function(modal) {
$scope.newTemplate = modal;
});

$scope.newTask = function() {
$scope.newTemplate.show();
};

$scope.markCompleted = function(key) {
$rootScope.show("Please wait... Updating List");
var itemRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail) + '/' + key);
itemRef.update({
isCompleted: true
}, function(error) {
if (error) {
$rootScope.hide();
$rootScope.hide();
$rootScope.notify('Oops! something went wrong. Try again later');
} else {
$rootScope.hide();
$rootScope.notify('Successfully updated');
}
});
};

$scope.deleteItem = function(key) {
$rootScope.show("Please wait... Deleting from List");
var itemRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));
bucketListRef.child(key).remove(function(error) {
if (error) {
$rootScope.hide();
$rootScope.notify('Oops! something went wrong. Try again later');
} else {
$rootScope.hide();
$rootScope.notify('Successfully deleted');
}
});
};
})

Thingstonotice:

WewillbebuidingtheFirebaseReferencebasedontheloggedinuser,asdiscussedintheDesigning the data structuresection.

var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));

Wearecreatingacollectionnamedafterescapingtheemailaddressoftheuser.YoucanaddescapeEmailAddress()definitionatthebottomofthecontrollers.js.

function escapeEmailAddress(email) {
if (!email) return false

email = email.toLowerCase();
email = email.replace(/\./g, ',');
return email.trim();
}

Next,wewillusethisdynamicreferencetopullallthebuckelistitemsusingtheonlistenerforvalueevent.Thiswillgettriggeredwheneverthereisachangein
collection(OneofthebestpartsofFirebase).
Wecheckiftheitemisnotcompleteddata[key].isCompleted == false,andthenaddittothelistofitemstobeshown.
WealsoregisterthenewTask(),thatwillopentheCreate Newitempopup.
$scope.markCompleted()and$scope.deleteItem(),interactwiththeFirebaseAPItoupdatetheisCompletedvaluetotrueanddeleteapieceofdata
fromthecollectionrespectively.

Next,wewilladdthenewCtrl,responsibleforcreatinganewcontroller.CreateanewfilenamednewItem.htmlinsidethetemplatesfolderandaddthefollowing
code:

<div class="modal slide-in-up" ng-controller="newCtrl">


<header class="bar bar-header bar-secondary">
<button class="button button-clear button-primary" ng-click="close()">Cancel</button>
<h1 class="title">New Item</h1>
<button class="button button-positive" ng-click="createNew()">Done</button>
</header>
<ion-content class="padding has-header">
<input type="text" placeholder="I need to do..." ng-model="data.item">
</ion-content>
</div>

OnclickingDone,wecallcreateUser().Incontroller.jsappendthefollowingcode:
.controller('newCtrl', function($rootScope, $scope, $window, $firebase) {
$scope.data = {
item: ""
};

$scope.close = function() {
$scope.modal.hide();
};

$scope.createNew = function() {
var item = this.data.item;

if (!item) return;

$scope.modal.hide();
$rootScope.show();
$rootScope.show("Please wait... Creating new");
var form = {
item: item,
isCompleted: false,
created: Date.now(),
updated: Date.now()
};

var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));


$firebase(bucketListRef).$add(form);
$rootScope.hide();
};
})

Thingstonotice:

Webuildaformobject,thatwillconsistsofalltheessentialsdatatocreateanewbucketlistitem.
Wewillspawnanewconnectiontotheuserscollectionandthenusing$firebase(bucketListRef).$add(form);weinsertthedataintothecollection.
Oncethedataisinserted,Firebasetriggersthevalueevent,whichwillrefreshourbucketlistitemsview.

Finally,letusaddthecontrollertoshowallthecompletedbucketlistitems.Createanewfilenamedbucketcompleted.htmlinsidethetemplatesfolderandaddthe
followingcode:
<ion-header-bar class="bar-positive">
<h1 class="title">Completed Items</h1>
<button class="button button-clear" ng-click="logout()">Logout</button>
</ion-header-bar>
<ion-content class="has-header padding" has-tabs="true" on-refresh="onRefresh()">
<div class="card" ng-repeat="item in list" >
<div class="item item-text-wrap">
<span>{{ item.item }}</span>
<br/> <br/>
<p class="actions padding">
<i class="ion-trash-b icon-actions margin" ng-click="deleteItem('{{item.key}}')"></i>
</p>
</div>
</div>
<div class="card" >
<div class="item item-text-wrap" ng-show="noData || incomplete">
<span ng-show="incomplete">
You can have not completed any of your Bucket List items yet. Try harder!!
</span>
<span ng-show="noData">
No Items in your bucket List.
</span>
</div>
</div>
</ion-content>

Thiscontrollerissimilartotheincompletebucketlistcontroller,exceptforCreate NewitemandMark Item Incomplete.Youcanaddthemheretooifyouwant.The


controllerlookslikethis:
.controller('completedCtrl', function($rootScope, $scope, $window, $firebase) {
$rootScope.show("Please wait... Processing");
$scope.list = [];

var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));


bucketListRef.on('value', function(snapshot) {
$scope.list = [];
var data = snapshot.val();

for (var key in data) {


if (data.hasOwnProperty(key)) {
if (data[key].isCompleted == true) {
data[key].key = key;
$scope.list.push(data[key]);
}
}
}
if ($scope.list.length == 0) {
$scope.noData = true;
} else {
$scope.noData = false;
}

$rootScope.hide();
});

$scope.deleteItem = function(key) {
$rootScope.show("Please wait... Deleting from List");
var itemRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));
bucketListRef.child(key).remove(function(error) {
if (error) {
$rootScope.hide();
$rootScope.notify('Oops! something went wrong. Try again later');
} else {
$rootScope.hide();
$rootScope.notify('Successfully deleted');
}
});
};
});

Finally,letsaddabitofCSS.Openstyle.cssinthewww/cssfolderandaddthefollowingcode:
.margin {
margin-left: 9px;
margin-right: 9px;
}
.icon-actions {
font-size: 23px;
}
.checkbox {
vertical-align: middle;
}
.actions {
float: right;
}
.item-text-wrap {
overflow: auto;
}
.ion-checkmark-circled.icon-actions.margin{
margin-right: 35px;
}

Weredone!Letsruntheappandseehowitlooks.Intheterminal,run:

gulp serve

Thiswillstarttheserver.Next,navigatetohttp://localhost:1881andyoushouldbegreetedwithaSigninview.ClickonSignupandregisterforanaccount.Once
theregistrationissuccessful,youwillberedirectedtothebucketlistview.PlayaroundwithyournewFirebasepoweredIonicApplication.

Note:YoucangotoyourFirebaseappaccountandcheckoutthedatastructuretheretoo.

IssueaPhoneGapBuild

Wehavesuccessfullybuiltanappthatworksfineinthebrowser.Letsbuildanativeinstallerandseehowtheappworksonanactualdevice.

Note:IfyouarenewtoPhoneGap,IwouldrecommendreadingthePhoneGapQuickStartbeforecontinuing.

Step1:First,copythemyIonicFireApp/dev/bucketListApp/wwwfolderanditscontentstomyIonicFireApp/prod.ThisisallweneedtoissueaPhoneGap
build.

Step2:CreateanewGitHubreponamedIonicFirePGInstaller.

Step3:cdintothemyIonicFireApp/prodfolder(notinsidethewwwfolder)andrunthefollowingcommands:
$ git init
$ git add -A
$ git commit -am "Initial Commit"
$ git remote add origin git@github.com:sitepoint/IonicFirePGInstaller.git

Makesureyouupdatetherepopathtopointtotheoneyouhavecreated.Finally,checkinthecode:

$ git push origin master

ThiswillpushthecodetoGitHub.

Step4:NavigatetoPhoneGapBuildandlogin.

Step5:Clickon+ New AppandsubmittheGitHubrepoURL(thehttpsoneandnotthesshone)underopensource.Now,thePhoneGapservicewillgotoGitHuband


fetchtherepo.Oncetherepoisloaded,youwillseeaReady to Buildbutton.ClickonittoissueaPhoneGapbuild.

Oncethebuildiscompleted,youcandownloadtheinstallersforyourdeviceandtesttheappout.

Conclusion
ThisconcludesthearticleonbuildingaHybridappusingFirebaseandtheIonicframework.Hopeyougainedafairideaonhowtogoaboutbuildingoneofyourown.

YoucanfindthecodebasewedevelopedonGitHub.
YoucanfindthewwwfolderthatyoucansubmittoPhoneGapbuildonGitHubtoo.
Youcandownloadtheappinstallerhere.

Thanksforreading!

Anda mungkin juga menyukai