The source code is available in GitHub, and after a walk-through of the code in this tutorial, you will have the opportunity to make some changes and see their effect.
The sample code used in this tutorial is available in the following GitHub repository: https://github.com/SAP/cloud-hana-mobile-app-tutorial. Open the repository link in a browser, click the Clone or download button.
If you have the GitHub Desktop Client installed, you can click the Open in Desktop link and follow the prompts to clone the repository to your development machine.
Since you have already completed the Configure Android Studio for mobile development tutorial you already have the basic app framework in place. For these instructions, we will assume the path to your project is:
~/devel/Northwind_Android. Please substitute your real path accordingly.
What you will do next is replace the generated source files with those you just cloned from GitHub.
Navigate to the project folder created in the Create a basic native Android master-detail app tutorial, and move and rename the
~/devel/Northwind_Android/NWAndroid/app/src/main directory back up the directory tree to
~/devel/Northwind_Android/NWAndroid/main_old. This will preserve the generated files in case you want to refer to them later or do a
diff to examine the changes made in the cloned source.
Copy the main directory from the cloned directory (e.g.
~/devel/Git_Source/NWAndroid/main to your project folder (
~/devel/Northwind_Android/NWAndroid/app/src/main) so it takes the place of the directory you just moved.
Open Android Studio and confirm the source file structure looks the same as below. With the
app configuration selected, click the Make Project button to build the app.
After the build completes, click the Run button to launch the app.
When prompted to choose a device, select a virtual device based on the ARM architecture and click OK.
The emulator will start, you need to unlock the device, and then the app will appear.
When the app launches, the Logon screen will appear. You need to edit the three fields shown on the screen:
s123456 with your HCP trial account ID
s123456 to your account ID
||enter your HCP trial account password
After the In Progress alert is shown, the Set App Passcode screen is displayed. Uncheck Use application passcode, then click Done.
When you click Done, the app will register with the Development & Operations server, send the data requests and display the results.
Selecting one of the products in the list will display the product details.
Now that the app is running, you will take a closer look at the MAF Logon implementation and the modifications to the Android Studio generated code to handle the data.
The default MAF Logon screen sequence looks like this:
As you can tell from running the app, a number of customizations have already been made. These can be found in the
MAFLogonActivity.onCreate() method, along with a few strings extracted to the
strings.xml file in
app/res/values. The key methods that are implemented in the
MAFLogonActivity are detailed in the SAP online docs.
The first obvious changes are that the splash screen and SAP Mobile Place on-boarding screen have been suppressed. There are a number of fields that can be shown or hidden on the Login details screen. In the tutorial, we show only the Server URL, Username and Password fields.
There is a great blog by Claudia Pacheco available on SCN that provides details on the customizations. Please take a few minutes to review it now.
For more information on the
LogonUIFacade class and the
isFieldHidden() methods as well as the
LogonCore.SharedPreferenceKeys enum used in the
onCreate() method , you can review the Javadocs downloaded with the SDK.
- See the
maflogoncore-<version #>-javadoc folder:
- See the
maflogonui-<version #>-javadoc folder:
In the tutorial code, the
onLogonFinished() method is also used to create an instance of the
NorthwindApp class and initialize the
ProductDataSingleton before setting the
Intent to display the list view. There is a discussion of those two classes below.
The Master-Detail flow template in Android Studio generates the directory structure shown below.
com.northwind.nwandroid package, there are two
Activity classes (
ProductDetailActivity). These are the
Activities that manage the display of the master (list) view and detail views. The two
Fragment classes (
ProductDetailFragment) are modular UI components attached to an
Activity, and make use of an XML layout.
Fragments make it possible for the UI to be displayed differently depending on the device screen size. In the tutorial code – the three layouts used are in the
res/layout directory are (
com.northwind.nwandroid.dummy package is not used in the tutorial – it is created by Android Studio to generate the placeholder data.
To compare the files in your tutorial project with those generated by Android Studio initially (and moved to your
~/devel/Northwind_Android/NWAndroid/main_old directory) select the
ProductListActivity class, right-click on it
and select Compare With…
Navigate to the
main_old directory and select the
Note: if the
main_old directory does not show up in your tree – follow the directions in the dialog box to drag and drop it into the window from your file system.
As you will see in the side-by-side comparison, the
ProductListActivity class is unchanged (other than some comment changes).
Repeat the comparison process above for
ProductDetailActivity.java. Here, the class is also mostly unchanged – except for the class declaration where the
extends statement has been changed to
ActionBarActivity class has been deprecated.
public class ProductDetailActivity extends AppCompatActivity
ProductListFragment files, a few changes are noted:
private Callbacks mCallbacks = callbacks;
- The onCreate() method was changed to use a String array in a Singleton class (populated from the OData service) to display the list items.
public void onCreate(Bundle savedInstanceState)
NorthwindApp app = (NorthwindApp) getActivity().getApplication();
//Use the ProductDataSingleton.listItems array to display list data
ProductDetailFragment class a few more changes were made:
private TextView productID, productName, supplierID, categoryID, qtyPerUnit;
private TextView unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued;
- In the
onCreate() method, the product selected by the user is retrieved from a HashMap in the same manner as the generated code. The only difference is that the tutorial code stores all the data in the
ProductDataSingleton class – not the
mItem = ProductDataSingleton.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
- In the
onCreateView() method, the
TextView variables are assigned to the appropriate fields in the
fragment_product_detail file following the same pattern used in the generated code.
if (mItem != null)
productID = (TextView) rootView.findViewById(R.id.product_id);
productName = (TextView) rootView.findViewById(R.id.product_name);
supplierID = (TextView) rootView.findViewById(R.id.supplier_id);
categoryID = (TextView) rootView.findViewById(R.id.category_id);
qtyPerUnit = (TextView) rootView.findViewById(R.id.qty_per_unit);
unitPrice = (TextView) rootView.findViewById(R.id.unit_price);
unitsInStock = (TextView) rootView.findViewById(R.id.units_in_stock);
unitsOnOrder = (TextView) rootView.findViewById(R.id.units_on_order);
reorderLevel = (TextView) rootView.findViewById(R.id.reorder);
discontinued = (TextView) rootView.findViewById(R.id.discontinued);
// Update the TextView items with data from the selected Product Object
Lastly, a new method –
initializeViews() was added to set the value of the
TextView elements to the
Product selected by the
* Set the value of the TextView elements to the selected Product Object
private void initializeViews()
if (mItem != null)
fragment_product_detail.xml file, a few
android:layout directives are used to control where the labels and values are shown on the page.
- The first two
TextView blocks below make up the first “row” of data shown on the details page. The
android:layout_toRightOf line ensures the “value” is to the right of the label.
- To control the position of each successive row,
android:layout_below is used in the label
TextView block and is set to the label
TextView field above it. The corresponding value
TextView block again uses
See the XML snippet below showing the layout controlling the position of the product name (first row) and unit price (second row).
view classes covered, we will inspect the remaining packages.
There is one class in the
NorthwindApp class is a sub-class of the Android
Application class, and its only purpose is to store the
ProductDataSingleton as a “global” variable.
com.northwind.services package there are five classes used to handle the OData communication with Development & Operations and are application independent (they have no knowledge of the data being passed through). The only class in the app that uses them is the
com.northwind.model package has three classes that are tied to the data service being used.
Collections class is a simple helper class to map the OData field names to string constants that are used by the
If you would like to review the fields in the OData server used in the app, open the following URL in a browser window and search for
EntityType Name="Product": http://services.odata.org/V2/Northwind/Northwind.svc/$metadata
Product class encapsulates all the details for a given product entity received from the OData service. It declares the private members, constructor and getter/setter methods.
Singleton is a standard design pattern to ensure that there is only once instance of a given class. In this tutorial, the
ProductDataSingleton class is used to hold all the data received from the OData source in one place and make it available for the master (list) and detail views. References to the
ProductDataSingleton object can be retrieved through the
The Android Studio generated code used two variables to hold data for the two views:
ITEMS – a
ListArray used for the list view
ITEM_MAP – a
HashMap used to retrieve detail view information quickly for a selected item.
getProducts() method the OData resource path is built from three parts:
- The Collection ID or
EntitySet name (
- The query option: this is not required, but used in our case to set the order for the returned data set. In the code, we leave the string to make it easier to read
- The last part of the sort key for the
$orderby directive (
Collections.PRODUCT_ID. Note that you can change the sort key to any other field in the collection. For instance, using
Collections.PRODUCT_NAME will return the records so they are displayed in alphabetical order.
The next three lines, request the data, get the response payload and build a
List Array of the entities.
ODataResponseSingle resp = store.executeReadEntitySet(resourcePath, null);
ODataEntitySet feed = (ODataEntitySet) resp.getPayload();
List entities = feed.getEntities();
getProducts() method, a
for loop steps through each entity received from an OData request, and stores the value in the appropriate field in a
Product object is added to the
The last method called in the
try block is
storeData(), which adds each
Product object in the
ArrayList to the
ITEM_MAP HashMap. Note that storing the data in the
ArrayList and the
HashMap is not ideal for large datasets – the approach used in the Android Studio template was preserved for consistency.
Also in the
getProducts() method, a
do-while loop wraps the
for loop to iterate through all entities exposed by the Northwind OData service. This is required because the Northwind service enforces server-side paging which limits each request to 20 entities. If you want to see the value in the OData response, enter the following URL in your browser, go to the bottom of the response and look for
The SDK will handle any paging size for you (
$skiptoken value), so you don’t need to know what it is when developing the app. After sending the first request, the
ODataEntitySet.getNextResourcePath() method will generate any subsequent resource paths based on the received
skiptoken value. The string returned by
getNextResourcePath() will be null if all entities have been received.
The full logic of
getProducts() in pseudo code looks like this:
Get the open online store
Build initial resource path and query options string
Start the do loop
Send and receive the OData request
Process all received entities in the for loop
Get the next resource path
Test if the resource path is null
Save the data in the String array for the master view
Store the data in the HashMap for the detail view
To simplify the log on process, change the default server URL and username to match your account. Open the
s123456 in the
USERNAME line to match your HCP trial account number:
Since your account info will be populated correctly, there is no need to show those two fields when the app starts (only the password field will be required). To hide the
USERNAME fields in the log on screen, open the
MAFLogonActivity class and navigate to the
- To hide the username field, add the following line below the line you just modified.
When working with a new OData service, it is sometimes useful to see the field data received in the log. To log all Product data, insert a call to
logItems() in the
ProductDataSingleton.storeData() method as shown below.
private static void storeData()
listItems = new String[ITEMS.size()];
int i = 0;
for (Product element : ITEMS)
//Store Product objects in a HashMap
//Store product name in a String array
listItems[i] = element.getProductName();
Log.i(TAG, String.format("Stored %d items in HashMap", i) );