Tuesday, August 16, 2016

Build an iOS app with Push Notifications using Ionic Framework_part 2 (end)


News

In app.js we have set up already two routes for the news section: tab.news and tab.details. On the first one we will see all news. Tapping on each of them will open view page with the news details.

News list
  1. <!-- www/templates/tab-news.html -->
  2. <ion-view view-title="Good News">
  3.   <ion-content>
  4.     <ion-refresher
  5.         pulling-text="Pull to refresh..."
  6.         on-refresh="refresh()">
  7.     </ion-refresher>
  8.     <ul class="list">
  9.       <li class="item" ng-repeat="n in news" ui-sref="tab.details({id: n._id})">
  10.         {{n.text}}
  11.       </li>
  12.     </ul>
  13.   </ion-content>
  14. </ion-view>
To refresh news list we have used pull to refresh, available in the Ionic and typical for native apps.
  1. // www/js/controllers.js
  2. ...
  3. .controller('NewsCtrl', function ($scope, NewsService, $ionicLoading) {
  4.   $ionicLoading.show({
  5.     template: 'Loading...'
  6.   });
  7.   NewsService.all().then(function (news) {
  8.     $scope.news = news;
  9.     $ionicLoading.hide();
  10.   });

  11.   $scope.refresh = function () {
  12.     NewsService.all().then(function (news) {
  13.       $scope.news = news;
  14.       $scope.$broadcast('scroll.refreshComplete');
  15.     });
  16.   };
  17. })
  18. ...
Details page
  1. <!-- www/templates/details.html -->
  2. <ion-view view-title="Good News">
  3.   <ion-content>
  4.     <div class="card">
  5.       <div class="item item-divider">
  6.         {{news.createdAt | date : fullDate}}
  7.       </div>
  8.       <div class="item item-text-wrap">
  9.         {{news.text}}
  10.       </div>
  11.       <div class="item item-divider">
  12.         by {{news.username}}
  13.       </div>
  14.     </div>
  15.   </ion-content>
  16. </ion-view>
  1. // www/js/controllers.js
  2. ...
  3. .controller('DetailsCtrl', function ($scope, $state, NewsService, 
  4.                                      $ionicLoading) {
  5.   $ionicLoading.show({
  6.     template: 'Loading...'
  7.   });
  8.   var id = $state.params.id;
  9.   NewsService.one(id).then(function (news) {
  10.     $scope.news = news;
  11.     $ionicLoading.hide();
  12.   });
  13. })
  14. ...
Let's create dedicated service for backend requests
  1. // www/js/newsService.js
  2. (function () {
  3.   function _NewsService($q, config, $http) {

  4.     function getOne(id) {
  5.       var deferred = $q.defer();

  6.       $http.get(config.server + '/news/' + id)
  7.         .success(function (data) {
  8.           if (data.error || !data.news) {
  9.             deferred.reject(data.error);
  10.           }
  11.           deferred.resolve(data.news);
  12.         })
  13.         .error(function () {
  14.           deferred.reject('error');
  15.         });
  16.         return deferred.promise;
  17.       }
  18.       function getAll() {
  19.         var deferred = $q.defer();

  20.         $http.get(config.server + '/news')
  21.           .success(function (data) {
  22.             if (data.error || !data.news) {
  23.               deferred.reject(data.error);
  24.             }
  25.             deferred.resolve(data.news);
  26.           })
  27.           .error(function () {
  28.             deferred.reject('error');
  29.           });
  30.           return deferred.promise;
  31.       }
  32.       return {
  33.         one: getOne,
  34.         all: getAll
  35.       };
  36.   }
  37.   _NewsService.$inject = ['$q', 'Config', '$http'];

  38.   angular.module('app.services')
  39.     .factory('NewsService', _NewsService);
  40. })();
  41. Server side (news)
