CS4521:   Mobile and Topics in Web Programming

STEP 5: Creating code in your Android or IOS app that uses the AWS mobile SDK(here will show Android).

STEP 5.1: Decide how to trigger the AWS Lambda code   

NOTE: mobile app triggers the AWS Lamba code:

      • OPTION A via AWS Gateway API?

      • acts as a “front door” for applications to access data, business logic, or functionality from your back-end services, such as workloads running on Amazon Elastic Compute Cloud (Amazon EC2), code running on AWS Lambda

      • Amazon API Gateway handles all the tasks involved in accepting and processing up to hundreds of thousands of concurrent API calls, including traffic management, authorization and access control, monitoring, and API version management.


      OPTION B via the AWS Lambda SDK

       



      1. The mobile application sends a request to Amazon Cognito with an identity pool ID in the request (you create the identity pool as part of the setup)
      2. Amazon Cognito returns temporary security credentials to the application.

      Amazon Cognito assumes the role associated with the identity pool to generate temporary credentials. 

      1. The mobile application invokes the Lambda function using the temporary credentials (Cognito Identity). 
      2. AWS Lambda assumes the execution role to execute your Lambda function on your behalf.
      3. The Lambda function executes.
      4. AWS Lambda returns results to the mobile application, assuming the app invoked the Lambda function using the RequestResponse invocation type (referred to as synchronous invocation).

       

Example of Option B: Using AWS Mobile SDK to connect directly from Android App
--> There are 2 options using downloaded code created in previous step 4

OPTION 1: DOWNLOAD the AWS Mobile SDK AND take some of the sample code from step 4 and put it into YOUR app code --- NOW you can take the packaged code in amazonaws.mobile and everything you need in the demo directory code and use it in your app

THIS ASSUMES that you already have DOWNLOADED the Mobile Hub Autogenerated Android App
Code which we will use some of it (see here for how to do this)

Step 1: Go to AWS Mobile and download the AWS Android Mobile SDK

 

Step 2: Copy the core jar file and any other jar files you need for the AWS services you will need. Follow the directions at Getting Started with AWS and Android

--- you copy the jar file into the app/libs directory ******NOTE NEWER VERSION of android studio hides the libs directory so you have to copy it into the lib directory via a directory window displaying the contents and not directly in Android Studio ***** DO a Project Clean (in Build Menu) in Android Studio to sync in the new jar files you copied

 

 

I have copied for core, DynamoDB Database, Lambda and S3


 

Step 3: Add to dependencies for the Libraries ---go to right click on app and say OPen Module Settings...then click on


Here I am loading the dependencies of :

  • aws-android-sdk-ddb

  • aws-android-sdk-core

  • aws-android-sdk-ddb-mapper

  • aws-android-sdk-lambda

  • aws-android-gateway

  • aws-android-sdk-s3

 

 

 

 

Step 4: Copy the com directory from the sample Mobile Hub code you downloaded in step 4 containing both the amasonaws.mobile code and mysampleapp code above to your own project's java directory inside a file explorer as shown below



Step 5: Delete the code in com/mysampleapp as you don't want it in your applicaiton as separate java files (you have your own applicaiton code, in my case in teh comptervision directory package) -- this is optional but, best to clean up if you are not going to use it.

Step 6: YOU must import the MODULE MobileAWSHelper from the sample Mobile Hub code you downloaded in step 4c
File->New->Import Module and select the module from the OTHER dowloaded Mobile Hub code (it will copy it over for you and setup your project

SPECIAL TIP: if you get the following ERROR


*****FIX *** YOU MUST MANUALLY EDIT your settings.gradle file to contain
the line = include ':AWSMobileHelper'


NOW --yu can see the new module has been imported

ALSO add the following line to the app's gradle dependencies in its build.grale file if not there

compile project(':AWSMobileHelper')

 

ALSO add the following line to the app's default configuration in its build.gradle file if not there

multiDexEnabled = true

Here is what my build.gradle file looks like after adding the above 2 items to it

apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "computervision.grewe.blindbikewithawslambdabackend"
minSdkVersion 23
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7'
testCompile 'junit:junit:4.12'
compile files('libs/aws-android-sdk-core-2.4.2.jar')
compile files('libs/aws-android-sdk-ddb-2.4.2.jar')
compile files('libs/aws-android-sdk-ddb-mapper-2.4.2.jar')
compile files('libs/aws-android-sdk-lambda-2.4.2.jar')
compile files('libs/aws-android-sdk-s3-2.4.2.jar')
compile files('libs/aws-android-sdk-apigateway-core-2.4.2.jar')
compile project(':AWSMobileHelper')
}

 

 

