Salesforce for iPad (released February 2012) is the new Salesforce Viewer (released November 2011) which is the new Account Viewer (released August 2011).
Account Viewer is a free, full-featured open-source native iPad app written in Objective-C and Cocoa. Account Viewer is the easiest way to browse your Accounts in any Salesforce environment and read news headlines tailored to them. Store your most important Accounts locally for secure offline access. Account Viewer has robust encrypted local account functionality and does not require you ever connect it to a Salesforce org.
by Jonathan Hersh.
This document is intended to introduce you to the app's architecture and design and make it as easy as possible for you to jump in, run it, and start contributing.
Account Viewer's source is freely available on GitHub.
In this document:
- Release History
- Account Viewer License
- Authentication and Security
- Getting Started
- App Architecture
- External APIs
- Third-party Code
New in v1.1 (October 12, 2011)
- Related Lists! Browse all related lists on your Account layout and tap individual related records to view full related record detail!
- Share any article or URL to Chatter! With any webpage open, tap the action link and choose 'Share to Chatter'.
- Browse up to 50,000 owned accounts in the "My Accounts" list.
- Full support for rich text fields, including links and images!
- Numerous other tweaks, fixes, and performance improvements.
New in v1.0.1 (September 8, 2011)
- Fixes an issue where some users were unable to view any remote account if they didn't have field-level security access to one of the four overview fields on Account (Name, Phone, Website, Industry)
- Corrected display of decimal fields to properly match their formatting on salesforce.com
- Better validation on custom login host endpoints
- Preliminary support for rich text fields
- Numerous spacing/sizing/alignment fixes and other minor corrections
New in v1.0 (August 25, 2010)
- Initial Release
Copyright (c) 2011, salesforce.com, inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Almost all interaction with Salesforce web services is accomplished through the
zksForce library with the exception of the OAuth flow in the
OAuthViewController class. By default, the app uses OAuth for all authentication, but there is built-in support for a hardcoded username/password as well - this is left in largely for testing purposes and to enable you to get up and running without setting up OAuth.
Local Accounts and OAuth refresh tokens are encrypted into the device's keychain. Remote accounts, page layouts, and Account sObject describes are cached in-memory and cleared upon logout.
Some data (Account names and addresses) are sent to third-party APIs to provide app functionality, but always over HTTPS. More details below in the API section.
Other app details (first-run settings, other app preferences) are stored in
- Grab the Account Viewer source code:
git clone https://github.com/ForceDotComLabs/Account-Viewer.git
Choose either OAuth or a hardcoded username/password for your login method.
For OAuth, create a new Remote Access application (Setup -> Develop -> Remote Access) and copy your OAuth Client ID into the
RootViewController.h. Then, set the
For client login with a hardcoded username/password, enter your credentials into the
RootViewController.m. Then, set the
If you have a Google API key, paste it into
- Build and run, and you should be good to go!
- If you're getting build warnings/errors akin to "Multiple build commands for output file...", you'll need to remove the .git directory from your project. See this answer for more detail.
When the app first loads, it evaluates whether it has a stored OAuth refresh token from a previous authentication. If so, it attempts to refresh the SFDC session with that refresh token. See
RootViewController.m. If there is no stored refresh token, or if the refresh fails for any reason, the app destroys all session data and places itself in offline Local Accounts mode.
The left-side navigation view (in landscape mode, also visible in portrait mode in a popover when you tap the 'Accounts' button), a.k.a. the Master view, is powered by the
SubNavViewController class. The
RootViewController initializes three instances of
SubNavViewController - Local Accounts, My Accounts, and Accounts I Follow - and stacks them on top of each other in the Master view. You can switch between them by tapping on the
SubNavViewController's name in the upper left. If the user does not have a valid SFDC session, only Local Accounts are accessible and attempting to switch to any of the others will show a login screen.
The right-side view is powered by the
DetailViewController. It serves mostly as a container for the rest of the app's content and is responsible for creating, managing, and destroying Flying Windows. It also ensures that Flying Windows cannot be dragged off the screen, and it is responsible for applying inertial dragging when Flying Windows are moved as well as the overall management of the Flying Window stack.
The various interactive, draggable panes that fill the
DetailViewController - the record overview pane, news results pane, web view pane, list of related lists, related record grid, and related record views - are termed Flying Windows and each is a subclass of the
FlyingWindowController class. They are, respectively,
FlyingWindowController base class defines some basics about its look and enables it to be dragged about the screen.
RecordOverviewController is responsible for displaying a selected Account's record overview (Name, Industry, Phone, Website), rendering the Account's location on a map, and rendering the full record page layout for the Account.
RecordNewsViewController is responsible for querying Google News (over HTTPS) and displaying news stories about a single Account or a list of Accounts (if no single Account has yet been selected).
WebViewController is a simple
UIWebView with a few added pieces of functionality, like being able to email the link to the open page, copy its URL, open in Safari, and expand the webview to full-screen.
ListOfRelatedListsViewController lists all of the related lists on an Account. The list ordering as well as which lists appear is determined by your Account page layout. This view controller also chains subqueries together to display the number of related records on each list before you tap one.
RelatedListGridView displays the related records on an Account for a given related object. The columns displayed on the grid are determined by your Account page layout. Related record grids have tap-to-sort columns and tapping an individual record's name will open its full detail.
RelatedRecordViewController renders the full two-column layout for a related record. It supports any layoutable object, standard and custom alike.
FieldPopoverButton is a generic
UIButton intended to display the value of an sObject field. All
FieldPopoverButtons can be tapped to copy the text value of that field, but depending on the field type, some may have additional actions. For example, a
FieldPopoverButton displaying an address will offer to open the address in Google Maps, phone/email fields will offer to call with Facetime or Skype, and lookups to User will display a full-featured user profile with a photo and other details from the User record.
CommButton is a generic
UIButton intended to make it easy to Email, Skype, Facetime, or open the website for any field on the sObject. If an Account page layout has three fields of type Phone, for example, a
CommButton of type Skype, when tapped, will allow you to place a Skype call to any of those three phone numbers.
FollowButton is a generic
UIBarButtonItem intended to make it easy to create a follow/unfollow toggle between the running user and any other chatter-enabled object (User, Account, etc).
ChatterPostController is the main interface for sharing an article or URL to chatter. It's geared mostly around sharing links, so while linkUrl and title are not traditionally required fields in a Chatter post, they are required here.
ObjectLookupController is a lookup box launched when you tap the 'Post To' field in the
ChatterPostController. It allows you to search for a User, Chatter Group, or Account (if Accounts are chatter-enabled in the current environment).
AccountUtil is a singleton, a general utility class that encapsulates many common functions used throughout the application.
AccountUtil handles metadata operations, like querying and processing sObject describes, page layouts, as well as rendering the full record page layout for an Account.
AccountUtil is also responsible for all local database operations for local accounts, processing sObject fields, various string manipulation utility functions, managing the network activity indicator, logging app errors, and other miscellaneous operations like determining the current IP address.
Account Viewer makes use of several external APIs.
- Google's Geocoding API allows Account Viewer to convert an account street address into a latitude/longitude coordinate pair for display on a map.
- Google's News Search API provides news articles, images, bylines, and article summaries. Google deprecated this API on May 26, 2011, but it will remain operational for at least 2.5-3 years after that date. At some point, Account Viewer will likely need to transition to a different news API.
Account Viewer uses HTTPS when communicating with these APIs, so no user or account data should ever be traveling in the clear.
Account Viewer makes use of a number of third-party components:
- zksForce, a Cocoa library for calling the Salesforce Web Services APIs.
- Various components from Matt Drance's excellent iOS Recipes book.
- DSActivityView for loading and authentication indicators.
- MGSplitViewController, a modified split view that powers the app's main interface.
- JSON-Framework, a JSON parser for objective-C.
- InAppSettingsKit for in-app and Settings.app settings.
- AQGridView, a grid layout system used in the Account record overview.
- PullRefreshTableViewController, an easy way to add pull-to-refresh to most any table.