Integrating Android Apps with Force.com

Author: Seetha Annamraju

Abstract: This tutorial gives an example of a native Android application and how to integrate it with the Salesforce Analytics API. It is a step-by-step procedure for pushing data to your mobile clients using the API, and an explanation of how to leverage the Analytics API for reporting needs.

When you’re building data-driven cloud applications, you’re eventually going to need to present that data in reports, on dashboards, etc. That’s where the Analytics API comes in handy. The Analytics API allows you to retrieve reports programmatically through JSON to present to users. These reports can then be shown graphically on both mobile and desktop applications. The objective of this tutorial will be to gain an understanding of how the Salesforce Analytics API works, how to authenticate using OAuth, understand and retrieve basic results using REST & SOQL, and graph the results using achartEngine [9]. The Opportunities by Type report is used, similar to this tutorial, but instead there will be a focus on making it work with the native Android platform, as well as graphing some of this data.

In this example, I am using Mac OS X 10.9.4 with Android ADT and Java 1.7 for development and testing. The API level used is 16, but it should work on other versions as well. The instructions given in this tutorial should work with minor modifications for Windows/Linux platforms as well.

If you want to access the entire source code from this tutorial, it can be found here:
https://github.com/seethaa/force_analytics_example/tree/master/native/TemplateApp

When to Use Analytics API

There is a lot of data that is now available under Salesforce Dashboard and Reports. There may be situations where you want to allow your customers to have Salesforce integration through a mobile platform so that they can access important reports easily. The Analytics API allows you to programmatically run and retrieve reports to display through your app. This can be done both synchronously and asynchronously, making the API much more useful. The data returned is in JSON, which can be parsed and presented in a user-friendly manner.

Setting up your development account and Prerequisites

To get started, you will need to do the following:

  • Go to https://developer.salesforce.com/signup, and sign up for your Developer Edition (DE) account. For the purposes of this example, I recommend sign up for a Developer Edition even if you already have an account. This ensures you get a clean environment with the latest features enabled.
  • Navigate to https://login.salesforce.com to log into your developer account.
  • In the left-hand menu choose Create > Apps and under the Connected Apps section click New to create a new Connected App.
  • Name it “MySample App”, enter your email and check “Enable OAuth Settings”
  • Enter sfdc://success as your Callback URL.
  • Select all available OAuth Scopes
  • Save the Connected App


Figure 1: Creating a new connected app

  • Copy the Consumer Key and Consumer Secret for later


Figure 2: Save Consumer Key and Consumer Secret

Authentication

Now you have the basic information from salesforce.com to test the REST/SOQL APIs. As Salesforce Platform uses OAuth2.0, you first need to authenticate and get an access token from salesforce.com to use the data connected to the developer account. This can be done as follows:

Give this command in command line:

curl -d “grant_type=password” -d “client_id=your_consumer_id” -d “client_secret=your_consumer_secret” -d “username=your_email_id” -d “password=your_password + your_account_token” https://login.salesforce.com/services/oauth2/token

If you are working through a Mac or Linux-based command line interface, remember to escape each line with a backslash [6]:
curl –form client_id=3MVG9PhR6g6B7ps4xDycwGrI4PvjVZvK9 \
–form client_secret=8870355475032095511 \
–form grant_type=password \
–form username=admin@seattleapps.com \
–form password=1Lsfdc!xxxxxxxxxtokenxxx \
https://login.salesforce.com/services/oauth2/token

*Replace all bolded items with the corresponding information.

NOTE: (i) Your account token is the token you got when you first registered your account. If you did not receive this string, then sign in to developer console. Go to YourName → My Settings → Personal Information → Reset your token. You will get another email with new token. Use this token, and form the curl command. Remember that form password = your password concatenated with the token.

You will get a response similar to this:

{“id”:”https://login.salesforce.com/id/00DF00000008MpPMAU/005F0000003Z6tGIAS”,”issued_at”:”1406842804137″,
“token_type”:”Bearer”,”instance_url”:”https://na10.salesforce.com”,”signature”:
“eXYtXSwWiNpoMVRwW21rsotHNBp8uclw54EijmlnQzA=”,”access_token”:”00DF00000008MpP
!ARAAQIrqJxjrpwn30SYNwpeGPX8q1CimE.qW2U0Xl9hRzg1DNL4omztCyE7hhmG5YhXOi.iZ6BZqbF1z7JMzCE_PCwnZstT3″}

