If you have not completed the tutorials in Mobile Groups 1-3 – it would be best to work through those first to become familiar with the OData service used.
The final version of the app will look like this:
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.
Step 1: Get sample code
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.
Step 2: Replace generated files
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.
Step 3: Copy main directory
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.
Step 4: Build and run
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.
Step 5: Choose device
- 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.
Step 6: Edit details
When the app launches, the Logon screen will appear. You need to edit the three fields shown on the screen:
s123456 with your SAP Cloud Platform trial account ID
s123456 to your account ID
||enter your SAP Cloud Platform trial account password
Step 7: Finish deployment
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.
Step 8: Examine code modifications
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.
Step 9: Log-in screen changes
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.
Step 10: View package details
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.
Step 11: Compare project to generated code
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).
Step 12: Compare product detail activity
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
Step 13: Compare product list fragment
ProductListFragment files, a few changes are noted:
- The name of the callbacks were changed:
private Callbacks mCallbacks = callbacks;
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
Step 14: Further product detail class changes
ProductDetailFragment class a few more changes were made:
- For the detail view, some
TextView variables were added:
private TextView productID, productName, supplierID, categoryID, qtyPerUnit;
private TextView unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued;
Step 15: Product selection
- 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));
Step 16: Text view variables assignment
- 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
Step 17: Initialize views method added
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)
Step 18: Android layout directives
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.
Step 19: The Nortwind App class
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.
Step 20: OData communication classes
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
Step 21: Northwind model classes
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.
ProductDataSingleton keeps the same two variable names and simply replaces the
Dummy class with the
Product class. Most of the methods in the
ProductDataSingleton class are self-explanatory (and have comments inserted). Most of the heavy lifting occurs in the
Step 22: The get products method
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.
Step 23: Build list array
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();
Step 24: Populating the Product object
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.
Step 25: Paging
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.
Step 26: Get products pseudocode
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
Step 27: Customizing the code
To simplify the log on process, change the default server URL and username to match your account. Open the
s123456 in the
SERVER_URL line to match your SAP Cloud Platform trial account number:
s123456 in the
USERNAME line to match your SAP Cloud Platform 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
- Set the second parameter in the
mLogonUIFacade.isFieldHidden call below to
- To hide the username field, add the following line below the line you just modified.
Step 28: Logging Product data
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) );
Next time you run the app, watch the
logcat tab for the logged output.
Step 29: Changing the sort key
As discussed earlier, it is possible to change the sort order of the entities returned by the OData service. The current order (Product ID) is the same used in the early Mobile Group tutorials. Since it would be easier to find a product by name rather than ID, change the sort key to
ProductDataSingleton.getProducts() method, change the
resourcePath line to match the following:
resourcePath = Collections.PRODUCT_COLLECTION + "?$orderby=" + Collections.PRODUCT_NAME;
A few key online help documents: