Display Customer Locations Using a Fiori Map Control
- How to add a Google Map to the wizard-generated app and display customer locations
- How to add a Fiori Map control and try out its features
Prerequisites
You have:
1. Set Up a BTP Account for Tutorials. Follow the instructions to get an account, and then to set up entitlements and service instances for the following BTP services.
- SAP Mobile Services
2. Downloaded and Installed version 7.0.2 or higher of the SAP BTP SDK for Android.
3. Completed Try Out SAP BTP SDK Wizard for Android.
A Fiori Map control extends the Google Maps SDK for Android or Esri ArcGIS Runtime SDK for Android. It provides additional APIs that handle clustering, as well as a toolbar, panel, and an editor to annotate map. For additional details, see Fiori Design Guidelines.
- Step 1
In this section you will create a new activity to display a map.
-
In Android Studio, in the project explorer, navigate to
app > java > com.sap.wizapp > mdui > customers. -
Right-click and choose
New > Activity > Gallery... > Google Maps Views Activity.
-
Set Activity Name to be
CustomersMapActivity.
-
Click Finish.
-
In the
AndroidManifest.xmlfile, copy the URL on line 18. -
Paste the URL into a browser to register the application with the Maps SDK for Android. Follow the instructions to get an API Key.
-
Open the
local.propertiesfile in your project-level directory and then add the following code. ReplaceYOUR_API_KEYwith your API key.MAPS_API_KEY=YOUR_API_KEY -
In your
AndroidManifest.xmlfile, go tocom.google.android.geo.API_KEYand update theandroid:valueattribute as follows:XMLCopy<meta-data android:name="com.google.android.geo.API_KEY" android:value="${MAPS_API_KEY}" /> -
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeEntitySetListActivityto openEntitySetListActivity.kt. -
On Windows, press
Ctrl+F, or, on a Mac presscommand+F, and search forCustomersActivity::class. -
Replace
CustomersActivity::classwithCustomersMapActivity::classso that when the user taps on Customers, the app will navigate to the newly added activity that includes a map. -
On Windows press
Ctrl+Nor on a Mac presscommand+O, and typeCustomersMapActivityto openCustomersMapActivity.kt. -
Add the following import if it isn’t added automatically:
KotlinCopyimport com.sap.wizapp.R -
Go to Firebase console.
-
Select the project, in this example, Wiz App.
-
Access Project settings for the project.

-
Scroll down to download google-services.json.

-
Save the json file to the WizApp/app folder.
-
Run the app. Select Customers.

Instead of a customer list, a map is now displayed.