Save this access token to test more curl commands using any API. In this tutorial, I will focus on the Analytics API, but the steps can be modified to use any API with Android.

Generating Data in Salesforce Platform

To get the analytics working, you first need to add some data to your Force.com account in order to generate reports and test them using the API. Go to your main page of Force.com and click on the “+” sign on the main bar. Select “Opportunities” from there; Click on New Opportunities, to add a few opportunities. In this example, I have added 11 test opportunities with various opportunity types (“Existing Customer- Upgrade”, “Existing Customer – Replacement”, “New Customer”, etc.).

Make sure to fill in the “Amount” field to get data for graphing later.


Figure 3: Creating a Test Opportunity

Figure 4: View All Created Test Opportunities

Now when you go to Reports tab, you will be able to generate a report for opportunities. Click on Reports → Opportunity Reports → Opportunities By Type. Click Run report, and you will be able to see the report you just created. Play around to get hold of this report’s functionality. When you have finished, click on save as to save this custom report. Let’s give it the name “firstReport”.

Figure 4: Opportunity by Type Results Table

Clicking on Show Details will allow you to get the list of all opportunities and the values used:

Figure 6: All Test Opportunities with Corresponding Values

Now you are ready to test the Analytics REST API. Give this command on the command line to get the JSON response:


curl -X GET https://na10.salesforce.com/services/data/v29.0/analytics/reports -H

‘Authorization: Bearer 00Di0000000kgkh!ASAAQPTN19sjlasdDow1FSax6sda
EInN7GhI168f73F7e2wVk5SUQQllL5Mw7YUrv8GgCSQqLjrTX2Rscbw._Loqbc6W4A.3CB’

 

where services/data/v29.0/analytics/reports fetches the latest reports.

You will see the data from the report you just created in JSON format on your screen.

Android Native App Prerequisites

Now that you have the basics ready, it is time to build a solid android native app to leverage these APIs. There are two ways to create a native Android app using the Salesforce SDK, and both are described here. The one used for this project was the “GitHub repository” method, but both should work without issues.

1. Mobile SDK npm Packages [4]

The tutorial can be found here.

OR

GitHub Repository [5]

The tutorial can be found here.

This method requires that you have your Eclipse Development Environment set up with ADT and any required Android configurations. If you already have that set up, then the easiest way to get started is to clone the GitHub project from here: https://github.com/forcedotcom/SalesforceMobileSDK-Android
Import TemplateApp from the samples; you can edit this project directly to integrate with the Analytics API. Make sure TemplateApp has SalesforceSDK as a library (Right click on the project → Properties → Android)

This template project has three main files:

  • KeyImpl.java – Oauth
  • TemplateApp.java – Initializes the application
  • MainActivity.java – Main file where all your code will reside.

Integrating Salesforce with Android

Change strings.xml to change all button names:

 

<?xml version=”1.0″ encoding=”utf-8″?><resources>

<string name=”account_type”>com.salesforce.samples.analyticsapp.login</string>

<string name=”app_name”><b>Salesforce Analytics API</b></string>

<string name=”app_package”>com.salesforce.samples.analyticsapp</string>

<string name=”welcome”>Welcome %s</string>

<string name=”logout_button”>Logout</string>

<string name=”fetch_reports_button”>Fetch Reports</string>

<string name=”fetch_graphs_button”>Show Graphs</string>

<string name=”clear_button”>Clear</string>

</resources>

 

 

 

Change the MainActivity’s xml file to contain a TextView and remove the ListView. In my case, this file is called main.xml. Add a scrollview so that you can see the complete JSON response.

 

<?xml version=”1.0″ encoding=”utf-8″?>
<ScrollView xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/scrollView1″
android:layout_width=”match_parent”
android:layout_height=”wrap_content” >

<LinearLayout
android:id=”@+id/root”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:background=”#454545″
android:orientation=”vertical” >

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical” >
</LinearLayout>

<include layout=”@layout/header” />

<LinearLayout
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:orientation=”horizontal” >

<Button
android:id=”@+id/fetch_reports”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:onClick=”onFetchReportsClick”
android:text=”@string/fetch_reports_button” />

