Menu Search
Jump to the content X X
Smashing Conf New York

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf New York, dedicated to smart front-end techniques and design patterns.

Putting Mobile Back End As A Service Into Practice (Part 2)

Mobile back end as a service (MBaaS) aims at giving app developers the ability to create seamlessly new feature-complete cross-platform native and web applications. In the first part1 of this series, I walked through a messaging application demo powered by the Kinvey application. We explored how to leverage user management, file storage and the data store.

To complete the demo, we need to leverage two key pieces of Kinvey functionality: the permissions provided by the data store, and push notifications, which are enabled through the business logic functionality.

Screenshots of WaterCooler app2

WaterCooler messaging app demo. (View large version3)

Setting Up Link

Both the first article4 in this series and my initial article explaining the concept of MBaaS5 are prerequisites for this article. Also, this article does take a “real app” approach to understanding MBaaS. That being said, some knowledge of iOS and Swift would be helpful. However, I have provided the application’s source code6, so you can learn and analyze at your own pace.

Permissions Link

In addition to the data store and file storage support provided by Kinvey, there is also a robust security model that can be applied to all of this data. All of the configuration for permissions on either files or data store objects happen with the KCSMetadata class. This instance can be configured with custom data model classes or attached as parameters to a file when uploading it to the file store.

Further Reading on SmashingMag

File Permissions Link

As we discussed earlier, the file management functionality is a powerful piece of any MBaaS tool. However, clarifying who can access this content is equally important. In our example, profile pictures that are uploaded should be viewable by all users. If we wanted to add image sharing to the one-to-one messaging portion of the application, then we would want to limit who can access the image. Because we want to allow anyone to see the profile picture, we can leverage the setGloballyReadable method of KCSMetadata to ensure that while only the creator can edit this file, any user may read it.

// Extracted from SignupViewController.swift (http://tuck.cc/1vsaWcr)

// Initialize with the default initializer
let metadata = KCSMetadata();

// Ensure that all users can access this file
metadata.setGloballyReadable(true);

// This is added to a params object that is passed to the upload data method
var fileParams = [
  KCSFileMimeType : "image/jpeg",
  KCSFileACL : metadata
];

// By passing in this fileParams object, the file will be readable by all
KCSFileStore.uploadData(photoData, options: fileParams, completionBlock: { (file:KCSFile!, error:NSError!) -> Void in
    println("File Upload Completed")
}, progressBlock: nil);

Data Store Permissions Link

To make this as simple as possible to integrate in the application, I’ve defined a few Swift extensions10 to the classes provided in Kinvey’s SDK for iOS. One such extension is a convenience initializer for the KCSMetadata class to configure permissions for the Message and MessageThread classes:

/*
  This method is a convenience initializer that sets the permissions
  on a KCSMetadata instance for the user IDs passed in (and, optionally,
  for the current user as well). All persistable data in the Kinvey data
  store can be configured using a KCSMetadata instance.
 */
extension KCSMetadata {

  convenience init(userIds:[String], includeActiveUser:Bool) {
    self.init()
    var ids = userIds
    setGloballyReadable(false)
    setGloballyWritable(false)
    if(includeActiveUser) {
      ids.append(KCSUser.activeUser().userId)
    }
    for userId in ids {
      readers.addObject(userId)
      writers.addObject(userId)
    }
  }
}

With this in place, we can now initialize the meta-data instance with the correct security settings when we create an instance of the Message class. Now, when it is saved, it will be accessible only to the sender and recipient.

class Message : NSObject, KCSPersistable {

  var entityId:String = ""
  var messageText:String!
  var senderId:String!
  var threadId:String!
  var userEntryTime:NSDate!
  var metadata:KCSMetadata! = nil

  init(messageText:String, recipientId:String) {
    senderId = KCSUser.activeUser().userId
    messageText = messageText
    entityId = NSUUID().UUIDString
    // This uses our convenience initializer to allow for the limited
    // permissions of only the sender and recipient
    metadata = KCSMetadata(userIds: [recipientId], includeActiveUser:true)
  }

  // You have to let Kinvey know which property corresponds to the meta-data
  // instance. Do this by assigning the property the KCSEntityKeyMetadata
  // value in the hostToKinveyPropertyMapping method.
  override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
    return [
      "entityId" : KCSEntityKeyId,
      "messageText" : "message",
      "senderId" : "senderId",
      "threadId" : "threadId",
      "metadata" : KCSEntityKeyMetadata
    ]
  }
}