Step 7: Add the permission to your AndroidManifest.xml file

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

Step 8: Modify your application code so that whenever you want to call an AWS Lambda function

***** there is a lot of code here that you need to add into your activity class (or Fragment or any class) that is going to
make a request to your AWS backend *******

Main Activity Class for my blindBike application that makes a call to a /getRoutes AWS Lambda path --you will need to know what YOUR payload for your request to YOUR lambda function should look like --see downloaded sample code from step 4 for example

 

 

package computervision.grewe.blindbikewithawslambdabackend;


import android.app.AlertDialog;
import android.net.UrlQuerySanitizer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

//NEW - imports needed - some AWS some dealing with character encoding for request setup
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.mobile.AWSMobileClient;
import com.amazonaws.mobile.api.CloudLogicAPI;
import com.amazonaws.mobile.api.CloudLogicAPIFactory;
import com.amazonaws.mobile.api.CloudLogicAPIConfiguration;
import com.amazonaws.mobilehelper.util.ThreadUtils;
import com.amazonaws.mobileconnectors.apigateway.ApiRequest;
import com.amazonaws.mobileconnectors.apigateway.ApiResponse;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;


import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import android.os.AsyncTask;

import org.json.JSONArray;
import org.json.JSONObject;


public class MainRoutesActivity extends AppCompatActivity {




//NEW - setup defaults for Request to AWS
private static final String DEFAULT_QUERY_STRING = "?lang=en_US";
private static final String DEFAULT_REQUEST_BODY = "{\n  \"userId\" : \"lynne123\" \n}";

public static final int DEFAULT_API_INDEX = 0;
public static final String DEFAULT_PATH = "/getRoutes";
public static final String DEFAULT_METHOD = "POST";
private CloudLogicAPIConfiguration apiConfiguration;


//NEW- setup parameters to make AWS call -use defaults set above
final int apiIndex = this.DEFAULT_API_INDEX//0
final String path = this.DEFAULT_PATH;   // it is /getRoutes
final String method = this.DEFAULT_METHOD; // it is POST
final String queryString = this.DEFAULT_QUERY_STRING; // sets language US English see above
final String body = this.DEFAULT_REQUEST_BODY// is JSON object containing data to send in request, see above


private static String LOG_TAG;

private EditText mResultField//widget where will dump results

 

 

//Class variables representing the request, the CloudLopciAPI to communicate from
// client to send the request.
AWSMobileClient awsMobileClient;
CloudLogicAPI
client;
ApiRequest
request;


@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.
activity_main_routes);


//grab EditText widget where will dump the info
mResultField = (EditText) findViewById(R.id.editTextRoutesDump);


// now make the request to AWS /getRoutes
invokeRequest();

 

//register Refresh Button event handler to also -refersh and recall invokeFunction
((Button) this.findViewById(R.id.buttonRefresh)).setOnClickListener(this);

 

}

/**
* event handler for Refresh button --simply call invokeRequest which makes /getRoutes call
* to backend AWS lambda and returns results that
*

*/
@Override
public void onClick(View v) {
invokeRequest();
}





/**
* invokeRequest to AWS performed on separate Thread rather than using AsyncTask
*
Makes /getRoutes request to AWS Lambda backend that retrieves all routs associated with
* userId specified in the body class variable. Gets results and call setBodyText method
* to display response.
*/