<Button
android:id=”@+id/fetch_graphs”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:onClick=”onFetchAccountsClick”
android:text=”@string/fetch_graphs_button” />

<Button
android:id=”@+id/clear”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:onClick=”onClearClick”
android:text=”@string/clear_button” />
</LinearLayout>

<TextView
android:id=”@+id/textView1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”First fetch reports” />

</LinearLayout>

</ScrollView>

 

 

The MainActivity was changed to add a different request to retrieve all reports data for “firstReport”. The report ID for this can be found in the URL when you click on the report you wish to retrieve data from. For example, the URL for my firstReport is https://na10.salesforce.com/00OF0000005q9Jx, so the ID is 00OF0000005q9Jx.

The send request would then be formatted as below:

 

RestRequest feedRequest = generateRequest(“GET”, “analytics/reports/00OF0000005q9Jx?includeDetails=true”, null);sendRequest(feedRequest);

 

The next step is to create a parse method to go through the entire JSON response, and capture the percentages necessary for graphing.

The complete MainActivity.java code is given below:

 

/*lab
* Copyright (c) 2012, salesforce.com, inc.
* All rights reserved.
* Redistribution and use of this software 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 of salesforce.com, inc.
* 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.
*/
package com.salesforce.samples.analyticsapp;import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONObject;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.salesforce.androidsdk.app.SalesforceSDKManager;
import com.salesforce.androidsdk.rest.RestClient;
import com.salesforce.androidsdk.rest.RestRequest;
import com.salesforce.androidsdk.rest.RestResponse;
import com.salesforce.androidsdk.ui.sfnative.SalesforceActivity;

/**
* Main activity
* Starts an activity with a options to fetch reports from Salesforce Analytics API, and graph the average amounts as a pie chart
* This can be easily adapted to create remaining graphs
*/