Business Logic And Push Notifications Link

An MBaaS solutions that provides all of the functionality detailed so far would be adequate in a limited set of use cases. However, the ability to centralize business logic when tied to the lifecycle of a data object opens up many doors to creating effective cross-platform applications. In the WaterCooler demo application, this is relevant in two distinct ways:

  1. When a Message instance is saved to the data store, we need to determine who it is being sent to. After we have ascertained the recipient of the message, we need to send them a push notification.
  2. When a Message instance is saved to the data store, we need to also fetch the MessageThread instance and save the latestMessage property. This frees us from having to make an additional fetch call when populating the message thread’s view.

While both of these could be accomplished through the iOS application, that would be putting the logic in the incorrect place. Consider if we also had a web and Android application for the WaterCooler application. We would want the same functionality to occur upon a message being saved on both of those platforms as well. In this case, any business logic that spans multiple platforms should reside on the server using Kinvey’s business logic capability.

Configuring Push Notifications Link

For push notifications to work properly on iOS, we need to configure Kinvey to work with our push notification certificate(s) for the application. This step involves two distinct steps:

  1. Configure the application with push notification certificates for development and production in the iOS Developer Center.
  2. Export and uploaded the required certificate to Kinvey.

The full process of completing these steps is beyond the scope of this article, but Kinvey has a “Push11” guide in the documentation to assist you in the overall integration process.

Kinvey Collection Hooks Link

To accomplish the two goals listed above, we leverage one particular piece of functionality in Kinvey: collection hooks. With collection hooks, we can define server-side business logic in JavaScript that is executed at particular points. The predefined points for a collection are listed below:

  • onPreSave
    This logic is executed prior to an instance being saved in the data store. Any errors that occur in this logic would prevent the save action from being executed.
  • onPreFetch
    This logic is executed prior to any fetch call on the entity type. Any errors would prevent the fetch action from being executed.
  • onPreDelete
    This logic is executed prior to any entity instance being deleted from the data store. Any errors would prevent the instance from being deleted from the data store.
  • onPostSave
    This logic would be executed after an instance is saved to the data store. Any errors here would not prevent the object from being stored in the data store, but they would cause an error to return on the request.
  • onPostFetch
    This logic would be executed after a fetch command has been executed by the data store. Any errors here would not prevent the object from being fetched from the data store, but they would cause an error to return on the request.
  • onPostDelete
    The logic would be executed after an instance is deleted from the data store. Any errors here would not prevent the object from being deleted, but they would cause an error to return for the request.

Push notifications are an important part of any messaging application. Ideally, we would want one to be sent each time a sender saves a Message object to the data store. Kinvey provides this capability through its business logic’s hooks functionality. In short, you can add custom JavaScript logic that executes at certain points in the lifecycle of a data object.

Business Logic Modules Link

Kinvey provides a set of modules that enable you to access core Kinvey functionality within your business logic’s code. Several modules are available, but I’ve leveraged the following modules with the business logic hook:

  • logger
    Provides a seamless way to log information from your business logic to the Kinvey console
  • collectionAccess
    Provides a way to query the Kinvey data store from within your business logic
  • push
    Provides access to the push notification functionality for Kinvey with your business logic
  • async
    Provides an approach for dealing with asynchronous JavaScript. This is the async12 module you may have used in Node.js applications.
  • For a complete reference on business logic modules, see the “Modules13” guide in the “Business Logic” section of Kinvey’s documentation.

These hooks are configured via the Kinvey Console14. To add an onPostSave hook for the Messages collection, follow these steps:

  1. Select the “Business Logic” option in the navigation on the left side of the Kinvey console.
  2. Select the “Create Your First Script” option.
  3. In the popup menu, select the “Add Hook” option for the Messages collection.
  4. Select the onPostSave option.
  5. You will see the new onPostSave JavaScript hook within the browser, which you can edit.
Screenshot of Kinvey console15

The business logic section of Kinvey’s console. (View large version16)

Complete Code for Business Logic Link

Putting these pieces together yields a single JavaScript method that is only 115 lines (with comments). This code gets the recipient, updates the thread with the latest message and then sends the push notification to the recipient.

To enable this hook, copy this JavaScript into the hook you’ve just created:

// Extracted from Messages-PostSave.js (http://tuck.cc/1MPrCAc)