We need a simple form for creating news on the server
  1. <!-- adminpanel/add-news.html -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5.   <meta charset="utf-8">
  6.   <title>Add News</title>
  7. </head>
  8. <body>
  9.   <form action="/news" method="post">
  10.     <h2>Add good news</h2>
  11.     <p>
  12.       <textarea name="text" cols="50" rows="10" 
  13.                 placeholder="News body"></textarea>
  14.     </p>
  15.     <p>
  16.       <input type="text" required name="username" 
  17.              placeholder="Username"/>
  18.     </p>
  19.     <p>
  20.       <button type="submit">Add news</button>
  21.     </p>
  22.   </form>
  23. </body>
  24. </html>
express.static middleware
  1. // server.js
  2.  ...
  3. app.use(express.static(__dirname + '/adminpanel'));
  4. app.get('/add-news', function (req, res) {
  5.   res.sendFile(__dirname + '/adminpanel/add-news.html');
  6. });
  7.  ...

Now we have to describe news model.
  1. // models/news.js
  2. var mongoose = require('mongoose');
  3. var Schema = mongoose.Schema;

  4. module.exports = function () {
  5.   'use strict';

  6.   var NewsSchema = new Schema({
  7.     username: String,
  8.     text: String,
  9.     createdAt: {type: Date, default: Date.now}
  10.   });

  11.   mongoose.model('News', NewsSchema, 'News');
  12. };
On receiving a POST request we should create a new record in db and send push notifications with created news to all our users.
  1. // routes/news.js
  2. var mongoose = require('mongoose');
  3. var News = mongoose.model('News');
  4. var Users = mongoose.model('Users');
  5. var apn = require('apn');
  6. var _ = require('lodash');

  7. module.exports = function (app) {
  8.   'use strict';

  9.   var router = app.get('router');

  10.   router.get('/news', function (req, res) {
  11.     News.find().sort({createdAt: -1}).lean().exec(function (err, news) {
  12.       if (err) {
  13.         return res.json({error: err});
  14.       }

  15.       res.json({error: null, news: news});
  16.     });
  17.   });

  18.   router.get('/news/:id', function (req, res) {
  19.     var id = req.params.id;
  20.     News.findById(id).lean().exec(function (err, news) {
  21.       if (err) {
  22.         return res.json({error: err});
  23.       }

  24.       res.json({error: null, news: news});
  25.     });
  26.   });

  27.   router.post('/news', function (req, res) {
  28.     var username = req.body.username;
  29.     var text = req.body.text;

  30.     var news = new News({
  31.       username: username,
  32.       text: text
  33.     });

  34.     news.save(function (err, news) {
  35.       process.nextTick(function () {
  36.         sendPush(news);
  37.       });
  38.       res.redirect('/add-news');
  39.     });
  40.   });

  41.   function sendPush(news) {
  42.     var text = news.text.substr(0, 100);
  43.     Users.find({deviceRegistered: true}).lean().exec(function (err, users) {
  44.       if (!err) {
  45.         for (var i = 0; i < users.length; i++) {
  46.           var user = users[i];

  47.           var device = new apn.Device(user.deviceToken);
  48.           var note = new apn.Notification();
  49.           note.badge = 1;
  50.           note.contentAvailable = 1;
  51.           note.alert = {
  52.             body : text
  53.           };
  54.           note.device = device;

  55.           var options = {
  56.             gateway: 'gateway.sandbox.push.apple.com',
  57.             errorCallback: function(error){
  58.               console.log('push error', error);
  59.             },
  60.             cert: 'PushNewsCert.pem',
  61.             key:  'PushNewsKey.pem',
  62.             passphrase: 'superpass',
  63.             port: 2195,
  64.             enhanced: true,
  65.             cacheLength: 100
  66.           };
  67.           var apnsConnection = new apn.Connection(options);
  68.           console.log('push sent to ', user.username);
  69.           apnsConnection.sendNotification(note);
  70.         }
  71.       }
  72.     });
  73.   }
  74. };