If a message appears that says Wiz App is having trouble with Google Play services, try running the app on an Android emulator that includes the Google Play Store app.
-
- Step 2
In this section, you will add code to place a marker on the map for each customer.
-
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersMapActivityto openCustomersMapActivity.kt. -
Add the following class variable:
KotlinCopyprivate val locations = HashMap<String, LatLng>() -
Add the following methods:
KotlinCopyprivate fun addCustomersToMap() { val query = DataQuery() .from(ESPMContainerMetadata.EntitySets.customers) .where(Customer.country.equal("US") .or(Customer.country.equal("CA")) .or(Customer.country.equal("MX"))) val sapServiceManager = (application as SAPWizardApplication).sapServiceManager val eSPMContainer = sapServiceManager?.eSPMContainer eSPMContainer?.let { it.getCustomersAsync(query, { customers: List<Customer> -> for (customer in customers) { Log.d("", "Adding a marker for " + customer.city) addCustomerMarkerToMap(customer) } }, { re: RuntimeException -> Log.d("", "An error occurred during async query: " + re.message) }) } } private fun getCustomerLatLongFromAddress(address: String): LatLng? { locations[address]?.let { return it } //String strAddress = "Wilmington, Delaware, US"; val coder = Geocoder(this) try { // May throw an IOException val addresses = coder.getFromLocationName(address, 5) if (addresses.isNullOrEmpty()) { return null } val location = addresses[0] return LatLng(location.latitude, location.longitude) } catch (ex: IOException) { ex.printStackTrace() return null } } private fun addCustomerMarkerToMap(customer: Customer) { val latLng = getCustomerLatLongFromAddress(customer.city + ", " + customer.country) latLng?.let { val customerMarker = mMap.addMarker(MarkerOptions() //.snippet("") .position(it) .title(customer.firstName + " " + customer.lastName) ) customerMarker?.tag = customer } } -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeonMapReadyto move to theonMapReadymethod. -
Replace it with the following code:
KotlinCopyoverride fun onMapReady(googleMap: GoogleMap) { mMap = googleMap val centre = LatLng(39.8283, -98.5795) mMap.moveCamera(CameraUpdateFactory.newLatLng(centre)) // For demo purposes, speed up the lookup of address details. // Will use Geocoder to translate an address to a LatLng if address is not in this list locations.put("Wilmington, Delaware, US", LatLng(39.744655, -75.5483909)) locations.put("Antioch, Illinois, US", LatLng(42.4772418, -88.0956396)) locations.put("Santa Clara, California, US", LatLng(37.354107899999995, -121.9552356)) locations.put("Hermosillo, MX", LatLng(29.0729673, -110.9559192)) locations.put("Bismarck, North Dakota, US", LatLng(46.808326799999996, -100.7837392)) locations.put("Ottawa, CA", LatLng(45.4215296, -75.69719309999999)) locations.put("México, MX", LatLng(23.634501, -102.55278399999999)) locations.put("Boca Raton, Florida, US", LatLng(26.368306399999998, -80.1289321)) locations.put("Carrollton, Texas, US", LatLng(32.9756415, -96.8899636)) locations.put("Lombard, Illinois, US", LatLng(41.8800296, -88.00784349999999)) locations.put("Moorestown, US", LatLng(39.9688817, -74.948886)) addCustomersToMap() } -
Run the app.
-
Select Customers and notice that a map is displayed that contains a marker for every customer.

If a marker is tapped, an info marker is displayed with additional customer details.

Based on the where statement, customers from which countries are shown on the map?
-
- Step 3
In this section, you will add code to display the customer detail screen when the info marker is tapped.
-
In the class definition for
CustomersMapActivity, afterOnMapReadyCallback, add:KotlinCopy, GoogleMap.OnInfoWindowClickListener -
Add the following method to the class:
KotlinCopyoverride fun onInfoWindowClick(marker: Marker) {//import com.google.android.gms.maps.model.Marker val customer = marker.tag as Customer val intent = Intent(this, CustomersActivity::class.java) intent.putExtra(BundleKeys.ENTITY_INSTANCE, customer) startActivity(intent) } -
In the
onMapReadymethod inCustomersMapActivity, add the following line to the end of the method:KotlinCopymMap.setOnInfoWindowClickListener(this) -
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersActivityto openCustomersActivity.kt. -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeonCreateto navigate to theonCreatemethod. -
Replace the
if (savedInstanceState == null)block (keepelse-blockunchanged) in theonCreatemethod with the following code:KotlinCopyif (savedInstanceState == null) { val listFragment = CustomersListFragment() intent.extras?.let { if (it.containsKey(BundleKeys.ENTITY_INSTANCE)) { listFragment.arguments = it } } supportFragmentManager.beginTransaction() .replace(R.id.masterFrame, listFragment, UIConstants.LIST_FRAGMENT_TAG) .commit() } -
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersListFragmentto openCustomersListFragment.kt. -
Add the following variable to the top of the class:
KotlinCopyprivate var entityFromMap: Customer? = null -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeonCreateto navigate to theonCreatemethod. -
Add the following code to the end of the method:
KotlinCopyarguments?.let { if (it.containsKey(BundleKeys.ENTITY_INSTANCE)) { entityFromMap = it.get(BundleKeys.ENTITY_INSTANCE) as Customer } } -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeonViewStateRestoredto navigate to theonViewStateRestoredmethod. -
Add the following code to the end of the method:
KotlinCopyentityFromMap?.let { viewModel.setSelectedEntity(it) listener?.onFragmentStateChange(UIConstants.EVENT_ITEM_CLICKED, it) entityFromMap = null } -
Run the app. Select Customers, and tap on a marker. Then tap on the info marker.

This sequence displays the customer details page.

-
- Step 4
In this section, you will create a new activity that uses the Fiori Map control.
-
Press
Shifttwice, and typestyles.xmlto openstyles.xml. -
Add the following code right after
AppThemeand beforeAppTheme.NoActionBar:XMLCopy<style name="FioriMap.NoActionBar" parent="FioriTheme.Onboarding"> <item name="colorPrimary">?attr/sap_fiori_color_s2</item> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="windowActionModeOverlay">true</item> </style> -
Add the following dependency in the app module’s
build.gradlefile in the dependencies object and click Sync Now.GradleCopyimplementation group: 'com.sap.cloud.android', name: 'google-maps', version: sdkVersion
-
Create a new Layout Resource File in
res/layoutcalleddetail_panel.xmland replace its contents with the following code.
XMLCopy<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" tools:layout_constraintVertical_weight="100"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="4dp" android:paddingLeft="4dp" android:text="Default panel content goes here" /> </androidx.constraintlayout.widget.ConstraintLayout> -
Create a new Layout Resource File in
res/layoutcalledsearch_auto_complete.xmland replace its contents with the following code.XMLCopy<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/search_auto_complete_text" android:layout_width="match_parent" android:layout_height="50dp" /> </LinearLayout> -
In Android Studio, using the project explorer, navigate to
app > java > com.sap.wizapp > mdui > customers. -
Right-click and choose New > Activity > Empty Views Activity.
-
Set Activity Name to be
CustomersFioriMapActivity. -
Click Finish.

-
Replace the file contents in the newly created
CustomersFioriMapActivity.ktwith the following code:KotlinCopypackage com.sap.wizapp.mdui.customers import android.app.SearchManager import android.content.Context import android.content.Intent import android.location.Geocoder import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.ArrayAdapter import android.widget.ImageButton import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.LatLng import com.sap.cloud.android.odata.espmcontainer.Customer import com.sap.cloud.android.odata.espmcontainer.ESPMContainerMetadata import com.sap.cloud.mobile.fiori.maps.google.GoogleFioriMapView import com.sap.cloud.mobile.fiori.maps.google.GoogleMapActionProvider import com.sap.cloud.mobile.fiori.maps.google.GoogleMapViewModel import com.sap.cloud.mobile.odata.DataQuery import com.sap.wizapp.R import com.sap.wizapp.app.SAPWizardApplication import com.sap.wizapp.mdui.BundleKeys import androidx.appcompat.app.AppCompatActivity import com.sap.cloud.mobile.fiori.maps.* import java.io.IOException class CustomersFioriMapActivity : AppCompatActivity(), GoogleFioriMapView.OnMapCreatedListener { private lateinit var mGoogleFioriMapView: GoogleFioriMapView private var mUseClustering = false private var mMapType: Int = 0 private val locations = HashMap<String, LatLng>() // Used for demo purposes to speed up the process of converting an address to lat, long private val markers = HashMap<String, FioriMarkerOptions>() // Used to associate an address with a marker for search private val addresses = arrayListOf<String>() // Used to populate the list of addresses that are searchable private lateinit var mActionProvider: GoogleMapActionProvider override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val intent = intent setContentView(R.layout.activity_customers_fiori_map) mGoogleFioriMapView = findViewById(R.id.googleFioriMap) mGoogleFioriMapView.onCreate(savedInstanceState) savedInstanceState?.let { mUseClustering = it.getBoolean("UseClustering", false) mMapType = it.getInt("MapType", GoogleMap.MAP_TYPE_NORMAL) } mGoogleFioriMapView.setOnMapCreatedListener(this) } /** * Manipulates the map once available. * This callback is triggered when the map is ready to be used. * This is where we can add markers or lines, add listeners or move the camera. In this case, * we just add a marker near Toronto, Canada. */ override fun onMapCreated() { mActionProvider = GoogleMapActionProvider(mGoogleFioriMapView, this) // For demo purposes, speed up the lookup of address details. // Will use Geocoder to translate an address to a LatLng if address is not in this list locations.put("Wilmington, Delaware, US", LatLng(39.744655, -75.5483909)) locations.put("Antioch, Illinois, US", LatLng(42.4772418, -88.0956396)) locations.put("Santa Clara, California, US", LatLng(37.354107899999995, -121.9552356)) locations.put("Hermosillo, MX", LatLng(29.0729673, -110.9559192)) locations.put("Bismarck, North Dakota, US", LatLng(46.808326799999996, -100.7837392)) locations.put("Ottawa, CA", LatLng(45.4215296, -75.69719309999999)) locations.put("México, MX", LatLng(23.634501, -102.55278399999999)) locations.put("Boca Raton, Florida, US", LatLng(26.368306399999998, -80.1289321)) locations.put("Carrollton, Texas, US", LatLng(32.9756415, -96.8899636)) locations.put("Lombard, Illinois, US", LatLng(41.8800296, -88.00784349999999)) locations.put("Moorestown, US", LatLng(39.9688817, -74.948886)) addCustomersToMap() // Setup toolbar buttons and add to the view. val settingsButton = SettingsButton(mGoogleFioriMapView.toolbar.context) val legendButton = LegendButton(mGoogleFioriMapView.toolbar.context) legendButton.isEnabled = true val locationButton = LocationButton(mGoogleFioriMapView.toolbar.context) val extentButton = ZoomExtentButton(mGoogleFioriMapView.toolbar.context) val buttons = arrayOf<ImageButton>(settingsButton, legendButton, locationButton, extentButton) mGoogleFioriMapView.toolbar.addButtons(buttons.asList()) // Setup draggable bottom panel val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater val detailView = inflater.inflate(R.layout.detail_panel, null) mGoogleFioriMapView.setDefaultPanelContent(detailView) mActionProvider.setClustering(false) val currentPosition = (mActionProvider.mapViewModel as GoogleMapViewModel).latLng val currentZoom = (mActionProvider.mapViewModel as GoogleMapViewModel).zoom if (currentPosition != null && currentZoom != 0f) { // Position the camera after a lifecycle event. mGoogleFioriMapView.map?.moveCamera(CameraUpdateFactory.newLatLngZoom(currentPosition, currentZoom)) } else { // Move the camera to the centre of North America val centre = LatLng(39.8283, -98.5795) mGoogleFioriMapView.map?.animateCamera(CameraUpdateFactory.newLatLng(centre)) } findViewById<FioriMapSearchView>(R.id.fiori_map_search_view)?.let { val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager it.setSearchableInfo(searchManager.getSearchableInfo(componentName)) it.setAdapter(ArrayAdapter<String>(this@CustomersFioriMapActivity, R.layout.search_auto_complete, R.id.search_auto_complete_text, addresses)) it.setThreshold(2) it.setOnItemClickListener{ parent, view, position, id -> it.setQuery(parent.getItemAtPosition(position).toString(), false) searchResultSelected(parent.getItemAtPosition(position) as String) val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0) } } } private fun searchResultSelected(selectedSearchResult: String) { locations[selectedSearchResult]?.let { latLng -> // Center the marker. mGoogleFioriMapView.map?.moveCamera(CameraUpdateFactory.newLatLng(latLng)) // Select the marker (or cluster the marker is in). mActionProvider.selectMarker(markers[selectedSearchResult]) } } // Methods overriding the lifecycle events are required for FioriMapView to run properly override fun onStart() { super.onStart() mGoogleFioriMapView.onStart() } override fun onResume() { super.onResume() mGoogleFioriMapView.onResume() } override fun onPause() { super.onPause() mGoogleFioriMapView.onPause() } override fun onStop() { super.onStop() mGoogleFioriMapView.onStop() } override fun onDestroy() { super.onDestroy() mGoogleFioriMapView.onDestroy() } override fun onSaveInstanceState(bundle: Bundle) { super.onSaveInstanceState(bundle) mGoogleFioriMapView.onSaveInstanceState(bundle) bundle.putBoolean("UseClustering", mUseClustering) bundle.putInt("MapType", mMapType) } override fun onLowMemory() { super.onLowMemory() mGoogleFioriMapView.onLowMemory() } private fun getCustomerLatLongFromAddress(address: String) : LatLng? { locations[address]?.let { return it } // String strAddress = "Wilmington, Delaware"; val coder = Geocoder(this) try { // May throw an IOException val addresses = coder.getFromLocationName(address, 5) if (addresses.isNullOrEmpty()) { return null } val location = addresses[0] return LatLng(location.latitude, location.longitude) } catch (ex: IOException) { ex.printStackTrace() return null } } private fun addCustomerMarkerToMap(customer: Customer) { getCustomerLatLongFromAddress(customer.city + ", " + customer.country)?.let {latLng -> val customerMarker = FioriMarkerOptions.Builder() .tag(customer) .point(FioriPoint(latLng.latitude, latLng.longitude)) .title(customer.firstName + " " + customer.lastName) .legendTitle("Customer") .build() mActionProvider.addMarker(customerMarker) markers.put(customer.city + ", " + customer.country, customerMarker) mGoogleFioriMapView.map?.moveCamera(CameraUpdateFactory.newLatLng(latLng)) } } private fun addCustomersToMap() { val query = DataQuery() .from(ESPMContainerMetadata.EntitySets.customers) .where(Customer.country.equal("US") .or(Customer.country.equal("CA")) .or(Customer.country.equal("MX"))) val sapServiceManager = (application as SAPWizardApplication).sapServiceManager val eSPMContainer = sapServiceManager?.eSPMContainer eSPMContainer?.let { it.getCustomersAsync(query, { customers: List<Customer> -> for (customer in customers) { addCustomerMarkerToMap(customer) addresses.add(customer.city + ", " + customer.country) } mActionProvider.doExtentsAction() }, { re: RuntimeException -> Log.d("", "An error occurred during async query: " + re.message) }) } } } -
Press Shift twice and type
activity_customers_fiori_map.xmlto openactivity_customers_fiori_map.xml. -
Replace its contents with the following code.
XMLCopy<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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=".mdui.customers.CustomersFioriMapActivity"> <com.sap.cloud.mobile.fiori.maps.google.GoogleFioriMapView android:id="@+id/googleFioriMap" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout> -
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeEntitySetListActivityto openEntitySetListActivity.kt. -
On Windows, press
Ctrl+F, or, on a Mac, presscommand+F, and search forCustomersMapActivity::class. -
Replace
CustomersMapActivity::classwithCustomersFioriMapActivity::classso that when the user taps on Customers, the app will navigate to the newly added activity with the Fiori map on it. -
On Windows, press
Ctrl+Shift+N, or, on a Mac, presscommand+Shift+O, and typeAndroidManifestto openAndroidManifest.xml. -
On Windows, press
Ctrl+F, or, on a Mac, presscommand+F, and search forCustomersFioriMapActivity. -
Modify the activity so it specifies the
NoActionBartheme, which will cause the activity to not display an action bar.XMLCopy<activity android:name=".mdui.customers.CustomersFioriMapActivity" android:theme="@style/FioriMap.NoActionBar" android:exported="false" /> -
Run the app.
You should be able to see markers on the screen that represent customers.

Users can use the search bar at the top of the screen to find markers. For example, enter
IllinoisorMX.
The toolbar on the side provides icons for a settings dialog, marker legend, current location, and zoom to the extent of the markers on the map.

The floating action button in the bottom right corner opens the edit annotations panel, which provides the capability to draw points, lines, and polygons on the map.
The bottom panel is where additional details for a selected marker can be displayed.
What are some differences between the Google map activity and Fiori Google map activity?
-
- Step 5
In this section, the bottom panel will be populated with details of the selected marker and an action will be implemented to enable navigation to the selected customer’s detail page.
-
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersFioriMapActivityto openCustomersFioriMapActivity.kt. -
At the top of the class, add the following variables:
KotlinCopyprivate lateinit var mMapResultsAdapter: MapResultsAdapter -
At the bottom of the class, add the following methods and the
MapResultsAdapterclass.KotlinCopyprivate fun setupInfoProvider() { val infoAdapter = object:AnnotationInfoAdapter { override fun getInfo(tag: Any) : Any { return tag } override fun onBindView(mapPreviewPanel: MapPreviewPanel, info: Any) { val customer = info as Customer mapPreviewPanel.setTitle(customer.firstName + " " + customer.lastName) val objectHeader = mapPreviewPanel.objectHeader objectHeader.apply { headline = customer.city + ", " + customer.country val customerLatLng = getCustomerLatLongFromAddress(customer.city + ", " + customer.country) customerLatLng?.let { body = "Latitude: " + it.latitude footnote = "Longitude " + it.longitude } } val cell = ActionCell(this@CustomersFioriMapActivity) cell.apply { setText(customer.phoneNumber) setIcon(R.drawable.ic_phone_black_24dp) } val cell2 = ActionCell(this@CustomersFioriMapActivity) cell2.apply { setText(customer.emailAddress) setIcon(R.drawable.ic_email_black_24dp) } val cell3 = ActionCell(this@CustomersFioriMapActivity) cell3.apply { setText(customer.houseNumber + " " + customer.street) setIcon(R.drawable.ic_map_marker_unselected) } val cell4 = ActionCell(this@CustomersFioriMapActivity) cell4.apply { setText("Additional Details") setIcon(R.drawable.ic_list_24dp) setOnClickListener{ v -> val intent = Intent(this@CustomersFioriMapActivity, CustomersActivity::class.java) intent.putExtra(BundleKeys.ENTITY_INSTANCE, customer) startActivity(intent) } } mapPreviewPanel.setActionCells(cell, cell2, cell3, cell4) } } mActionProvider.annotationInfoAdapter = infoAdapter } private fun setListAdapter() { val mMapListPanel = mGoogleFioriMapView.mapListPanel mMapResultsAdapter = MapResultsAdapter() mMapListPanel.setAdapter(mMapResultsAdapter) } class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { lateinit var objectCell: ObjectCell init{ if (itemView is ObjectCell) { objectCell = itemView } } } inner class MapResultsAdapter: RecyclerView.Adapter<ViewHolder>(), MapListPanel.MapListAdapter { val customers = arrayListOf<Customer>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ViewHolder { val cell = ObjectCell(parent.context) cell.preserveIconStackSpacing = true cell.preserveDetailImageSpacing = false return ViewHolder(cell) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val customer = customers[position] val resultCell = holder.objectCell resultCell.apply { headline = customer.firstName + " " + customer.lastName subheadline = customer.houseNumber + " " + customer.street + ", " + customer.city setStatus(customer.country, 1) setOnClickListener{ v-> val intent = Intent(this@CustomersFioriMapActivity, CustomersActivity::class.java) intent.putExtra(BundleKeys.ENTITY_INSTANCE, customer) startActivity(intent) } } } override fun getItemCount(): Int { return customers.size } override fun clusterSelected(list: List<FioriMarkerOptions>?) { customers.clear() list?.let { l -> for (fmo in l) { fmo.tag?.let { customers.add(it as Customer) } } } } } -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and search foronMapCreated. -
At the top of the method, below the line that sets
mActionProvider, add the following code:KotlinCopysetupInfoProvider() setListAdapter() -
In the same method, comment out the following code. This will disable the default content panel so that the panel can be populated by the new methods.
KotlinCopyval detailView = inflater.inflate(R.layout.detail_panel, null) mGoogleFioriMapView.setDefaultPanelContent(detailView) -
Run the app.
Now, when you tap on a marker, the bottom panel should be populated with customer data.

-
Tap on Additional Details.

Notice that the customer details screen is now displayed.

-
- Step 6
In this section you will implement the settings dialog to include a map type setting and a clustering toggle.
-
Create a new Layout Resource File in
res/layoutcalledsettings_panel.xmland replace its contents with the following code. This creates the layout for the settings page.XMLCopy<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="fill_horizontal" android:orientation="horizontal"> <com.sap.cloud.mobile.fiori.formcell.ChoiceFormCell android:id="@+id/map_type" android:layout_width="match_parent" android:layout_height="wrap_content" app:key="Map Type" /> </LinearLayout> <com.sap.cloud.mobile.fiori.formcell.SwitchFormCell android:id="@+id/use_clustering" android:layout_width="match_parent" android:layout_height="wrap_content" android:showText="true" app:key="Clustering" app:value="true" /> </LinearLayout> </ScrollView> -
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersFioriMapActivityto openCustomersFioriMapActivity.kt. -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeonMapCreatedto navigate to theonMapCreatedmethod. -
Find the following line of code:
KotlinCopymActionProvider.setClustering(false) -
Replace the line above with the following code:
KotlinCopyval settingsView = inflater.inflate(R.layout.settings_panel, null) // Setup selection of a different map type val mapTypeChoice = settingsView.findViewById<ChoiceFormCell>(R.id.map_type) mapTypeChoice.valueOptions = arrayOf<String>("Normal", "Satellite", "Terrain", "Hybrid") mapTypeChoice.cellValueChangeListener = object : FormCell.CellValueChangeListener<Int>() { override fun cellChangeHandler(value: Int?) { value?.let { when (it) { 0 -> mMapType = GoogleMap.MAP_TYPE_NORMAL 1 -> mMapType = GoogleMap.MAP_TYPE_SATELLITE 2 -> mMapType = GoogleMap.MAP_TYPE_TERRAIN 3 -> mMapType = GoogleMap.MAP_TYPE_HYBRID } mGoogleFioriMapView.map?.mapType = mMapType } } } if (mMapType == 0) { mapTypeChoice.value = mMapType } else { mapTypeChoice.value = mMapType - 1 } mGoogleFioriMapView.setSettingsView(settingsView) if (mMapType != GoogleMap.MAP_TYPE_NONE) { mGoogleFioriMapView.map?.mapType = mMapType } // Setup clustering selection. val useClusteringSwitch = settingsView.findViewById<SwitchFormCell>(R.id.use_clustering) useClusteringSwitch.value = mUseClustering mActionProvider.setClustering(mUseClustering) useClusteringSwitch.cellValueChangeListener = object: FormCell.CellValueChangeListener<Boolean>() { override fun cellChangeHandler(value: Boolean?) { value?.let { mUseClustering = it mActionProvider.setClustering(mUseClustering) } } } -
Run the app.
-
Tap on the settings icon in the toolbar.

-
Change the map type to Hybrid and turn Clustering on.


Notice that the markers in close proximity are now grouped together and a number indicates how many markers are in the cluster.

Congratulations! You have now successfully added a Fiori Map to an application and tried out some of the features it provides.
What is clustering?
-
- Step 7
In this section, you will test the three different types of annotations.
-
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersFioriMapActivityto openCustomersFioriMapActivity.kt. -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeonCreateto navigate to theonCreatemethod. -
Add the following code after the
mGoogleFioriMapView.onCreateis called. This will save any points,polylines, or polygons drawn on the map.KotlinCopy// Handle the editor's save event. // This is where you can implement functions to save the new annotated points, polylines, and polygons mGoogleFioriMapView.editorView.setOnSaveListener{annotation -> var message: String? = null when (annotation) { is PointAnnotation -> { message = "This is a point" } is PolylineAnnotation -> { message = "This is a polyline with " + annotation.points.size + " points" } is PolygonAnnotation -> { message = "This is a polygon with " + annotation.points.size + " points" } } // import android.app AlertDialog val builder = AlertDialog.Builder(mGoogleFioriMapView.context, com.sap.cloud.mobile.fiori.R.style.FioriAlertDialogStyle) builder.setMessage(message) builder.setPositiveButton("Save") { _, _ -> // Close the editor. mGoogleFioriMapView.isEditable = false when (annotation) { is PointAnnotation -> { mActionProvider.addCircle( FioriCircleOptions.Builder().center(annotation.points[0] as FioriPoint).radius(40.0).strokeColor(resources.getColor(R.color.maps_marker_color_5, null)).fillColor(resources.getColor(R.color.maps_marker_color_6, null)).title("Editor Circle").build()) } is PolylineAnnotation -> { mActionProvider.addPolyline( FioriPolylineOptions.Builder().addAll(annotation.points as Iterable<FioriPoint>).color(resources.getColor(R.color.maps_marker_color_3, null)).strokeWidth(4f).title("Editor Polyline").build()) } is PolygonAnnotation -> { mActionProvider.addPolygon( FioriPolygonOptions.Builder().addAll(annotation.points as Iterable<FioriPoint>).strokeColor(resources.getColor(R.color.maps_marker_color_3, null)).fillColor(resources.getColor(R.color.maps_marker_color_4, null)).strokeWidth(4f).title("Editor Polygon").build()) } } } builder.setNegativeButton("Cancel") { dialogInterface, _ -> dialogInterface.cancel() } val dialog = builder.create() dialog.show() } -
Add the following code in the app module’s
build.gradlefile in theconfigurations.allblock and click Sync Now.GradleCopyresolutionStrategy { force("com.google.android.gms:play-services-location:17.1.0") }
-
Run the app.
-
Change the emulator’s location information so that Waterloo, Ontario, is the new default location.
Click on the three dots on the emulator’s toolbar to navigate to the emulator’s settings.

-
Under Location > Single points, search for
University of Waterlooand select the first instance.
-
Tap SAVE POINT.

-
Set the name you want to save as.

-
Select the saved point and tap SET LOCATION to set the default location.

-
Tap the current location button to zoom into the University of Waterloo and you should see the screen below. (If tapping doesn’t work, quit the app, try google Maps app first, then restart the app, and try the button again.)

-
To annotate the map, tap on the floating action button in the corner.

The bottom panel displays options to annotate the map.

-
To add the current location as a point:
-
Tap on the Add Point option in the panel. Note that it may take a few moments for the emulator to process the new coordinates from before.

-
Tap on Current Location under the search bar.

A list of location options at the University of Waterloo is displayed.

If an API error occurs, such as
Failed to get location address com.google.android.gms.common.api.ApiException, or nothing happens after tapping Current Location, ensure that the Places API is enabled on the Google Cloud Platform. TypePlaces APIin the search bar and you’ll be redirected to the following page.
If you enabled Places API but still nothing happens after tapping, use
URLCopyhttps://maps.googleapis.com/maps/api/place/details/json?place_id=ChIJN1t_tDeuEmsRUsoyG83frY4&fields=name,rating,formatted_phone_number&key=YOUR_API_KEYto see if your application could get certain place details successfully. Note that you’ll need to replace the key in this example with your own API key. See Google Maps Platform for additional information.
You can also add a point by tapping directly on the map. This will create a white and blue dot indicating where the new point is located. The added point will appear in the panel under the Address section. You can only add one point to the map. You can move it by long pressing on it and then dragging it to a new location or you can delete it (select the point and then click the X mark in the ADDRESS list) and then add a new point.

-
-
To add a polyline to the map, select the Polyline option and tap different places on the map to add multiple points. The added points will be connected with a line.

-
To add a polygon, select the Polygon option and tap different places on the map to add multiple points. The points are connected in the order that they appear in the list within the panel and take up the least amount of area.

You can move the existing points on the map by holding onto a point and then dragging it to the desired location.
In order to redraw the points the next time the map is opened, you must save and store them in the app using the on save click listener.
What type of annotations can you draw on the map?
-
- Step 8
In this section you will customize the map markers based on the customer’s country. The different marker colors will be recorded in the legend as well.
-
On Windows, press
Ctrl+N, or, on a Mac, presscommand+O, and typeCustomersFioriMapActivityto openCustomersFioriMapActivity.kt. -
On Windows, press
Ctrl+F12, or, on a Mac, presscommand+F12, and typeaddCustomerMarkerToMapto navigate to theaddCustomerMarkerToMapmethod. -
Replace the method with the following code:
KotlinCopyprivate fun addCustomerMarkerToMap(customer: Customer) { val country = customer.country val latLng = getCustomerLatLongFromAddress(customer.city + ", " + customer.country) latLng?.let { var color = (Color.parseColor("#E9573E")) // US if (country == "MX") { color = (Color.parseColor("#FFA02B")) } else if (country == "CA") { color = Color.parseColor("#0070F2") } val customerMarker = FioriMarkerOptions.Builder() .tag(customer) .point(FioriPoint(it.latitude, it.longitude)) .title(customer.firstName + " " + customer.lastName) .legendTitle(customer.country) .color(color) .build() mActionProvider.addMarker(customerMarker) markers[customer.city + ", " + customer.country] = customerMarker mGoogleFioriMapView.map?.moveCamera(CameraUpdateFactory.newLatLng(it)) } } -
Run the app.
The markers now have different colors depending on whether they are located in Canada (CA), the United States (US), or Mexico (MX). The meaning of the colors is shown in the legend.

With clustering enabled, notice that clustered markers turn white if the markers in the cluster are located in different countries.
Congratulations. You have now seen how an app can make use of the Fiori map control.
What object is used to customize markers?
-