Code Monkey home page Code Monkey logo

swiftlysalesforce's Introduction

Salesforce powered

Swiftly Salesforce

Swiftly Salesforce is a framework for the rapid development of native iOS mobile apps that interact with the Salesforce Platform.

  • Written entirely in Swift, Apple's "modern programming language that is safe, fast and interactive."
  • Enables elegant, painless coding for complex, asynchronous Salesforce API interactions
  • Manages the Salesforce OAuth2 authorization process (the "OAuth dance") automatically and transparently
  • Simpler and lighter alternative to the Salesforce Mobile SDK for iOS
  • Easy to install and update

How do I set up Swiftly Salesforce?

You can be up and running in a few minutes by following these steps:

  1. Get a free Salesforce Developer Edition
  2. Set up a Salesforce Connected App
  3. Register your Connected App's callback URL scheme with iOS (see appendix)
  4. Add Swiftly Salesforce to your Xcode project:
  1. Configure your app delegate for Swiftly Salesforce (see appendix)
  2. Add an ATS exception for salesforce.com (see appendix)

Minimum requirements:

  • iOS 10.0
  • Swift 3
  • Xcode 8

Documentation

Documentation is here. See especially the public methods of the Salesforce class - those are likely all you'll need to call from your code.

Examples

Below are some examples to illustrate how to use Swiftly Salesforce, and how you can chain complex asynchronous calls. You can also find a complete example app here; it retrieves the logged-in user’s task records from Salesforce, and enables the user to update the status of a task.

Swiftly Salesforce will automatically manage the entire Salesforce OAuth2 process (the "OAuth dance"). If Swiftly Salesforce has a valid access token, it will include that token in the header of every API request. If the token has expired, and Salesforce rejects the request, then Swiftly Salesforce will attempt to refresh the access token, without bothering the user to re-enter the username and password. If Swiftly Salesforce doesn't have a valid access token, or is unable to refresh it, then Swiftly Salesforce will direct the user to the Salesforce-hosted login form.

Behind the scenes, Swiftly Salesforce leverages Alamofire and PromiseKit, two very widely-adopted frameworks, for elegant handling of networking requests and asynchronous operations.

Example: Configure Your App to Talk to Salesforce

import UIKit
import SwiftlySalesforce

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, LoginDelegate {

    var window: UIWindow?
	
    /// Salesforce Connected App properties (replace with your own…)
    let consumerKey = "3MVG91ftiraGaMd_SSxxaqQgk21_rz_GVRxxFpDR6yDaxxEfpC0vKresPMY1kopH98G9Ockl2p7IJuqRk23nQ"
    let redirectURL = URL(string: "taskforce://authorized")!

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        configureSalesforce(consumerKey: consumerKey, redirectURL: redirectURL)
        return true
    }
	
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        handleRedirectURL(url: url)
        return true
    }
}

Note the following in the above example:

  1. Your app delegate should implement LoginDelegate
  2. Replace the values for consumerKey and redirectURL with the values defined in your Connected App
  3. Call configureSalesforce() and handleRedirectURL() as shown

Example: Retrieve a Salesforce Record

The following will retrieve all the fields for an account record:

salesforce.retrieve(type: "Account", id: "0013000001FjCcF")

To specify which fields should be retrieved:

let fields = ["AccountNumber", "BillingCity", "MyCustomField__c"]
salesforce.retrieve(type: "Account", id: "0013000001FjCcF", fields: fields)

Note that retrieve() is an asynchronous function, whose return value is a "promise" that will be fulfilled at some point in the future:

let promise = salesforce.retrieve(type: "Account", id: "0013000001FjCcF")

And we can add a closure that will be called later, when the promise is fulfilled:

promise.then {
	queryResult in
	for record in queryResult.records {
		// Parse JSON dictionary of record fields & values, and do interesting stuff…
	}
}

Example: Update a Salesforce Record

salesforce.update(type: "Task", id: "00T1500001h3V5NEAU", fields: ["Status": "Completed"])
.then {
	(_) -> () in
	// Update the local model
}.always {
	// Update the UI
}