public class MainActivity extends SalesforceActivity {
public static double[] percentages = new double[5];
private RestClient client;
private ArrayAdapter<String> listAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Setup view
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.fetch_graphs);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Bundle b=new Bundle();
b.putDoubleArray(“percentages”, percentages);
Intent i = new Intent(MainActivity.this, GraphActivity.class);
i.putExtras(b);
startActivity(i);
}
});
}
@Override
public void onResume() {
// Hide everything until we are logged in
findViewById(R.id.root).setVisibility(View.INVISIBLE);
super.onResume();
}
@Override
public void onResume(RestClient client) {
// Keeping reference to rest client
this.client = client;
// Show everything
findViewById(R.id.root).setVisibility(View.VISIBLE);
}
/**
* Called when “Logout” button is clicked.
*
* @param v
*/
public void onLogoutClick(View v) {
SalesforceSDKManager.getInstance().logout(this);
}
/**
* Called when “Clear” button is clicked.
*
* @param v
*/
public void onClearClick(View v) {
listAdapter.clear();
}
/**
* Called when “Fetch Reports” button is clicked
*
* @param v
* @throws UnsupportedEncodingException
*/
public void onFetchReportsClick(View v) throws UnsupportedEncodingException {
RestRequest feedRequest = generateRequest(“GET”, “analytics/reports/00OF0000005q9Jx?includeDetails=true”, null);
sendRequest(feedRequest);
}
/**
* Helper function to round doubles up to two decimal places.
* @param d
* @return double result
*/
public double roundTwoDecimals(double d) {
DecimalFormat twoDForm = new DecimalFormat(“#.##”);
return Double.valueOf(twoDForm.format(d));
}
/**
* Main parse function which reads the JSON response from server and parses data
* to retrieve all amounts.
* This parse function will only work for Opportunity type reports.
* _rev and _age variables can be used for graphing revenue and age components.
* @param jsonLine
* @return
*/
public String parse(String jsonLine) {
try {
JSONObject reader = new JSONObject(jsonLine);
String factMaps = reader.getString(“factMap”);
TextView tv = (TextView) findViewById(R.id.textView1);
tv.setText(jsonLine);
JSONObject maps = new JSONObject(factMaps); //creates new json object for all maps
//GET TOTALS (T!T)
String T_T_full = maps.getString(“T!T”);
JSONObject T_T_object = new JSONObject(T_T_full);
JSONArray T_T_label = T_T_object.getJSONArray(“aggregates”);
String T_amt = T_T_label.getJSONObject(0).getString(“label”);
System.out.println(“T_amt: ” + T_amt);
T_amt = T_amt.substring(1);
int T_T_amt_int = NumberFormat.getNumberInstance(java.util.Locale.US).parse(T_amt).intValue();
System.out.println(“T int: ” + T_T_amt_int);
String T_rev = T_T_label.getJSONObject(1).getString(“label”);
System.out.println(“T_rev: ” + T_rev);
String T_age = T_T_label.getJSONObject(2).getString(“label”);
System.out.println(“T_age: ” + T_age);
//GET TYPE (0!T)
String T_0_full = maps.getString(“0!T”);
JSONObject T_0_object = new JSONObject(T_0_full);
JSONArray T_0_label = T_0_object.getJSONArray(“aggregates”);
String T_0_amt = T_0_label.getJSONObject(0).getString(“label”);
System.out.println(“T_0_amt: ” + T_0_amt);
T_0_amt = T_0_amt.substring(1);
int T_0_amt_int = NumberFormat.getNumberInstance(java.util.Locale.US).parse(T_0_amt).intValue();
System.out.println(“0 int: ” + T_0_amt_int);
double T_0_amt_avg = ( ((double) T_0_amt_int) / ((double) T_T_amt_int)) * 100;
T_0_amt_avg = roundTwoDecimals(T_0_amt_avg);
percentages[0] = T_0_amt_avg;
System.out.println(“0 avg: ” + T_0_amt_avg);
String T_0_rev = T_0_label.getJSONObject(1).getString(“label”);
System.out.println(“T_0_rev: ” + T_0_rev);
String T_0_age = T_0_label.getJSONObject(2).getString(“label”);
System.out.println(“T_0_age: ” + T_0_age);
//GET EXISTING CUSTOMER – UPGRADE (1!T)
String T_1_full = maps.getString(“1!T”);
System.out.println(“full string 1t : “+ T_1_full);
JSONObject T_1_object = new JSONObject(T_1_full);
JSONArray T_1_label = T_1_object.getJSONArray(“aggregates”);
String T_1_amt = T_1_label.getJSONObject(0).getString(“label”);
System.out.println(“T_1_amt: ” + T_1_amt);
T_1_amt = T_1_amt.substring(1);
int T_1_amt_int = NumberFormat.getNumberInstance(java.util.Locale.US).parse(T_1_amt).intValue();
System.out.println(“1 int: ” + T_1_amt_int);
double T_1_amt_avg = ( ((double) T_1_amt_int) / ((double) T_T_amt_int)) * 100;
T_1_amt_avg = roundTwoDecimals(T_1_amt_avg);
percentages[1] = T_1_amt_avg;
System.out.println(“1 avg: ” + T_1_amt_avg);
String T_1_rev = T_1_label.getJSONObject(1).getString(“label”);
System.out.println(“T_1_rev: ” + T_1_rev);
String T_1_age = T_1_label.getJSONObject(2).getString(“label”);
System.out.println(“T_1_age: ” + T_1_age);
//GET EXISTING CUSTOMER – REPLACEMENT (2!T)
String T_2_full = maps.getString(“2!T”);
System.out.println(“full string 2t : “+ T_2_full);
JSONObject T_2_object = new JSONObject(T_2_full);
JSONArray T_2_label = T_2_object.getJSONArray(“aggregates”);
String T_2_amt = T_2_label.getJSONObject(0).getString(“label”);
System.out.println(“T_2_amt: ” + T_2_amt);
T_2_amt = T_2_amt.substring(1);
int T_2_amt_int = NumberFormat.getNumberInstance(java.util.Locale.US).parse(T_2_amt).intValue();
System.out.println(“2 int: ” + T_2_amt_int);
double T_2_amt_avg = ( ((double) T_2_amt_int) / ((double) T_T_amt_int)) * 100;
T_2_amt_avg = roundTwoDecimals(T_2_amt_avg);
percentages[2] = T_2_amt_avg;
System.out.println(“2 avg: ” + T_2_amt_avg);
String T_2_rev = T_2_label.getJSONObject(1).getString(“label”);
System.out.println(“T_2_rev: ” + T_2_rev);

String T_2_age = T_2_label.getJSONObject(2).getString(“label”);
System.out.println(“T_2_age: ” + T_2_age);

//GET EXISTING CUSTOMER – DOWNGRADE (3!T)
String T_3_full = maps.getString(“3!T”);
System.out.println(“full string 3t : “+ T_3_full);
JSONObject T_3_object = new JSONObject(T_3_full);
JSONArray T_3_label = T_3_object.getJSONArray(“aggregates”);
String T_3_amt = T_3_label.getJSONObject(0).getString(“label”);
System.out.println(“T_3_amt: ” + T_3_amt);

T_3_amt = T_3_amt.substring(1);
int T_3_amt_int = NumberFormat.getNumberInstance(java.util.Locale.US).parse(T_3_amt).intValue();
double T_3_amt_avg = ( ((double) T_3_amt_int) / ((double) T_T_amt_int)) * 100;
T_3_amt_avg = roundTwoDecimals(T_3_amt_avg);
percentages[3] = T_3_amt_avg;
String T_3_rev = T_3_label.getJSONObject(1).getString(“label”);
System.out.println(“T_3_rev: ” + T_3_rev);
String T_3_age = T_3_label.getJSONObject(2).getString(“label”);

System.out.println(“T_3_age: ” + T_3_age);

//NEW CUSTOMER (4!T)
String T_4_full = maps.getString(“4!T”);
System.out.println(“full string 4t : “+ T_4_full);
JSONObject T_4_object = new JSONObject(T_4_full);
JSONArray T_4_label = T_4_object.getJSONArray(“aggregates”);
String T_4_amt = T_4_label.getJSONObject(0).getString(“label”);
System.out.println(“T_4_amt: ” + T_4_amt);
T_4_amt = T_4_amt.substring(1);
int T_4_amt_int = NumberFormat.getNumberInstance(java.util.Locale.US).parse(T_4_amt).intValue();
double T_4_amt_avg = ( ((double) T_4_amt_int) / ((double) T_T_amt_int)) * 100;
T_4_amt_avg = roundTwoDecimals(T_4_amt_avg);
percentages[4] = T_4_amt_avg;
String T_4_rev = T_4_label.getJSONObject(1).getString(“label”);
System.out.println(“T_4_rev: ” + T_4_rev);

String T_4_age = T_4_label.getJSONObject(2).getString(“label”);
System.out.println(“T_4_age: ” + T_4_age);

for (int i=0; i< percentages.length; i++){
System.out.println(“%PS: ” + percentages[i]);
}

} catch (Exception e) {
Toast.makeText(getBaseContext(), e.getMessage(),
Toast.LENGTH_SHORT).show();
}
return null;
}
private RestRequest generateRequest(String httpMethod, String resource, String jsonPayloadString) {
RestRequest request = null;
if (jsonPayloadString == null) {
jsonPayloadString = “”;
}
String url = String.format(“/services/data/%s/” + resource, getString(R.string.api_version)); // The IDE might highlight this line as having an error. This is a bug, the code will compile just fine.
try {
HttpEntity paramsEntity = getParamsEntity(jsonPayloadString);
RestRequest.RestMethod method = RestRequest.RestMethod.valueOf(httpMethod.toUpperCase());
request = new RestRequest(method, url, paramsEntity);
return request;
} catch (UnsupportedEncodingException e) {
Log.e(“ERROR”, “Could not build request”);
e.printStackTrace();
}
return request;
}

private HttpEntity getParamsEntity(String requestParamsText)
throws UnsupportedEncodingException {
Map<String, Object> params = parseFieldMap(requestParamsText);
if (params == null) {
params = new HashMap<String, Object>();
}
List<NameValuePair> paramsList = new ArrayList<NameValuePair>();
for (Map.Entry<String, Object> param : params.entrySet()) {
paramsList.add(new BasicNameValuePair(param.getKey(),
(String) param.getValue()));
}
return new UrlEncodedFormEntity(paramsList);
}
private Map<String, Object> parseFieldMap(String jsonText) {
String fieldsString = jsonText;
if (fieldsString.length() == 0) {
return null;
}
try {
JSONObject fieldsJson = new JSONObject(fieldsString);
Map<String, Object> fields = new HashMap<String, Object>();
JSONArray names = fieldsJson.names();
for (int i = 0; i < names.length(); i++) {
String name = (String) names.get(i);
fields.put(name, fieldsJson.get(name));
}
return fields;
} catch (Exception e) {
Log.e(“ERROR”, “Could not build request”);
e.printStackTrace();
return null;
}
}
private void sendRequest(RestRequest restRequest) {
client.sendAsync(restRequest, new RestClient.AsyncRequestCallback() {
@Override
public void onSuccess(RestRequest request, RestResponse result) {
try {
//Do something with JSON result.
System.out.println(result); //Use our helper function, to print our JSON response.
System.out.println(“got here”);
String data = parse(result.toString());
System.out.println(“data: ” +data);
} catch (Exception e) {
e.printStackTrace();
}
//EventsObservable.get().notifyEvent(EventsObservable.EventType.RenditionComplete);
}
@Override
public void onError(Exception e) {
e.printStackTrace();
//EventsObservable.get().notifyEvent(EventsObservable.EventType.RenditionComplete);
}
});
}
}

 