function onPostSave(request, response, modules) {

  // Get references to Kinvey's business logic modules
  var push = modules.push;
  var collectionAccess = modules.collectionAccess;
  var logger = modules.logger;
  var async = modules.async;

  // Pull information from the request (the postSave messages request)
  var senderUsername = request.username;
  var messageId = request.entityId;
  var message = request.body;

  // Set up our state variables
  var recipient = {};

  // This method fetches the recipient of the message based on the thread
  // ID. This is needed to send the push notification to the correct user.
  var getRecipient = function(callback) {
    var recipientId = recipientIdToFetch();
    collectionAccess.collection('user').findOne({ "_id": recipientId }, function(error, user) {
      if(error) {
        logger.info("ERROR Fetching Recipient KCSUser");
        callback(error);
      } else {
        recipient = user;
        callback(null, recipient);
      }
    });
  };

  // This method sets the lastMessage on the MessageThread
  var updateLatestMessage = function(callback) {
    var threadId = message.threadId;

    // This is how Kinvey stores relationships to other entities.
    // We simply need to insert this object for the thread at the
    // specified ID and we will have updated the reference.
    var lastMessageRef = {
      _type: "KinveyRef",
      _id: threadId,
      _collection: "Messages"
    };

    // This method finds and updates the thread with the updated
    // reference to the message. It returns the updated thread from
    // the data store.
    collectionAccess.collection('MessageThreads').findAndModify({ "_id": threadId }, { "_id": 1 }, { $set: { "lastMessage" : lastMessageRef } }, { new: true }, function(error, thread) {
      if(error) {
        logger.error("Could Not Fetch Thread from ID: " + threadId);
      } else {
        callback(null, thread);
      }
    });
  };

  // This checks the threadId and, based on the ID of the user who saved the
  // message, determines the ID of the recipient of the message.
  var recipientIdToFetch = function() {
    var threadId = message.threadId;
    var ids = threadId.split(":");
    var recipientId = (ids[0] == message.senderId) ? ids[1] : ids[0];
    return collectionAccess.objectID(recipientId);
  };

  // This method creates both the APS and extras objects to be used in the push
  // notification.
  var pushDataFromMessageAndUser = function(user) {
    return {
      aps: {
        alert: notificationAlertMessage(user),
        badge: 0,
        sound: "notification.wav"
      },
      extras: {
        messageText: message.message,
        creationDate: message._kmd.ect,
        threadId: message.threadId,
        senderId: message.senderId,
        entityId: messageId
      }
    };
  };

  // This method calculates the text of the push notification based on the
  // sender and the text of the message.
  var notificationAlertMessage = function(user) {
    var alertMessageComponents = [];
    var alertMessage = user.first_name + " ";
    alertMessage += user.last_name + ": ";
    alertMessage += message.message;
    return alertMessage;
  };

  // This method is executed after the recipient has been fetched from the
  // user collection.  It proceeds with making the push notification call to
  // the recipient with the correct information.
  var callback = function(error, results) {
    if(error) {
      response.body = {
        error: error.message
      };
      response.complete(400);
    } else {
      var pushData = pushDataFromMessageAndUser(results[1]);
      push.sendPayload(recipient, pushData.aps, pushData.extras);
      response.complete(200);
    }
  };

  // This kicks off the process to fetch the recipient and update the thread.
  // Once that is complete, it calls the callback function to send the
  // push notification.
  async.parallel([updateLatestMessage, getRecipient], callback);
}

With this code in place (and the push notifications properly configured), the application will now send a push notification to the recipient and also update the lastMessage property in the relevant message thread. In the image below, you can see a sample push notification that has been sent to the recipient of a message:

Screenshot of push notification in WaterCooler app17

A push notification in the WaterCooler app. (View large version18)

Putting the Pieces Together Link

While we didn’t walk through every step of how the application was created, you now have a complete example of a Kinvey application that you can use as a starting point for your own application. Hopefully, this article has given you a good working understanding of the code required, but also helped you think about how to solve problems using an MBaaS solution.

Screenshots of WaterCooler app19

WaterCooler messaging app demo. (View large version20)

If you want to continue learning, you could add a lot of features to this application:

  • support for images in messages,
  • notifications for when a user is typing,
  • group messaging.

All of these features would be possible with Kinvey’s back end.

Further Resources Link

Here are a few resources that were used in the creation of our app:

  • SlackTextViewController21, Slack, GitHub
    The team at Slack has released an amazing control for inputting text in a messaging app.
  • MBProgressHUD22, Jonathan George, GitHub
    This is a common control for showing overlays when the application is waiting on a task to finish. It is very customizable and configurable.
  • Search for Creative Commons-licensed icons on Freepik23, SimpleIcon24, Google25 and Flaticon26.
  • Brandon McQuilkin27, GitHub
    McQuilkin used to provide some great Swift image extensions for masking images. Since my initial work on the app, he has removed these from GitHub.