private void invokeRequest(){

//NEW - setup log tag to equal class name
this.LOG_TAG = this.getClass().getName();

//GET APIs: get APIs from CloudLogicAPIFactory
apiConfiguration = CloudLogicAPIFactory.getAPIs()[apiIndex];
final String endpoint = apiConfiguration.getEndpoint();

Log.d(
LOG_TAG, "Endpoint : " + endpoint);
Log.d(
LOG_TAG, "Path : " + path);
Log.d(
LOG_TAG, "Method : " + method);

//AWS SETUP: setup AWS request query string parameters as Map object  -- only sending language type //as US English
final Map<String, String> parameters = convertQueryStringToParameters(queryString);

//AWSMobileClient & CloudLogicAPI SETUP: create instance of CloudLogicAPI to send request through to //AWS Lambda code
AWSMobileClient.initializeMobileClientIfNecessary(this);
awsMobileClient = AWSMobileClient.defaultMobileClient();
client = awsMobileClient.createAPIClient(apiConfiguration.getClientClass());

//DEFINE Variables Related to Request: setup headers for request --initally empty
final Map<String, String> headers = new HashMap<String, String>();

//SETUP content based on BODY -- THIS contains data sending in request if POST method type
final byte[] content = body.getBytes(StringUtils.UTF8);

//SETUP ApiReqest = HTTP request given path, method, headers, parameters and copy in body if not //empty as content
ApiRequest tmpRequest =  new ApiRequest(client.getClass().getSimpleName());
tmpRequest.withPath(
path);
tmpRequest.withHttpMethod(HttpMethodName.valueOf(
method));
tmpRequest.withHeaders(headers);
tmpRequest.addHeader(
"Content-Type", "application/json");
tmpRequest.withParameters(parameters);

// final ApiRequest request;
// Only set body if it has content.
if (body.length() > 0) {

request = tmpRequest
.addHeader(
"Content-Length", String.valueOf(content.length))
.withBody(content);

} else { request = tmpRequest; }

 // Make network call on background thread
new Thread(new Runnable() {

Exception exception = null;

@Override
public void run() {

try {

Log.d(LOG_TAG, "Invoking API w/ Request : " + request.getHttpMethod() + ":" + request.getPath());
long startTime = System.currentTimeMillis();

//THIS EXECUTES THE REQUEST
final ApiResponse response = client.execute(request);  
final long latency = System.currentTimeMillis() - startTime;

final InputStream responseContentStream = response.getContent();  //GRAB THE RESPONSE
if (responseContentStream != null) {
final String responseData = IOUtils.toString(responseContentStream);
Log.d(
LOG_TAG, "Response : " + responseData);
setResponseBodyText(responseData);

}

}
catch (final Exception exception) {

Log.e(LOG_TAG, exception.getMessage(), exception);
exception.printStackTrace();
ThreadUtils.runOnUiThread(
new Runnable() {
@Override
public void run() {
//take response and DISPLAY IT
setResponseBodyText(
exception.getMessage());
}
});

}

}


}).start();

}



/** * setups parameters of query string in Map format to pass to AWS request
* @param
queryStringText
* @return */
private Map<String,String> convertQueryStringToParameters(String queryStringText) {

while (queryStringText.startsWith("?") && queryStringText.length() > 1) {

queryStringText = queryStringText.substring(1);

}

final UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
sanitizer.setAllowUnregisteredParamaters(
true);
sanitizer.parseQuery(queryStringText);

final List<UrlQuerySanitizer.ParameterValuePair> pairList = sanitizer.getParameterList();
final Map<String, String> parameters = new HashMap<>();

for (final UrlQuerySanitizer.ParameterValuePair pair : pairList) {

Log.d(LOG_TAG, pair.mParameter + " = " + pair.mValue);
parameters.put(pair.
mParameter, pair.mValue);

}

return parameters;


}

/**
* receives the JSON string and dumps it into an EditText widget associated with
*   class variable mResultField
* @param
text */
private void setResponseBodyText(final String text) {

ThreadUtils.runOnUiThread(new Runnable() {

@Override
public void run() { mResultField.setText(text);}

});

}
 

 

 

/**
* display error message
* @param
errorMessage
*/
public void showError(final String errorMessage) {

new AlertDialog.Builder(this)
.setTitle(
this.getResources().getString(R.string.app_name) + " AWS error")
.setMessage(errorMessage)
.setNegativeButton(
this.getResources().getString(R.string.error_dismiss), null)
.create().show();

}

}

ALTERNATIVE invokeRequest method that uses AsyncTask changed code (no Thread) is in Orange