Graphing Data with achartEngine [9]

The send request and parse methods can be modified to obtain any values necessary from Salesforce. This tutorial is focused on retrieving Opportunity type, so the parse method is customized for this purpose.

It is nice for users to have a visual representation of all of the data, and you can use achartEngine to achieve this purpose. AChartEngine is an open-source framework, free to use in any free or commercial application. It is based on the Apache 2.0 license.

I am using the “Show Graphs” button from the MainActivity’s UI to redirect to GraphActivity.

First, download the achartEngine jar file from http://www.achartengine.org/ and add the file under libs folder of your TemplateApp project.

Make sure to add this line to your manifest file:

 

<activity android:name=”org.achartengine.GraphicalActivity”/>

 

Create a GraphActivity.java, which is in charge of initiating the PieChart:

 

package com.salesforce.samples.analyticsapp;
import com.salesforce.samples.analyticsapp.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
public class GraphActivity extends Activity {
Button efforts;
public static double[] percent_array;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.graph_main);
Bundle b=this.getIntent().getExtras();
percent_array=b.getDoubleArray(“percentages”);
PieChart effort = new PieChart();
Intent effortIntent = effort.getIntent(this);
startActivity(effortIntent);
}
}

 

Create a new XML file for the graph activity. I called mine graph_main.xml:

 

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_marginLeft=”30dp”
android:layout_marginRight=”30dp”
android:orientation=”vertical” >
</LinearLayout>

 