Conclusion Link

I hope that through the last few articles, you have seen ways that an MBaaS solution can help you to create compelling business- and consumer-focused mobile experiences. This certainly is a space with a lot of focus at the moment, and I expect rapid maturity and evolution of these services over the next year. Most any mobile developer would agree that this a valuable tool to have in your arsenal.

The following additional resources from Kinvey’s documentation will assist you to create iOS applications:

(da, al, ml)

Footnotes Link

  1. 1 https://www.smashingmagazine.com/2014/12/15/understanding-mobile-back-end-as-a-service/
  2. 2 https://www.smashingmagazine.com/wp-content/uploads/2015/03/01-watercooler-demo-app-opt.jpg
  3. 3 https://www.smashingmagazine.com/wp-content/uploads/2015/03/01-watercooler-demo-app-opt.jpg
  4. 4 https://www.smashingmagazine.com/2014/12/15/understanding-mobile-back-end-as-a-service/
  5. 5 https://www.smashingmagazine.com/2014/12/15/understanding-mobile-back-end-as-a-service/
  6. 6 https://github.com/davidtucker/WaterCooler-Demo
  7. 7 https://www.smashingmagazine.com/2016/04/consider-react-native-mobile-app/
  8. 8 https://www.smashingmagazine.com/2016/03/server-side-rendering-react-node-express/
  9. 9 https://www.smashingmagazine.com/2015/05/client-rendered-accessibility/
  10. 10 https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
  11. 11 http://devcenter.kinvey.com/ios/guides/push
  12. 12 https://github.com/caolan/async
  13. 13 http://devcenter.kinvey.com/ios/reference/business-logic/reference.html#utils-module
  14. 14 https://console.kinvey.com/
  15. 15 https://www.smashingmagazine.com/wp-content/uploads/2015/03/02-kinvey-console-opt.png
  16. 16 https://www.smashingmagazine.com/wp-content/uploads/2015/03/02-kinvey-console-opt.png
  17. 17 https://www.smashingmagazine.com/wp-content/uploads/2015/03/03-push-notifications-opt.jpg
  18. 18 https://www.smashingmagazine.com/wp-content/uploads/2015/03/03-push-notifications-opt.jpg
  19. 19 https://www.smashingmagazine.com/wp-content/uploads/2015/03/04-in-se-on-devices-opt.jpg
  20. 20 https://www.smashingmagazine.com/wp-content/uploads/2015/03/04-in-se-on-devices-opt.jpg
  21. 21 https://github.com/slackhq/SlackTextViewController
  22. 22 https://github.com/jdg/MBProgressHUD
  23. 23 http://www.freepik.com/
  24. 24 http://www.simpleicon.com/
  25. 25 http://www.google.com/
  26. 26 http://www.flaticon.com/
  27. 27 https://github.com/Marxon13
  28. 28 http://devcenter.kinvey.com/ios/guides/getting-started
  29. 29 http://devcenter.kinvey.com/ios/samples
  30. 30 http://devcenter.kinvey.com/ios/timeline

↑ Back to top Tweet itShare on Facebook

David Tucker serves as the Vice President of Technology at Universal Mind. He is a software architect with more than 14 years of experience in web and mobile development, working closely with Universal Mind's clients to develop an effective technical strategies in the enterprise. David is an author and speaks regularly at industry events.

  1. 1

    Great post David, it perfectly completes the previous one your wrote. Clear and straight to the point! More and more services are popping out with slightly different approach on solving devs’ problem and you should also check out Stamplay (https://stamplay.com), it’s like IFTTT meets Heroku providing super-easy back-end development and web hosting over globally and super fast CDN.

    -1
  2. 3

    Hi David,
    Thanks again for your lesson part 2 here. Finally I feel more understand about this MBaaS more from you. And I will try to apply this information of yours to mine as soon as possible here.
    Thanks again David.

    0
  3. 5

    Hi David,

    Thank you very much for sharing this information. It is very useful and informative!

    0
  4. 7

    Hello,
    thank you for posting such useful article. I was in search of stuff like this.
    but now m search is completed. Please keep posting like articles like this.

    0
  5. 8

    Great post – would love to see one on Parse!

    0

↑ Back to top