The always closure will be called regardless of success or failure elsewhere in the promise chain.

Example: Querying

let soql = "SELECT Id,Name FROM Account WHERE BillingPostalCode = '\(postalCode)'"
salesforce.query(soql: soql)

See the next example for handling the query results.

Example: Chaining Asynchronous Requests

Let's say we want to retrieve a random zip/postal code from a custom Apex REST resource, and then use that zip code in a query:

// Chained asynch requests 
first {
    // Make GET request of custom Apex REST resource
    // (Enclosing this in a ‘first’ block is optional and can keep things neat.)
    salesforce.apexRest(path: "/MyApexResourceThatEmitsRandomZip")
}.then {
    // Query accounts in that zip code
    result in
    guard let zip = result["zip"] as? String else {
        throw TaskForceError.generic(100, “Can’t get random zip code from our custom REST endpoint!)
    }
    let soql = "SELECT Id,Name FROM Account WHERE BillingPostalCode = '\(zip)'"
    return salesforce.query(soql: soql)
}.then {
    queryResult in
    for record in queryResult.records {
        if let name = record["Name"] as? String {
          print("Account name = \(name)")
        }
    }
}

You could repeat this chaining multiple times, feeding the result of one asynchronous operation as the input to the next. Or you could spawn multiple, simultaneous operations and easily specify logic to be executed when all operations complete, or when just the first completes, or when any one operation fails, etc. PromiseKit is an amazingly-powerful framework for handling multiple asynchronous operations that would otherwise be very difficult to coordinate. See PromiseKit documentation for more examples.

Example: Handling Errors

The following code is adapted from the example file, TaskStore.swift and shows how to handle errors:

first {
    // Get ID of current user
    salesforce.identity()
}.then {
    // Query tasks owned by user
    userInfo in
    guard let userID = userInfo.userID else {
        throw TaskForceError.generic(code: 100, message: "Can't determine user ID")
    }
    let soql = "SELECT Id,Subject,Status,What.Name FROM Task WHERE OwnerId = '\(userID)' ORDE 	R BY CreatedDate DESC"
    return salesforce.query(soql: soql)
}.then {
    // Parse JSON into Task instances
    (result: QueryResult) -> () in
    let tasks = result.records.map { Task(dictionary: $0) }
    // Do something interesting with the tasks…
}.catch {
    error in
    // Handle the error…
}

You could also recover from an error, and continue with the chain, using a recover closure. The following snippet is from PromiseKit's documentation:

CLLocationManager.promise().recover { err in
    guard !err.fatal else { throw err }
    return CLLocationChicago
}.then { location in
    // the user’s location, or Chicago if an error occurred
}.catch { err in
    // the error was fatal
}

Example: Log Out

If you want to log out the current Salesforce user, and then clear any locally-cached data, you could call the following. Swiftly Salesforce will revoke and remove any stored credentials, and automatically display a Safari View Controller with the Salesforce login page, ready for another user to log in.

// Call this when your app's "Log Out" button is tapped, for example
if let app = UIApplication.shared.delegate as? LoginDelegate {
    app.logout().then {
        () -> () in
        // Clear any cached data and reset the UI
        return
    }.catch {
        error in
        debugPrint(error)
    }
}

Main Components of Swiftly Salesforce

  • Salesforce.swift: This is your Swift interface to the Salesforce Platform, and likely the only file you’ll refer to. It has methods to query, retrieve, update and delete records, and to access custom Apex REST endpoints.

  • Router.swift: Acts as a 'router' for Alamofire requests. The more important and commonly-used Salesforce REST API endpoints are represented as enum values, including one for custom Apex REST endpoints.

  • AuthData.swift: Swift struct that holds tokens, and other data, required for each request made to the Salesforce REST API. These values are stored securely in the iOS keychain.

  • Extensions.swift: Swift extensions used by other components of Swiftly Salesforce. The extensions that you'll likely use in your own code are DateFormatter.salesforceDateTime, and DateFormatter.salesforceDate, for converting Salesforce date/time and date values to and from strings for JSON serialization.

  • AuthManager.swift: Coordinates the OAuth2 authorization process, and securely stores and retrieves the resulting access token. The access token must be included in the header of every HTTP request to the Salesforce REST API. If the access token has expired, the AuthManager will attempt to refresh it. If the refresh process fails, then AuthManager will call on its delegate to authenticate the user, that is, to display a Salesforce-hosted web login form. The default implementation uses a Safari View Controller (new in iOS 9) to authenticate the user via the OAuth2 'user-agent' flow. Though 'user-agent' flow is more complex than the OAuth2 'username-password' flow, it is the preferred method of authenticating users to Salesforce, since their credentials are never handled by the client application.