Create a java file called PieChart.java, which will take care of rendering the graph:

 

package com.salesforce.samples.analyticsapp;import org.achartengine.ChartFactory;

import org.achartengine.model.CategorySeries;

import org.achartengine.renderer.DefaultRenderer;

import org.achartengine.renderer.SimpleSeriesRenderer;

import android.content.Context;

import android.content.Intent;

import android.graphics.Color;

import android.graphics.Typeface;

public class PieChart {

public Intent getIntent(Context context){

double []Performance = GraphActivity.percent_array;

CategorySeries series = new CategorySeries(“pie”); // adding series to charts. //collect 3 value in array. therefore add three series.

series.add(“——–“,Performance[0]);

series.add(“Existing Customer – Upgrade”,Performance[1]);

series.add(“Existing Customer – Replacement”,Performance[2]);

series.add(“Existing Customer – Downgrade”,Performance[3]);

series.add(“New Customer”,Performance[4]);

// add three colors for three series respectively

int []colors = new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE, Color.GRAY};

// set style for series

DefaultRenderer renderer = new DefaultRenderer();

for(int color : colors){

SimpleSeriesRenderer r = new SimpleSeriesRenderer();

r.setColor(color);

r.setDisplayBoundingPoints(true);

r.setDisplayChartValuesDistance(5);

r.setDisplayChartValues(true);

r.setChartValuesTextSize(20);

renderer.addSeriesRenderer(r);

}

renderer.setInScroll(true);

int[] margs = {10,10,10,10};

renderer.setMargins(margs);

renderer.setAntialiasing(false);

renderer.setZoomButtonsVisible(true);   //set zoom button in Graph

renderer.setApplyBackgroundColor(true);

renderer.setBackgroundColor(Color.WHITE); //set background color

renderer.setChartTitle(“Salesforce Analytics API Graph”);

renderer.setAxesColor(Color.BLACK);

renderer.setChartTitleTextSize((float) 40);

renderer.setTextTypeface(null, Typeface.BOLD);

renderer.setShowLabels(true);

renderer.setLabelsColor(Color.BLACK);

renderer.setLabelsTextSize(30);

renderer.setShowLegend(false);

renderer.setDisplayValues(true);

return ChartFactory.getPieChartIntent(context, series, renderer, “PieChart”);

}

}

 

