This app was generated by the SDK Wizard. The following provides an overview of the generated code and a few suggestions on where to go to customize the app.
- SDK Wizard Generated App Overview
The SDK version used by the application is set in the project.properties file.
sdkVersion=2.2.0
Additional SDK libraries such as the Offline OData library can be added by editing app's build.gradle (Module:app) file in the dependencies section as shown below.
// SAP Cloud Android SDK dependencies
implementation group: 'com.sap.cloud.android', name: 'fiori', version: sdkVersion
implementation group: 'com.sap.cloud.android', name: 'onboarding', version: sdkVersion
implementation group:'com.sap.cloud.android', name:'odata', version: sdkVersion
implementation group: 'com.sap.cloud.android', name: 'foundation', version: sdkVersion
implementation group: 'com.sap.cloud.android', name: 'offline-odata', version: sdkVersion
The app is designed to use a single OkHttpClient which is configured when the app is started. If you wish to make a network request (such as to upload usage data) to the SAP Cloud Platform Mobile Services, you can access an instance of the OkHttpClient using the below code.
AppUsageUploader.upload(ClientProvider.get());
The Wizard generated application leverages both service (EntityContainer) and proxy (Entity) classes generated by OData gradle plugin to interact with the specified mobile application hosted in Cloud Platform Mobile Services. Code generated by OData gradle plugin can be found under the app/build/generated/source/odata directory
. The settings for the proxy generation are in the app's build.gradle file under an odata section.
Should the OData service change its metadata, corresponding classes will be regenerated on execution of relevant gradle tasks. It will impact the existing application if changes are compatible. See documentation for what constitutes compatible changes. SAPServiceManager
is responsible for instantiating the generated service class.
If you wish to make an OData query, you can access an instance of the service class using the below code. This example is from the service class generated against the sample back end.
//get the DataService class which we will use to query the Back-End OData service
SAPServiceManager sapServiceManager = ((SAPWizardApplication)getApplication()).getSAPServiceManager();
ESPMContainer espmContainer = sapServiceManager.getESPMContainer();
DataQuery query = new DataQuery()
.filter(ProductCategory.numberOfProducts.greaterThan(0))
.orderBy(ProductCategory.mainCategoryName)
.orderBy(ProductCategory.categoryName);
espmContainer.executeQuery(query);
A few of the Welcome screen customizations include the title, app icon, description and privacy terms of service URL. For additional details consult Customizing the Welcome Screen.
The following steps will add a custom menu to the EntitySetListActivity
.
In EntitySetListActivity.java
, in the onCreateOptionsMenu
, add the below line.
menu.add( Menu.NONE, 300, 1, "Custom Menu");
In the method, onOptionsItemSelected, add the following to handle the click of the menu.
case 300:
LOGGER.debug("Custom menu clicked.");
return true;
This version utilize Android's new Architecture Components: ViewModel, Repository, DataBinding and LiveData. Each entity set will have its own set of activities, fragments, view model, XML layout and repository. When using the generated application as a starting point for development, unnecessary entity types can be purged by simply removing the corresponding files:
- res/layout (files): <entity_set>_detail.xml, <entity_set>_create_update.xml
- src/.../<package_name>/mdui (directory): <entity_set>
- src/.../<package_name>/view_model (directory): <entity_set>
In addition, update the following files to remove reference to the unwanted entity set
- src/.../<package_name>/viewmodel/EntityViewModelFactory.java
- src/.../<package_name>/mdui/EntitySetListActivity.java
- res/AndroidManifest.xml
XML layout files for each entity set uses one and two-way databinding to an instance of the proxy class for viewing and editing. For Android UI components like EditText, TextView, etc., the necessary adapters are available from the corresponding Android library for Java data types. However, the use of Fiori UI components and additional OData data types require specific binding adapters. Please refer to the databinding directory under src for examples of one-way and two-way databindings of a proxy class to KeyValueCell (one-way) and SimplePropertyFormCell (two-way) Fiori UI components.
Note that the list of InverseMethods used for two-way databinding can be trimmed based on the actual set of data types that are required for the project. For example, GeometryPoint and related types can be removed if metadata document does not contain these data types.
During offline application generation, a defining request, which queries the entire collection, will be generated for each entity set within the entity container. Thereafter, user can perform CRUD operations against local data offline. Local changes can be applied to the OData service when online through synchronization (upload followed by download). Local offline store is encrypted using a random string derived from the EncryptionUtil.getEncryptionKey method. While this is a security best practice, in the event the offline store needs to be submitted for support purposes, the encryption key must be extracted via a debug session or change the application to skip encryption.
Defining requests can be changed in SAPServiceManager's initializeOffline method. By default, all data within each entity is downloaded. This is not normally done for a typical offline application as only a subset of the data is brought onto the device. For best practices of offline data definition, please refer to offline section of SDK documentation.
The generated application assumes that primary key(s) is assigned by the server. For online use case, the server key will be returned in the response of the operation. However, in offline use case, the key(s) is absent until an explicit synchronization is triggered through the main menu on the home screen. Since the key(s) is not even set, accessing them in two-way databinding during create/edit will cause an exception to be thrown. Hence, in create and update fragments, the key(s) is not visible. It is not advisable to initialize the key(s) due to the potential of duplicate key. While it is possible to generate random key values, it is not good for user experience as key value will change after synchronization. Hence, the compromise is to exclude them in create/edit screens.
Furthermore, one should always use read/editLink to access the new local instances.
For each entity set, a random non-key property is selected to identify an instance of corresponding entity type. This property is also used to sort the returned collection. For an app generated against the SAP Sample Service Back End OData service, the City property for the Customers entity set is selected. To change to a different property, the followng three files for the Customers entity set are to be updated: (search for Customer.city and replaced with another property in the Customer entity set)
- CustomerViewModel.java
- CustomersListActivity.java
- CustomersDetailFragment.java
Note: it is best to select a property that normally does not have null values. In some cases, perhaps the key property is appropriate.
The detail screen shows each property of the entity. The set of properties to be displayed can be changed by modifying the XML layout file: _detail.xml. For example, the Customers entity set will have the following layout files:
- customers_detail.xml (detail)
- customers_create_update.xml (create and update)
The first screen shown following the onboarding screens is the EntitySetListActivity
screen. To instead show a different screen, modify LogonActivity.java
and in the method startEntitySetListActivity
, make the change below.
//Intent intent = new Intent(LogonActivity.this, EntitySetListActivity.class);
Intent intent = new Intent(LogonActivity.this, MyFirstActivity.class);
If the option Enable Push was checked on the Project Configuration page of the wizard, classes to enable the app to receive push notifications appear in the package fcm
. One customization might be to change the title of the notification message if it is not included in the received message. To do this, edit PushNotificationBroadcastReceiver.java
and in the onReceive
method modify the value of notificationTitle.
The Configuration Loader is used to acquire general, non-secure configuration information, such as the Service URL from one of several sources. These sources include the Android Managed Configuration (previously known as application restrictions), an embedded resource file, or the Discovery Service. The sources are tried in the above order until a valid configuration is returned or no input is supplied for a provider, such as the Discovery Service that requires input. By default, the configuration data is persisted to shared preferences. Subsequently, the data is retrieved from shared preferences and kept in the ConfigurationData
object. The ConfigurationData
object is contained in the SAPWizardApplication
application object and accessed through the getConfigurationData
method.
When a wizard generated application is initially started or reset, it needs to be onboarded. The onboarding process encompasses getting the configuration data and the initial authentication with the server. The onboarding process is handled by the launch screen activity, which invokes the startStandardOnboarding
method of the LaunchScreenActionHandlerImpl
class. The startStandardOnboarding
method checks to see if there already is configuration data in shared preferences from a prior invocation and if so it moves on. Otherwise the ConfigurationLoader
is run asynchronously through the startConfigurationLoader
private method. The startStandardOnboarding
method then loops until a configuration is successfully loaded or the loader fails. During this loop, the loader may request user input. If so, the private method promptForProviderInput
is called to obtain it. Upon success, startStandardOnboarding
performs the initial sever authentication, successfully exits the activity and onboarding is complete. Upon any failure, startStandardOnboarding
returns and is re-invoked to try again.
The startConfigurationLoader
private method simply instantiates a ConfigurationLoader
object and calls loadConfiguration
on it in a UI thread. The loadConfiguration
method runs the loader in its own non-UI thread. The two threads communicate through messages passed through the ConfigurationLoaderCallback
object methods and the ConfigurationLoader.processRequestedInputs
method.
The ConfigurationLoader
has several constructors. The one currently used employs the default queue and supplies the hard-coded Application ID to the discovery service provider. If desired, a developer can modify startConfigurationLoader
and define their own configuration provider queue and pass it to the ConfigurationLoader
constructor which takes a Context, the ConfigurationLoaderCallback
, and a provider queue. The provider queue is simply an array of ConfigurationProvider
objects in the order in which one wants them tried.
While you are syncing the project, a warning message
can appear in Android Studio.
"Could not find google-services.json while looking in [src/nullnull/debug, src/debug/nullnull, src/nullnull, src/debug, src/nullnullDebug]
registerResGeneratingTask is depricated, use registerGeneratedResFolders(FileCollection)"
You can ignore this warning message, it won't cause any problem.