/**
* Makes /getRoutes request to AWS Lambda backend that retrieves all routs associated with
* userId specified in the body class variable. Gets results and call setBodyText method
* to display response.
*/
private void invokeRequest(){
//NEW - setup log tag to equal class name
this.LOG_TAG = this.getClass().getName();

//NEW- get APIs from CloudLogicAPIFactory
apiConfiguration = CloudLogicAPIFactory.getAPIs()[apiIndex];
final String endpoint = apiConfiguration.getEndpoint();

Log.d(LOG_TAG, "Endpoint : " + endpoint);
Log.d(LOG_TAG, "Path : " + path)
//NEW- setup AWS request query s;
Log.d(LOG_TAG, "Method : " + method);
tring parameters as Map object -- only sending language type as US English
final Map<String, String> parameters = convertQueryStringToParameters(queryString);

//NEW - create instance of CloudLogicAPI to send request through to AWS Lambda code
AWSMobileClient.initializeMobileClientIfNecessary(this);
//final AWSMobileClient awsMobileClient = AWSMobileClient.defaultMobileClient();
// final CloudLogicAPI client = awsMobileClient.createAPIClient(apiConfiguration.getClientClass());
awsMobileClient = AWSMobileClient.defaultMobileClient();
client = awsMobileClient.createAPIClient(apiConfiguration.getClientClass());

//NEW - setup headers for request --initally empty
final Map<String, String> headers = new HashMap<String, String>();

//NEW - setup body content translate from String into bytes will send via POST
final byte[] content = body.getBytes(StringUtils.UTF8);

//NEW - form ApiReqest = and HTTP request given path, method, headers, parameters and copy in body if not empty as content
ApiRequest tmpRequest = new ApiRequest(client.getClass().getSimpleName());
tmpRequest.withPath(path);
tmpRequest.withHttpMethod(HttpMethodName.valueOf(method));
tmpRequest.withHeaders(headers);
tmpRequest.addHeader("Content-Type", "application/json");
tmpRequest.withParameters(parameters);

// final ApiRequest request;
// Only set body if it has content.
if (body.length() > 0) {
request = tmpRequest
.addHeader("Content-Length", String.valueOf(content.length))
.withBody(content);
} else {
request = tmpRequest;
}

//Make network call using AsyncTask
new AsyncTask<Void, Void, ApiResponse>() {


ApiResponse response;
long
startTime;

@Override
protected ApiResponse doInBackground(Void... params) {
try {
Log.d(LOG_TAG, "Invoking API w/ Request : " + request.getHttpMethod() + ":" + request.getPath());

startTime = System.currentTimeMillis();

response = client.execute(request); //THIS EXECUTES THE REQUEST

return response;


} catch (final Exception e) {
Log.e("AWSLAMBDA:", "AWS Lambda invocation failed : " + e.getMessage(), e);
return
response;
}
}

@Override
protected void onPostExecute(final ApiResponse response) {

try {

final long latency = System.currentTimeMillis() - startTime;

final InputStream responseContentStream = response.getContent(); //GRAB THE RESPONSE

if (responseContentStream != null) {
final String responseData = IOUtils.toString(responseContentStream);
Log.d(LOG_TAG, "Response : " + responseData);
computervision.grewe.blindbikewithawslambdabackend.GetUserlRoutesActivity.this.setResponseBodyText(responseData);
}


} catch (final Exception e) {
Log.e("AWSLAMBDA", "Unable to decode results. " + e.getMessage(), e);
showError(e.getMessage());
setResponseBodyText(e.getMessage());
}
}
}.execute();






}

 

 

AndroidManifest.xml file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="computervision.grewe.blindbikewithawslambdabackend">


<uses-permission
android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainRoutesActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>



</manifest>

 

Layout File

 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="computervision.grewe.blindbikewithawslambdabackend.MainRoutesActivity">

<TextView
android:id="@+id/label"
android:layout_width="122dp"
android:layout_height="64dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="0dp"
android:layout_marginTop="8dp"
android:text="Routes"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
app:layout_constraintLeft_toRightOf="@+id/editTextRoutesDump"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.057"
app:layout_constraintRight_toLeftOf="@+id/buttonRefresh" />

<EditText
android:id="@+id/editTextRoutesDump"
android:layout_width="134dp"
android:layout_height="628dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:text="Routes To Appear Here"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteY="-126dp" />

<ListView
android:layout_width="189dp"
android:layout_height="406dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.49"
app:layout_constraintLeft_toRightOf="@+id/textView2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editTextRoutesDump"
tools:layout_editor_absoluteY="173dp"
tools:layout_editor_absoluteX="136dp"
android:id="@+id/listView" />

<Button
android:id="@+id/buttonRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Refresh"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toRightOf="@+id/editTextRoutesDump"
android:layout_marginLeft="0dp"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/listView"
app:layout_constraintHorizontal_bias="0.951"
app:layout_constraintVertical_bias="0.017" />

</android.support.constraint.ConstraintLayout>
 

 

 

 

 

 

 

Step 7: Here I am running the App and you can see the results of a /getRoutes request the data is dumped (not nice---but, just showing you the request/response)
into a narrow EditText field

 

 

OPTION 2: (I don't like this for you in general) Modify the sample app you downloaded in step 4 into what you want it to be ---that is a lot more work --you have interfaces to alter (A LOT) and you have to get rid of code you don't need (all the demo code you aren't using).

© Lynne Grewe