We have used apn module to send push notifications. Also we used PushNewsCert.pem and PushNewsKey.pem files, we will create them in the next step.
Push notification will be sent, but how app will know about it? We must add an event listener to the app.js
  1. // www/js/app.js
  2. ...
  3. .run(function ($ionicPlatform, $rootScope) {
  4.   $ionicPlatform.ready(function () {
  5.     $rootScope.$on(
  6.       '$cordovaPush:notificationReceived', 
  7.       function (event, notification) {
  8.         if (notification.alert) {
  9.           navigator.notification.alert(notification.alert);
  10.         }
  11.       });
  12.   });
  13. })
  14. ...
Provisioning Profile and Certificates

For using push notifications in our app, we have to prepare the App ID, SSL Certificate and create Provisioning Profile in iOS Dev Center.

Create Certificate

Open Keychain Access on your mac and choose Request a Certificate from a Certificate Authority...

Enter you email, common name, check Saved to disk. Save file as PushNews.certSigningRequest

Now we can find new private key in keychain, let's export it as PushNewsKey.p12. Enter secure passphrase when it will prompt.


Log in to the iOS Dev Center and select the Certificates, Identifiers and Profiles from the right panel.

Select Identifiers in the iOS Apps section.

Go to App IDs in the sidebar and click the "+" button.

Enter App ID registration data:
  • App ID Description / Name - PushNews
  • Explicit App ID / Bundle ID - com.your_domain.PushNews (this ID and cordova app name should be the same)
  • App Services / Enable Services - Push Notifications
Press Continue and Submit.
Now we have to set up our app. Select PushNews in Apps IDs list and press Edit in the bottom.
Find Push Notifications section and press Create Certificate... in the Development SSL Certificate section.

Press Continue and Choose PushNews.certSigningRequest created early, press Generate

Download generated certificate and save it as aps_development.cer

Making PEM files

Go to the directory where you've saved files and run these commands in Terminal for converting:

.cer file into a .pem
  1. $ openssl x509 -in aps_development.cer -inform der -out PushNewsCert.pem
.p12 into a .pem
  1. $ openssl pkcs12 -nocerts -in PushNewsKey.p12 -out PushNewsKey.pem
You will be asked to enter your passphrase for .p12 file and new pass for .pem file. Copy both .pem files to the app's root directory.

Making the Provisioning Profile

Let's return to iOS Dev Center. Click the Provisioning Profiles/Development in the sidebar and click the "+" button.
  • Choose iOS App Development
  • Select PushNews App in App ID list
  • Select your certificate
  • Select devices you want to include in this provisioning profile
  • Enter Profile Name (I will use "PushNews Development")
Press Generate and download profile when it will be created, we will use it later.


Let's change cordova id in config.xml (by default it will be something like com.ionicframework.PushNews456803) to Explicit App ID / Bundle ID value you have entered above (com.your_domain.PushNews).
  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <widget id="com.telnov.PushNews" version="0.0.1" xmlns="http://www.w3.org/ns/widgets"
  3.         xmlns:cdv="http://cordova.apache.org/ns/1.0">
  4.     <name>pushNews</name>
  5.     ...
Test on device

OK, now you can check result. To launch app on iOS device, we have to add ios platform first:
  1. $ ionic platform add ios
  2. $ ionic build ios
  • Open platforms/ios/pushNews.xcodeproj in the Xcode
  • Connect your iOS device via usb
  • Switch to the Build Settings tab
  • Select your Provisioning Profile
  • Select your device
  • Click run

You will see login page. After tapping the Login with Twitter button, you will see twitter auth page.





At the first time, when you are trying to launch any application that use push notifications, you should give an access, so after you entering twitter credentials, you will see popup


Try adding news and enjoy the results.


Conclusion

We have created the project, which demonstrates how easy you can build iOS applications using web technologies only.
Written by Oleksandr Telnov

If you found this post interesting, follow and support us.
Suggest for you:

No comments:

Post a Comment