This code already takes care of parsing the data that is received from the fetch reports function but the percent_array variable in GraphActivity.java is used to pass information between activities. As long as the right percentages array is passed in intentsExtra through MainActivity, the rest of the code will work as expected. More sections for the chart can be added through PieChart.java.

If everything went right so far, then running this Android application on your phone should produce a Salesforce login screen:


Figure 7: Salesforce Login Screen


Figure 8: Email Verification Code Screen


Figure 9: Salesforce Android Permission Screen

You should get a request permission page. Click Allow to proceed to the application. You may need to enter a verification code sent to email.

The first screen for the app will look similar to this:


Figure 10: Analytics API Fetch Reports Screen

You will need to press “Fetch Reports” before accessing the “Show Graphs” button so that the data can be retrieved and parsed first. Once “Fetch Reports” button is pressed, you should see a full JSON response similar to this:


Figure 11: Full JSON Response

The last screen is the pie chart, and can be accessed by pressing “Show Graphs” button. The final screen should have a pie chart with labels and properly calculated percentages for each section of the pie:


Figure 12: Pie Chart for Opportunities by Type

Summary
You have now learned how to use the Android Mobile SDK for Salesforce to create a simple mobile application. The Analytics API, which helps to retrieve any reports in JSON format, comes in handy when allowing your users to retrieve data either synchronously or asynchronously. After authenticating the user using OAuth 2.0, the given Template App can be edited to include the GET statement for retrieving all reports. The resulting JSON can be parsed using JSONArrays or JSONObjects, but the tutorial can be easily extended to use GSON for any complicated JSON formats. The resulting data was graphed using a pie chart through AchartEngine. In place of AChartEngine, there may be more customizable graphing frameworks, which work better for your application. The application can be further extended by including more APIs and turning this into a completely user-focused app.

Tips:
1. If you are using npm packages to install your project, make sure you have ant installed. If using Mac, use brew update / brew install ant to get ant ready on your machine. If ant clean debug or ant installd gives an error, check if you have an updated version of the SDK and platform tools. You may also get an error similar to “The SDK Build Tools revision (19.0.3) is too low for project ‘SalesforceSDK’. Minimum required is 19.1.0”.

2. The org.achartengine.GraphicalActivity activity which was added in manifest is not the same as GraphActivity.java

3. Make sure to add all new activities to Manifest file.

4. CURL command may not work sometimes because your access token has expired. If this is the case, simply submit the curl command for authentication again, and retrieve a new token.

Resources
For a comprehensive set or resources, check out:
https://developer.salesforce.com/en/mobile/resources

About the Author:
Seetha Annamraju is a mobile engineer working at Amazon Lab126. She holds a master’s degree in Information Technology – Mobility from Carnegie Mellon University. Her interests include developing software for mobile and wearable technologies with a focus on usability.

References

[1] http://www.salesforce.com/us/developer/docs/chatterapi/
[2] https://developer.salesforce.com/blogs/developer-relations/2013/09/using-the-salesforce-analytics-api-on-a-visualforce-page.html
[3] http://www.salesforce.com/us/developer/docs/api_analytics/index.htm
[4] https://developer.salesforce.com/en/mobile/getting-started/android#native
[5] https://developer.salesforce.com/docs/atlas.en-us.186.0.mobile_sdk.meta/mobile_sdk/install_github.htm
[6] http://www.salesforce.com/us/developer/docs/chatterapi/Content/quickstart_connecting.htm
[7] https://developer.salesforce.com/docs/atlas.en-us.186.0.mobile_sdk.meta/mobile_sdk/intro.htm
[8] https://developer.salesforce.com/blogs/tech-pubs/2014/06/tooling-analytics-apis-use.html
[9] http://androidforbegineers.blogspot.in/2013/07/pie-charts-in-android-using-achartengine_25.html
[10] https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/analytics_api_dashboard_list_resource.htm#topic-title