Dependent Frameworks

The great Swift frameworks leveraged by Swiftly Salesforce:

  • PromiseKit: "Not just a promises implementation, it is also a collection of helper functions that make the typical asynchronous patterns we use as iOS developers delightful too."
  • Alamofire: "Elegant HTTP Networking in Swift"
  • Locksmith: "A powerful, protocol-oriented library for working with the keychain in Swift."

Resources

If you're new to Swift, the Salesforce Platform, or the Salesforce REST API, you might find the following resources useful:

About Me

I'm a senior technical 'evangelist' at Salesforce, and I work with ISV partners who are building applications on the Salesforce Platform.

Contact

Questions, suggestions, bug reports and code contributions welcome:

Appendix

Add Swiftly Salesforce to Your CocoaPods Podfile

Adding Swiftly Salesforce to a simple Podfile:

target 'MyApp' do
  use_frameworks!
  pod 'SwiftlySalesforce'
  # Another pod here
end

See Podfile for more details

Register Your Connected App's Callback URL Scheme with iOS

Upon successful OAuth2 authorization, Salesforce will redirect the Safari View Controller back to the callback URL that you specified in your Connected App settings, and will append the access token (among other things) to that callback URL. Add the following to your app's .plist file, so iOS will know how to handle the URL, and will pass it to your app's delegate.

<!-- ADD TO YOUR APP'S .PLIST FILE -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>SalesforceOAuth2CallbackURLScheme</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string><!-- YOUR CALLBACK URL'S SCHEME HERE (scheme only, not entire URL) --></string>
    </array>
  </dict>
</array>

Then, you just need to add a single line in your app delegate class so that Swiftly Salesforce will handle the callback URL and the appended credentials.

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    handleRedirectURL(redirectURL: url as URL)
    return true
}

Configure your App Delegate for Swiftly Salesforce

Update your app delegate class so that it:

  • Configures Swiftly Salesforce with your Connected App's consumer key and callback URL
  • Implements LoginDelegate - you don't have to implement any methods, though, thanks to the magic of Swift's protocol extensions
  • Calls handleRedirectURL(redirectURL:) when asked by iOS to open the callback URL.
import UIKit
import SwiftlySalesforce

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, LoginDelegate {

    var window: UIWindow?
	
    /// Salesforce Connected App properties (replace with your own...)
    let consumerKey = "< YOUR CONNECTED APP’S CONSUMER KEY HERE >"
    let redirectURL = URL(string: "< YOUR CONNECTED APP’S REDIRECT URL HERE >")!

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        configureSalesforce(consumerKey: consumerKey, redirectURL: redirectURL)
        return true
    }
	
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        handleRedirectURL(redirectURL: url as URL)
        return true
    }
}

Add an ATS Exception for Salefsorce

As of this writing, you need to add an application transport security (ATS) exception to your iOS application's .plist file to allow it to connect to salesforce.com, as follows:

<!-- ADD TO YOUR APP'S .PLIST FILE -->
<key>NSAppTransportSecurity</key>
<dict>
	<key>NSExceptionDomains</key>
	<dict>
		<key>salesforce.com</key>
		<dict>
			<key>NSExceptionRequiresForwardSecrecy</key>
			<false/>
			<key>NSIncludesSubdomains</key>
			<true/>
		</dict>
	</dict>
</dict>

swiftlysalesforce's People

Contributors

mike4aday avatar hmuronaka avatar

Watchers

Larkin Whitaker avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.