## BluePic [![Build Status](https://travis-ci.org/IBM-MIL/BluePic.svg?branch=master)](https://travis-ci.org/IBM-MIL/BluePic)
BluePic is a sample application for iOS that shows you how to connect your mobile application with IBM Bluemix services. It is a photo sharing app that allows you to take photos, upload them and share them with the BluePic community. The BluePic community will be made up of all the users that run an instance of your created app.
## Table of Contents * [About IBM Bluemix](#about-ibm-bluemix) * [Requirements](#requirements) * [Project Structure](#project-structure) * [Getting Started](#getting-started) * [Create Bluemix Account](#1-create-bluemix-account) * [BluePic Account Requirements](#2-bluepic-account-requirements) * [Create Bluemix Application and Services](#3-create-bluemix-application-and-services) * [Connect BluePic to your Bluemix Account](#4-connect-bluepic-to-your-bluemix-account) * [Create an application instance on Facebook](#5-create-an-application-instance-on-facebook) * [Pre-populate Feed with Stock Photos (Optional)](#6-pre-populate-feed-with-stock-photos-optional) * [Using BluePic](#using-bluepic) * [Facebook Login](#facebook-login) * [View Feed](#view-feed) * [Post a Photo](#post-a-photo) * [View Profile](#view-profile) * [Architecture/Bluemix Services Implementation](#architecturebluemix-services-implementation) * [Mobile Client Access Facebook Authentication](#1-mobile-client-access-facebook-authentication) * [Cloudant Sync (CDTDatastore)](#2-cloudant-sync-cdtdatastore) * [Object Storage](#3-object-storage) * [Architecture Forethought](#architecture-forethought) * [Troubleshooting](#troubleshooting) * [Deploy to Bluemix Failure](#deploy-to-bluemix-failure) * [License](#license)
## About IBM Bluemix
Bluemix™ is the latest cloud offering from IBM®. It enables organizations and developers to quickly and easily create, deploy, and manage applications on the cloud. Bluemix is an implementation of IBM's Open Cloud Architecture based on Cloud Foundry, an open source Platform as a Service (PaaS). Bluemix delivers enterprise-level services that can easily integrate with your cloud applications without you needing to know how to install or configure them.
In Bluemix you should be aware that often the term “Application” is used to refer to a server component and its Bluemix services needed. It is this server component that gets deployed to Bluemix. It is not the mobile application that gets deployed, this will become clear as you go through the Getting Started guide.
## Requirements Currently, BluePic supports Xcode 7.1.1, iOS 9+, and Swift 2. Designed for iPhone, compatible with iPad.
## Project Structure * `/BluePic-iOS` directory for the iOS client. * `/BluePic-iOS/BluePic/Configuration` directory for configuring Bluemix services keys. * `/NodeStarterCode` directory for the server artifact that is deployed to Bluemix. * `/img` directory for images for this README.
## Getting Started
Create an IBM Bluemix account here and log in. If you already have an account, log in and continue to step 2.
A free trial of Bluemix comes with 2 GB of memory and allows the use of up to 10 services. If this is your first time using Bluemix, continue to step 3. Otherwise, you might need to delete some resources. BluePic requires 512 MB of memory and 4 services. If your account does not have enough resources available, delete unused instances to free up resources. You can check your usage by looking at the Dashboard tab of Bluemix:
Figure 1: Bluemix dashboard, account usage for memory and services highlighted.
Click the "Deploy to Bluemix" button below. It will create the BluePic Bluemix application in your account and initialize the required services.
If desired, update the app name, region, organization or space of the application (default parameters work). Click Deploy:
Figure 2: Parameters to deploy a Bluemix application.
Upon success you should see:
Figure 3: Bluemix application successfully deployed.
Note: If deploying to Bluemix fails, it will have created a faulty application on your account as well as a DevOps services (formerly known as JazzHub) project, these must be deleted manually before trying again. Steps on how to do this here.
Next, go to your dashboard by clicking the "Dashboard" tab on the top of the page:
Figure 4: Getting back to dashboard after successful deployment.
On your dashboard the application should then become accessible, click on the Application box to open that Application Overview:
Figure 5: Bluemix dashboard.
Application Overview:
Figure 6: Application Overview.
The app has to be configured with certain credentials from each of the three Bluemix services. Clone the BluePic repo and open BluePic.xcworkspace (NOT BluePic**.xcodeproj**) to get started. The file keys.plist
located in the Configuration
directory of the BluePic Xcode project must be updated accordingly.
Figure 7. keys.plist located in the BluePic-iOS/BluePic/Configuration directory.
cdt_username
: This username will be used to identify your created databases. Click your newly created application in the Dashboard to open the Application Overview (see Figure 5 above). On the Application Overview, open the Cloudant NoSQL Instantiating Credentials by clicking on the "Show Credentials" tab of the service box:
Figure 8. Credentials of a Cloudant NoSQL DB service.
Copy the “username” credential and paste it in the cdt_username
field of the keys.plist file.
cdt_db_name
: This will be the name of the main Cloudant database the iOS application will use to store information. We first must go to the Cloudant Dashboard, click on the CloudantNoSQL DB icon from the Application Overview (see Figure 5 above). You will land in this page:
Figure 9. Cloudant service landing page.
Click "LAUNCH" to open the Cloudant Dashboard:
Figure 10. Cloudant Dashboard.
Click on "Create Database", enter a name, and click "Create":
Figure 11. Creating a new Database.
Note: The name must start with a letter and can only contain lowercase letters (a-z), digits (0-9) and the following characters _, $, (, ), +, -, and /.
Put the name of the newly created database into the cdt_db_name
field of the keys.plist file.
cdt_key
andcdt_pass
: You must generate an API Key and Password for the mobile application to access the remote database. On the database page, click on the Permissions tab:
Figure 12. Permissions button on database main page.
On the Permissions page click "Generate API key" button:
Figure 13. Generate an API key for the database.
It will create a Key and Password:
Figure 14. Generated Key and Password.
Store these values into the cdt_key
and cdt_pass
fields of the keys.plist file respectively. Also, ensure that the created API Key has Writer and Replicator permissions by checking these boxes:
Figure 15. Ensure the generated API Key has the correct permissions.
cdt_tests_db_name
: The application has test cases that run on a separate database, we're storing the name of this test database here. Go through the exact same steps as done forcdt_db_name
except with a different database name. Put this name intocdt_tests_db_name
field of keys.plist file. Once created, click on the "Permissions" tab of the new database. DO NOT generate another API key for the tests database. The previously generated API Key should be listed, again ensure it has Writer and Replicator permissions:
Figure 16. Permissions page of the database to run test cases.
backend_route
: Listed on the top of the Application Overview page, next to the "Routes:" label:
Figure 17. Routes label on Application Overview page.
Copy and paste this value into the backend_route
field of keys.plist file. NOTE: Make sure to have "http://" at the front of the pasted value. For example: http://BluePic.bluemix.net
.
GUID
: From the Application Overview (see Figure 5 above) open the Mobile Client Access Instantiating Credentials by clicking on the "Show Credentials" tab of the service box:
Copy the "clientId" credential and paste into the GUID
field of keys.plist file.
Figure 18. Credentials of a Mobile Client Access service.
Download and install the Cloud Foundry CLI, make sure to install the Mac OS X 64 bit Installer, the latest release:
Figure 19. Cloud Foundry CLI installer.
Run the following commands on the terminal:
cf api https://api.ng.bluemix.net
cf login -u <email_address> -o <email_address> -s dev
Pick a name for the service key and use it in the following commands:
cf create-service-key 'Object Storage-rz' <unique_name_for_this_key>
In the following command, use the same name as the one created above:
cf service-key 'Object Storage-rz' <unique_name_for_this_key>
It will return several values:
Figure 20. Cloud Foundry CLI command.
obj_stg_password
: Copy the "password" from CF CLI command into this field (Do not copy the quotation marks for this, nor for the keys below).obj_stg_user_id
: Copy the "userId" from CF CLI command into this field.obj_stg_project_id
: Copy the "projectId" from from CF CLI command into this field.obj_stg_public_url
: Copy the "projectId" from CF CLI command and append it to "https://dal.objectstorage.open.softlayer.com/v1/AUTH_" like so:
https://dal.objectstorage.open.softlayer.com/v1/AUTH_<project_id>
Paste the resulting string into this field.
In order to have the app authenticate with Facebook, you must create an application instance on Facebook's website and connect it to your Bluemix app's Mobile Client Access.
-
To create an application instance on Facebook's website, first go to Facebook's Quick Start for iOS page. Type
BluePic
as the name of your new Facebook app and click theCreate New Facebook App ID
button. Choose any Category for the application, and click theCreate App ID
button. -
On the screen that follows, in the
Configure your info.plist
section understep 2
, copy that information into yourinfo.plist
file. You can find theinfo.plist
file in Configuration folder of the Xcode project. If you have trouble finding theCFBundleURLType
key, note that Xcode changes theCFBundleURLType
key toURL types
when the key is entered. Yourinfo.plist
file should now look like this:
Figure 21. Info.plist file.
- Next, scroll to the bottom of the quick start page where it says
Supply us with your Bundle Identifier
and enter the app's bundle identifier. To find the bundle identifier in the Xcode project you can do the following:- Make sure the project navigator folder icon is selected in the top left of Xcode. Select the BluePic project at the top of the file structure and then select the BluePic target. Under the identity section, you should see a text field for the bundle identifier that is empty or contains a dummy string like
com.bundle.id.BluePic
. You can keep the bundle id as is, or make the bundle identifier anything you want,com.BluePic
for example.
- Make sure the project navigator folder icon is selected in the top left of Xcode. Select the BluePic project at the top of the file structure and then select the BluePic target. Under the identity section, you should see a text field for the bundle identifier that is empty or contains a dummy string like
- Once you entered the bundle ID on the Facebook quick start page, click
next
. That's it for the Facebook quick start setup! - Next go back to your Bluemix dashboard, under services click
BluePic-AdvancedMobileAccess
. On the page that shows click theSet Up Authentication
button and then clickFacebook
. Enter your Facebook app ID you gathered from step 2 and press the next arrow. No further setup is required at this point.
At this point, you should be able to build and run your instance of the BluePic application on Xcode!
Once BluePic is configured, you should be able to upload photos and see them appear on the feed and profile. However, initially your feed will be empty. If you would like to pre-populate your feed with 3 images, simply do the following:
- Open BluePic.xcworkspace to get started. With the BluePic Xcode project open, show the Test Navigator by clicking the 4th icon from the right of the Navigator (toolbar frame on the left side)
Figure 22. PopulateFeedWithPhotos test case.
-
Run the test called PopulateFeedWithPhotos which should be grayed out (disabled by default when tests are run) by right clicking it and clicking Test "PopulateFeedWithPhotos".
-
The test should complete successfully. Launch the BluePic iOS app again, and you should see 3 images added by user "Mobile Innovation Lab" on the feed.
## Using BluePic
BluePic was designed so that anyone can quickly launch the app and view photos posted without needing to log in. However, to view the profile or post photos, the user can easily login with his/her Facebook account. This is only used for a unique user id, the user's full name, as well as to display the user's profile photo.
Figure 23. Welcome page.
The feed (first tab) shows all the latest photos posted to the BluePic community (regardless if logged in or not).
Figure 24. Main feed view.
Posting to the BluePic community is easy. Tap the middle tab in the tab bar and choose to either Choose a photo from the Camera Roll or Take a photo using the device's camera. You can then give the photo a caption before posting.
Figure 25. Posting a photo.
By tapping the third tab, you can view your profile. This shows your Facebook profile photo, lists how many photos you've posted, and shows all the photos you've posted to BluePic.
Figure 26. Profile feed.
## Architecture/Bluemix Services Implementation The following architecture is utilized for BluePic. For authentication, Mobile Client Access with Facebook Authentication is implemented. For profile and photo metadata, the Cloudant SDK is integrated. Finally, for photo storage and hosting, Object Storage is utilized.
Figure 27. BluePic Architecture Diagram.
Bluemix Mobile Client Access Facebook Authentication is used for logging into BluePic.
The FacebookDataManager
under the BluePic-iOS/BluePic/DataManagers
directory handles most of the code responsible for Facebook authentication. To start using Bluemix Facebook Authentication, it must first be configured on app launch, and we do this in the didFinishLaunchingWithOptions()
method of AppDelegate.swift
by calling the method below.
func initializeBackendForFacebookAuth() {
//Initialize backend
let key = Utils.getKeyFromPlist("keys", key: "backend_route")
let guid = Utils.getKeyFromPlist("keys", key: "GUID")
IMFClient.sharedInstance().initializeWithBackendRoute(key, backendGUID: guid);
//Initialize FB
IMFFacebookAuthenticationHandler.sharedInstance().registerWithDefaultDelegate()
}
Also in the App Delegate, two other methods must be overridden to activate Facebook Authentication, as shown below:
func applicationDidBecomeActive(application: UIApplication) {
FBAppEvents.activateApp()
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?,annotation: AnyObject) -> Bool {
return FBAppCall.handleOpenURL(url, sourceApplication:sourceApplication)
}
Now that the Facebook and Bluemix frameworks are configured, you can actually try authenticating to receive a unique identifier for a user. The FacebookDataManager
deals with authenticating and keeping track of user's credentials in a SharedInstance (singleton). The method below starts the process of showing a native Facebook login to the user when he/she presses the SIGN IN WITH FACEBOOK button on the LoginViewController
.
/**
Method to auth user using Facebook SDK
- parameter callback: Success or Failure
*/
func authenticateUser(callback : ((networkRequest : NetworkRequest) -> ())){
if (self.checkIMFClient() && self.checkAuthenticationConfig()) {
self.getAuthToken(callback) //this will in turn present FB login
}
else{
callback(networkRequest: NetworkRequest.Failure)
}
}
The self.checkAuthenticationConfig()
method call in the code above will try to present the native iOS 9 Safari Facebook Login Modal. The code above either continues with requesting a Facebook token if the login credentials were correct from the user, or throws an error if not correct or the user cancels.
After the user finishes inputting their credentials, the unique user id is received and saved in the getAuthToken()
method of the FacebookDataManager
. There, an IMFAuthorizationManager requests authorization by calling the obtainAuthorizationHeaderWithCompletionHandler()
method, resulting in a success or failure.
The successful closure of getAuthToken()
is shown below, where the user display name and unique id are saved to the sharedInstance
property of the FacebookDataManager
, as well as saved to NSUserDefaults to keep track of log-in status in future app launches.
if let userID = identity["id"] as? NSString {
if let userName = identity["displayName"] as? NSString {
//save username and id to shared instance of this class
self.fbUniqueUserID = userID as String
self.fbUserDisplayName = userName as String
//set user logged in
self.isLoggedIn = true
//save user id and name for future app launches
NSUserDefaults.standardUserDefaults().setObject(userID, forKey: "user_id")
NSUserDefaults.standardUserDefaults().setObject(userName, forKey: "user_name")
NSUserDefaults.standardUserDefaults().synchronize()
print("Got facebook auth token for user \(userName) with id \(userID)")
callback(networkRequest: NetworkRequest.Success)
}
}
### 2. Cloudant Sync (CDTDatastore) Cloudant Sync [(CDTDatastore)](https://github.com/cloudant/CDTDatastore) enables you to create a single local database for every user. The app simply replicates and syncs a copy of the remote database in Cloudant with its local copy on their phone or tablet. If there’s no network connection, the app runs off the local database on the device. In BluePic we have two types of documents: profile and picture. Note that we only store the metadata for pictures, the actual image is stored in the Object Storage Bluemix service. The `CloudantSyncDataManager` class was created to handle communication between iOS and Cloudant Sync.
Creating a local datastore:
/**
* Creates a local datastore with the specific name stored in dbName instance variable.
*/
func createLocalDatastore() throws {
let fileManager = NSFileManager.defaultManager()
let documentsDir = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last!
let storeURL = documentsDir.URLByAppendingPathComponent("cloudant-sync-datastore")
let path = storeURL.path
self.manager = try CDTDatastoreManager(directory: path)
self.datastore = try manager.datastoreNamed(dbName)
}
Function to create a Profile document:
/**
* Creates a profile document.
*
* @param id Unique ID the created document to have.
* @param name Profile name for the created document.
*/
func createProfileDoc(id:String, name:String) throws -> Void {
// Create a document
let rev = CDTDocumentRevision(docId: id)
rev.body = ["profile_name":name, "Type":"profile"]
// Save the document to the datastore
try datastore.createDocumentFromRevision(rev)
print("Created profile doc with id: \(id)")
}
Synching with a remote database is done by performing two main operations: push and pull. Below is an example of how we perform push.
/**
* This method will create a new Replicator object and push any new docs/updates on the local datastore to the remote database.
* This is a asynchronous call and will run on a separate replication thread.
*/
func pushToRemoteDatabase() throws {
//Initialize replicator
try createPushReplicator()
//Start the replicator
try self.pushReplicator.start()
}
/**
* Creates a new Push Replicator and stores it in pushReplicator instance variable.
*/
func createPushReplicator() throws {
//Initialize replicators
let replicatorFactory = CDTReplicatorFactory(datastoreManager: manager)
let remoteDatabaseURL = generateURL()
// Push Replicate from the local to remote database
let pushReplication = CDTPushReplication(source: datastore, target: remoteDatabaseURL)
self.pushReplicator = try replicatorFactory.oneWay(pushReplication)
self.pushReplicator.delegate = pushDelegate;
}
/**
* Creates the URL of the remote database from instance variables.
*/
private func generateURL() -> NSURL {
let stringURL = "https://\(apiKey):\(apiPassword)@\(username).cloudant.com/\(dbName)"
return NSURL(string: stringURL)!
}
You can view the Cloudant database (including profile and picture documents) by navigating to your Cloudant NoSQL DB service instance on the Bluemix Dashboard. To do this, navigate to your Bluemix Dashboard by clicking Dashboard on the top of your Bluemix home page (#1 in the image below). Then, click the Cloudant NoSQL DB service to view the record of images uploaded to each container (#2 in the image below)
Figure 28. Cloudant NoSQL service.
### 3. Object Storage [Object Storage](https://console.ng.bluemix.net/catalog/services/object-storage/) is used in BluePic for hosting images.
ObjectStorageDataManager
and ObjectStorageClient
were created based on this link for communicating between iOS and Object Storage.
Before uploading photos, it is necessary to authenticate with Object Storage by calling ObjectStorageDataManager.SharedInstance.objectStorageClient.authenticate()
which returns either a success or failure, shown below in the FacebookDataManager
.
ObjectStorageDataManager.SharedInstance.objectStorageClient.authenticate({() in
print("success authenticating with object storage!")
self.showLoginIfUserNotAuthenticated()
}, onFailure: {(error) in
print("error authenticating with object storage: \(error)")
DataManagerCalbackCoordinator.SharedInstance.sendNotification(DataManagerNotification.ObjectStorageAuthError)
})
Next, you must create a container on Object Storage for uploading photos to. The method below in the LoginViewModel
creates a container for later uploading photos to.
/**
Method to attempt creating an object storage container and call callback upon completion (success or failure)
- parameter userID: user id to be used for container creation
*/
func createObjectStorageContainer(userID: String!) {
print("Creating object storage container...")
ObjectStorageDataManager.SharedInstance.objectStorageClient.createContainer(userID, onSuccess: {(name) in
print("Successfully created object storage container with name \(name)") //success closure
self.fbAuthCallback(true)
}, onFailure: {(error) in //failure closure
print("Facebook auth successful, but error creating Object Storage container: \(error)")
self.fbAuthCallback(false)
})
}
Finally, you can upload an image to Object Storage by utilizing code similar to the method below in the CameraDataManager
/**
Method called to upload the image to object storage
*/
func uploadImageToObjectStorage() {
print("uploading photo to object storage...")
//push to object storage
ObjectStorageDataManager.SharedInstance.objectStorageClient.uploadImage(FacebookDataManager.SharedInstance.fbUniqueUserID!, imageName: self.lastPhotoTakenName, image: self.lastPhotoTaken,
onSuccess: { (imageURL: String) in
print("upload to object storage succeeded.")
print("imageURL: \(imageURL)")
}, onFailure: { (error) in
print("upload to object storage failed!")
print("error: \(error)")
DataManagerCalbackCoordinator.SharedInstance.sendNotification(DataManagerNotification.ObjectStorageUploadError)
})
}
You can view the Object Storage database (including all photos uploaded) by navigating to your Object Storage service instance on the Bluemix Dashboard. To do this, navigate to your Bluemix Dashboard by clicking Dashboard on the top of your Bluemix home page (#1 in the image below). Then, click the Object Storage service to view the record of images uploaded to each container (#2 in the image below)
Figure 29. Object Storage service.
## Architecture Forethought
For BluePic, we used a simple architecture where there is no middle tier component between the mobile app and the storage components (e.g. Cloudant) on the server. To roll out BluePic to a production environment, a few architectural changes should be made.
Cloudant Sync requires a complete replica of the database on each mobile client. This may not be feasible for apps with large databases. Under such scenarios, instead of leveraging Cloudant Sync, the REST API provided by Cloudant could be used to perform CRUD and query operations against the remote Cloudant instance. Though replicating subsets of records can be done today with Cloudant Sync, doing so with large databases where only a small subset of records should be replicated can introduce performance problems.
Using Cloudant Sync without an additional middle tier component between the mobile app and the database requires the mobile code to know the username and password for accessing the Cloudant database. This will lead to security breaches if someone gets their hands on those credentials. Hence, security could be a reason for having all database operations go first through a middleware component (e.g. Liberty, Node.js) to verify that only authenticated and authorized users of the app can perform such operations. In this architecture, the credentials to access the database are only known by the middleware component.
If the Deploy to Bluemix button failed, you would see a page similar to this:
Figure 30. A failed deployment to Bluemix.
These are some of the most common reasons for a failed deployment:
- Your account does not have enough resources. BluePic requires requires 512 Mb of memory and 4 services to deploy successfully.
- You have already deployed BluePic to your account and have tried to deploy it again.
- Bluemix servers are down.
Even though the deployment failed, Bluemix still have created an application, initializes some services and creates a DevOps project. You must delete these manually before attempting to deploy again. Begin by first going back to the dashboard, click the "Dashboard" tab on top of the page:
Figure 31. Click on the "Dashboard" tab to go back to the main page.
Once in the dashboard, find the failed application under the "Applications" section and click on the gray gear located on the top right corner of the application block to open the menu:
Figure 32. Opening the menu of a Bluemix application.
With the menu now open, click the "Delete App" option:
Figure 33. Menu open, delete option highlighted of a Bluemix application.
You will get at "Are you sure..." message, make sure all the services under the Services tab are checked and click DELETE, this will delete the application and all of the created services:
Figure 34. Deleting a Bluemix application and its bound services.
Now, you must delete the Bluemix DevOps project that was created. First, go to the IBM Bluemix DevOps Services main site here. The main page looks like this:
Figure 35. IBM Bluemix DecOps Services main page.
Locate the created project on the main page and click the gray gear icon on the top right corner of the project block.
Figure 36. Opening the menu of a DevOps Services Project.
With the project page open, click on the Delete tab on the left side of the page:
Figure 37. Deleting a DevOps Services project.
On the next page, type out DELETE in the empty field and click CONFIRM:
Figure 38. Confirm the deletion of selected project.
## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE).