- Developers
- Tutorials
- Display Customer Locations Using a Fiori Map Control
Display Customer Locations Using a Fiori Map Control
-
Join the conversation on Facebook
-
Join the conversation on Twitter
-
Subscribe to the YouTube Channel
-
Join the conversation on LinkedIn
-
View our projects on GitHub
-
Share via email
Display Customer Locations Using a Fiori Map Control
You will learn
- 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 completed Try Out SAP Cloud Platform SDK for Android Wizard.
- Downloaded and Installed version 3.2.1 (or higher version) of the SAP Cloud Platform SDK 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.
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 Activity
. -
Set Activity Name to be
CustomersMapActivity
. -
Click Finish.
-
In the
google_maps_api.xml
, on line 7 correct the package name at the end of the line to becom.sap.wizapp
only. -
Paste the URL (line 7) into a browser to register the application with the Maps SDK for Android. Follow the instructions to get an API Key and paste it into line 23 of
google_maps_api.xml
in place of the textYOUR_KEY_HERE
. -
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeEntitySetListActivity
to openEntitySetListActivity.java
. -
On Windows, press
Ctrl+F
, or, on a Mac presscommand+F
, and search forCustomersActivity.class
. -
Replace
CustomersActivity.class
withCustomersMapActivity.class
so that when the user taps on Customers, the app will navigate to the newly added activity with a map on it. -
On Windows press
Ctrl+N
or on a Mac presscommand+O
, and typeCustomersMapActivity
to openCustomersMapActivity.java
. -
Add the following import if it doesn’t automatically add itself:
import com.sap.wizapp.R;
-
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.
-
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 Activity
. -
Set Activity Name to be
CustomersMapActivity
. -
Click Finish.
-
In the
google_maps_api.xml
, on line 7 correct the package name at the end of the line to becom.sap.wizapp
only. -
Paste the URL (line 7) into a browser to register the application with the Maps SDK for Android. Follow the instructions to get an API key and paste it into line 23 of
google_maps_api.xml
in place of the textYOUR_KEY_HERE
. -
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeEntitySetListActivity
to openEntitySetListActivity.kt
. -
On Windows, press
Ctrl+F
, or, on a Mac presscommand+F
, and search forCustomersActivity::class
. -
Replace
CustomersActivity::class
withCustomersMapActivity::class
so that when the user taps on Customers, the app will navigate to the newly added activity with a map on it. -
On Windows press
Ctrl+N
or on a Mac presscommand+O
, and typeCustomersMapActivity
to openCustomersMapActivity.kt
. -
Add the following import if it doesn’t automatically add itself:
import com.sap.wizapp.R
-
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.
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 typeCustomersMapActivity
to openCustomersMapActivity.java
. -
Add the following class variable:
private HashMap<String, LatLng> locations = new HashMap<String, LatLng>();
-
Add the following methods:
private void addCustomersToMap() { DataQuery query = new DataQuery() .from(ESPMContainerMetadata.EntitySets.customers) .where(Customer.country.equal("US") .or(Customer.country.equal("CA")) .or(Customer.country.equal("MX"))); SAPServiceManager sapServiceManager = ((SAPWizardApplication) getApplication()).getSAPServiceManager(); ESPMContainer espmContainer = sapServiceManager.getESPMContainer(); espmContainer.getCustomersAsync(query, (List<Customer> customers) -> { for (Customer customer : customers) { Log.d("", "Adding a marker for " + customer.getCity()); addCustomerMarkerToMap(customer); } }, (RuntimeException re) -> { Log.d("", "An error occurred during async query: " + re.getMessage()); }); } private LatLng getCustomerLatLongFromAddress(String address) { //import android.location.Address; List<Address> addresses; LatLng latLng = locations.get(address); if (latLng != null) { return latLng; } //String strAddress = "Wilmington, Delaware, US"; Geocoder coder = new Geocoder(this); try { // May throw an IOException addresses = coder.getFromLocationName(address, 5); if (addresses == null || addresses.size() == 0) { return null; } Address location = addresses.get(0); latLng = new LatLng(location.getLatitude(), location.getLongitude()); return latLng; } catch (IOException ex) { ex.printStackTrace(); return null; } } private void addCustomerMarkerToMap(Customer customer) { LatLng latLng = getCustomerLatLongFromAddress(customer.getCity() + ", " + customer.getCountry()); if (latLng != null) { Marker customerMarker = mMap.addMarker(new MarkerOptions() //.snippet("") .position(latLng) .title(customer.getFirstName() + " " + customer.getLastName()) ); customerMarker.setTag(customer); } }
-
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonMapReady
to move to theonMapReady
method. -
Replace it with the following code:
@Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; LatLng centre = new 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", new LatLng(39.744655, -75.5483909)); locations.put("Antioch, Illinois, US", new LatLng(42.4772418, -88.0956396)); locations.put("Santa Clara, California, US", new LatLng(37.354107899999995, -121.9552356)); locations.put("Hermosillo, MX", new LatLng(29.0729673, -110.9559192)); locations.put("Bismarck, North Dakota, US", new LatLng(46.808326799999996, -100.7837392)); locations.put("Ottawa, CA", new LatLng(45.4215296, -75.69719309999999)); locations.put("México, MX", new LatLng(23.634501, -102.55278399999999)); locations.put("Boca Raton, Florida, US", new LatLng(26.368306399999998, -80.1289321)); locations.put("Carrollton, Texas, US", new LatLng(32.9756415, -96.8899636)); locations.put("Lombard, Illinois, US", new LatLng(41.8800296, -88.00784349999999)); locations.put("Moorestown, US", new 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.
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersMapActivity
to openCustomersMapActivity.kt
. -
Add the following class variable:
private val locations = HashMap<String, LatLng>()
-
Add the following methods:
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) { 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 typeonMapReady
to move to theonMapReady
method. -
Replace it with the following code:
override 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.
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
, afterimplements OnMapReadyCallback
, add:, GoogleMap.OnInfoWindowClickListener
-
Add the below method to the class:
@Override public void onInfoWindowClick(Marker marker) { Customer customer = (Customer) marker.getTag(); Intent intent = new Intent(this, CustomersActivity.class); intent.putExtra(BundleKeys.ENTITY_INSTANCE, customer); startActivity(intent); }
-
In the
onMapReady
method inCustomersMapActivity
, add the following line to the end of the method:mMap.setOnInfoWindowClickListener(this);
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersActivity
to openCustomersActivity.java
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonCreate
to navigate to theonCreate
method. -
Replace the content of the
else
block in theonCreate
method with the following code:CustomersListFragment listFragment = new CustomersListFragment(); Bundle extra = getIntent().getExtras(); if (extra != null && extra.containsKey(BundleKeys.ENTITY_INSTANCE)) { listFragment.setArguments(extra); } getSupportFragmentManager().beginTransaction() .replace(R.id.masterFrame, listFragment, UIConstants.LIST_FRAGMENT_TAG) .commit();
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersListFragment
to openCustomersListFragment.java
. -
Add the following variable to the top of the class:
private Customer entityFromMap = null;
-
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonCreate
to navigate to theonCreate
method. -
Add the following code to the end of the method:
if (getArguments() != null) { if (getArguments().containsKey(BundleKeys.ENTITY_INSTANCE)) { entityFromMap = (Customer) getArguments().get(BundleKeys.ENTITY_INSTANCE); } }
-
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonViewStateRestored
to navigate to theonViewStateRestored
method. -
Add the following code to the end of the method:
if (entityFromMap != null) { viewModel.setSelectedEntity(entityFromMap); listener.onFragmentStateChange(UIConstants.EVENT_ITEM_CLICKED, entityFromMap); 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.
-
In the class definition for
CustomersMapActivity
, afterOnMapReadyCallback
, add:, GoogleMap.OnInfoWindowClickListener
-
Add the following method to the class:
override 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
onMapReady
method inCustomersMapActivity
, add the following line to the end of the method:mMap.setOnInfoWindowClickListener(this)
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersActivity
to openCustomersActivity.kt
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonCreate
to navigate to theonCreate
method. -
Replace the
if (savedInstanceState == null)
block (keepelse-block
unchanged) in theonCreate
method with the following code:if (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 typeCustomersListFragment
to openCustomersListFragment.kt
. -
Add the following variable to the top of the class:
private var entityFromMap: Customer? = null
-
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonCreate
to navigate to theonCreate
method. -
Add the following code to the end of the method:
arguments?.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 typeonViewStateRestored
to navigate to theonViewStateRestored
method. -
Add the following code to the end of the method:
entityFromMap?.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.
In this section, you will create a new activity that uses the Fiori Map control.
-
Press
Shift
twice, and typestyles.xml
to openstyles.xml
. -
Declare the style of
AppTheme
as:<style name="AppTheme" parent="FioriTheme">
-
Add the following dependency in the app’s
build.gradle
file in the dependencies object.implementation group: 'com.sap.cloud.android', name: 'google-maps', version: sdkVersion
-
Create a new Layout Resource File in
res/layout
calleddetail_panel.xml
and replace its contents with the following code.<?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/layout
calledsearch_auto_complete.xml
and replace its contents with the following code:<?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 Activity.
-
Set Activity Name to be
CustomersFioriMapActivity
. -
Click Finish.
-
Replace the file contents in the newly created
CustomersFioriMapActivity.java
with the following code:package com.sap.wizapp.mdui.customers; import android.content.Context; import android.content.Intent; import android.location.Address; import android.location.Geocoder; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; 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.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.sap.cloud.android.odata.espmcontainer.Customer; import com.sap.cloud.android.odata.espmcontainer.ESPMContainer; import com.sap.cloud.android.odata.espmcontainer.ESPMContainerMetadata; import com.sap.cloud.mobile.fiori.maps.FioriMarkerOptions; import com.sap.cloud.mobile.fiori.maps.FioriPoint; import com.sap.cloud.mobile.fiori.maps.LegendButton; import com.sap.cloud.mobile.fiori.maps.LocationButton; import com.sap.cloud.mobile.fiori.maps.SettingsButton; import com.sap.cloud.mobile.fiori.maps.ZoomExtentButton; 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 com.sap.wizapp.service.SAPServiceManager; import java.io.IOException; import java.util.Arrays; import java.util.List; import androidx.appcompat.app.AppCompatActivity; public class CustomersFioriMapActivity extends AppCompatActivity implements GoogleFioriMapView.OnMapCreatedListener { private GoogleFioriMapView mGoogleFioriMapView; private boolean mUseClustering = false; private int mMapType; private HashMap<String, LatLng> locations = new HashMap<String, LatLng>(); // Used for demo purposes to speed up the process of converting an address to lat, long private HashMap<String, FioriMarkerOptions> markers = new HashMap<String, FioriMarkerOptions>(); // Used to associate an address with a marker for search private ArrayList<String> addresses = new ArrayList<String>(); // Used to populate the list of addresses that are searchable GoogleMapActionProvider mActionProvider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); setContentView(R.layout.activity_customers_fiori_map); mGoogleFioriMapView = findViewById(R.id.googleFioriMap); mGoogleFioriMapView.onCreate(savedInstanceState); if (savedInstanceState != null) { mUseClustering = savedInstanceState.getBoolean("UseClustering", false); mMapType = savedInstanceState.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 public void onMapCreated() { mActionProvider = new 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", new LatLng(39.744655, -75.5483909)); locations.put("Antioch, Illinois, US", new LatLng(42.4772418, -88.0956396)); locations.put("Santa Clara, California, US", new LatLng(37.354107899999995, -121.9552356)); locations.put("Hermosillo, MX", new LatLng(29.0729673, -110.9559192)); locations.put("Bismarck, North Dakota, US", new LatLng(46.808326799999996, -100.7837392)); locations.put("Ottawa, CA", new LatLng(45.4215296, -75.69719309999999)); locations.put("México, MX", new LatLng(23.634501, -102.55278399999999)); locations.put("Boca Raton, Florida, US", new LatLng(26.368306399999998, -80.1289321)); locations.put("Carrollton, Texas, US", new LatLng(32.9756415, -96.8899636)); locations.put("Lombard, Illinois, US", new LatLng(41.8800296, -88.00784349999999)); locations.put("Moorestown, US", new LatLng(39.9688817, -74.948886)); addCustomersToMap(); // Setup toolbar buttons and add to the view. SettingsButton settingsButton = new SettingsButton(mGoogleFioriMapView.getToolbar().getContext()); LegendButton legendButton = new LegendButton(mGoogleFioriMapView.getToolbar().getContext()); LocationButton locationButton = new LocationButton(mGoogleFioriMapView.getToolbar().getContext()); ZoomExtentButton extentButton = new ZoomExtentButton(mGoogleFioriMapView.getToolbar().getContext()); ImageButton[] buttons = {settingsButton, legendButton, locationButton, extentButton}; mGoogleFioriMapView.getToolbar().addButtons(Arrays.asList(buttons)); // Setup draggable bottom panel LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); View detailView = inflater.inflate(R.layout.detail_panel, null); mGoogleFioriMapView.setDefaultPanelContent(detailView); mActionProvider.setClustering(false); LatLng currentPosition = ((GoogleMapViewModel)mActionProvider.getMapViewModel()).getLatLng(); float currentZoom = ((GoogleMapViewModel)mActionProvider.getMapViewModel()).getZoom(); if (currentPosition != null && currentZoom != 0) { // Position the camera after a lifecycle event. mGoogleFioriMapView.getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(currentPosition, currentZoom)); } else { // Move the camera to the centre of North America LatLng centre = new LatLng(39.8283, -98.5795); mGoogleFioriMapView.getMap().animateCamera(CameraUpdateFactory.newLatLng(centre)); } FioriMapSearchView mFioriMapSearchView = findViewById(R.id.fiori_map_search_view); if (mFioriMapSearchView != null) { SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); mFioriMapSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); mFioriMapSearchView.setAdapter(new ArrayAdapter<String>(CustomersFioriMapActivity.this, R.layout.search_auto_complete, R.id.search_auto_complete_text, addresses)); mFioriMapSearchView.setThreshold(2); mFioriMapSearchView.setOnItemClickListener((parent, view, position, id) -> { mFioriMapSearchView.setQuery(parent.getItemAtPosition(position).toString(), false); searchResultSelected((String) parent.getItemAtPosition(position)); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(mFioriMapSearchView.getWindowToken(), 0); }); } } private void searchResultSelected(String selectedSearchResult) { LatLng latLng = locations.get(selectedSearchResult); if (latLng != null) { // Center the marker. mGoogleFioriMapView.getMap().moveCamera(CameraUpdateFactory.newLatLng(latLng)); // Select the marker (or cluster the marker is in). mActionProvider.selectMarker(markers.get(selectedSearchResult)); } } // Methods overriding the lifecycle events are required for FioriMapView to run properly @Override public void onStart() { super.onStart(); mGoogleFioriMapView.onStart(); } @Override public void onResume() { super.onResume(); mGoogleFioriMapView.onResume(); } @Override public void onPause() { super.onPause(); mGoogleFioriMapView.onPause(); } @Override public void onStop() { super.onStop(); mGoogleFioriMapView.onStop(); } @Override public void onDestroy() { super.onDestroy(); mGoogleFioriMapView.onDestroy(); } @Override protected void onSaveInstanceState(Bundle bundle) { super.onSaveInstanceState(bundle); mGoogleFioriMapView.onSaveInstanceState(bundle); bundle.putBoolean("UseClustering", mUseClustering); bundle.putInt("MapType", mMapType); } @Override public void onLowMemory() { super.onLowMemory(); mGoogleFioriMapView.onLowMemory(); } private LatLng getCustomerLatLongFromAddress(String address) { // import android.location.Address; List<Address> addresses; LatLng latLng = locations.get(address); if (latLng != null) { return latLng; } // String strAddress = "Wilmington, Delaware"; Geocoder coder = new Geocoder(this); try { // May throw an IOException addresses = coder.getFromLocationName(address, 5); if (addresses == null || addresses.size() == 0) { return null; } Address location = addresses.get(0); latLng = new LatLng(location.getLatitude(), location.getLongitude()); return latLng; } catch (IOException ex) { ex.printStackTrace(); return null; } } private void addCustomerMarkerToMap(Customer customer) { LatLng latLng = getCustomerLatLongFromAddress(customer.getCity() + ", " + customer.getCountry()); if (latLng != null) { FioriMarkerOptions customerMarker = new FioriMarkerOptions.Builder() .tag(customer) .point(new FioriPoint(latLng.latitude, latLng.longitude)) .title(customer.getFirstName() + " " + customer.getLastName()) .legendTitle("Customer") .build(); mActionProvider.addMarker(customerMarker); markers.put(customer.getCity() + ", " + customer.getCountry(), customerMarker); mGoogleFioriMapView.getMap().moveCamera(CameraUpdateFactory.newLatLng(latLng)); } } private void addCustomersToMap() { DataQuery query = new DataQuery() .from(ESPMContainerMetadata.EntitySets.customers) .where(Customer.country.equal("US") .or(Customer.country.equal("CA")) .or(Customer.country.equal("MX"))); SAPServiceManager sapServiceManager = ((SAPWizardApplication) getApplication()).getSAPServiceManager(); ESPMContainer espmContainer = sapServiceManager.getESPMContainer(); espmContainer.getCustomersAsync(query, (List<Customer> customers) -> { for (Customer customer : customers) { addCustomerMarkerToMap(customer); addresses.add(customer.getCity() + ", " + customer.getCountry()); } mActionProvider.doExtentsAction(); }, (RuntimeException re) -> { Log.d("", "An error occurred during async query: " + re.getMessage()); }); } }
-
Press Shift twice and type
activity_customers_fiori_map.xml
to openactivity_customers_fiori_map.xml
. -
Replace its contents with the following code:
<?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 typeEntitySetListActivity
to openEntitySetListActivity.java
. -
On Windows, press
Ctrl+F
, or, on a Mac, presscommand+F
, and search forCustomersMapActivity.class
. -
Replace
CustomersMapActivity.class
withCustomersFioriMapActivity.class
so 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 typeAndroidManifest
to openAndroidManifest.xml
. -
On Windows, press
Ctrl+F
, or, on a Mac, presscommand+F
, and search forCustomersFioriMapActivity
. -
Modify the activity so it specifies the
NoActionBar
theme, which will cause the activity to not display an action bar.<activity android:name=".mdui.customers.CustomersFioriMapActivity" android:theme="@style/AppTheme.NoActionBar"> </activity>
-
Run the app.
You should be able to see markers on the screen representing customers.
Users can use the search bar at the top of the screen to find markers. For example, enter
Illinois
orMX
.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.
-
Press
Shift
twice, and typestyles.xml
to openstyles.xml
. -
Declare the style of
AppTheme
as:<style name="AppTheme" parent="FioriTheme">
-
Add the following dependency in the app’s
build.gradle
file in the dependencies object.implementation group: 'com.sap.cloud.android', name: 'google-maps', version: sdkVersion
-
Create a new Layout Resource File in
res/layout
calleddetail_panel.xml
and replace its contents with the following code.<?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/layout
calledsearch_auto_complete.xml
and replace its contents with the following code.<?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 Activity.
-
Set Activity Name to be
CustomersFioriMapActivity
. -
Click Finish.
-
Replace the file contents in the newly created
CustomersFioriMapActivity.kt
with the following code:package 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.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Marker import com.sap.cloud.android.odata.espmcontainer.Customer import com.sap.cloud.android.odata.espmcontainer.ESPMContainer 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 com.sap.wizapp.service.SAPServiceManager 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) 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.xml
to openactivity_customers_fiori_map.xml
. -
Replace its contents with the following code.
<?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 typeEntitySetListActivity
to openEntitySetListActivity.kt
. -
On Windows, press
Ctrl+F
, or, on a Mac, presscommand+F
, and search forCustomersMapActivity::class
. -
Replace
CustomersMapActivity::class
withCustomersFioriMapActivity::class
so 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 typeAndroidManifest
to openAndroidManifest.xml
. -
On Windows, press
Ctrl+F
, or, on a Mac, presscommand+F
, and search forCustomersFioriMapActivity
. -
Modify the activity so it specifies the
NoActionBar
theme, which will cause the activity to not display an action bar.<activity android:name=".mdui.customers.CustomersFioriMapActivity" android:theme="@style/AppTheme.NoActionBar"> </activity>
-
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
Illinois
orMX
.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.
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 typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.java
. -
At the top of the class, add the following variables:
private MapListPanel mMapListPanel; private MapResultsAdapter mMapResultsAdapter;
-
At the bottom of the class, add the following methods and the
MapResultsAdapter
class.private void setupInfoProvider() { AnnotationInfoAdapter infoAdapter = new AnnotationInfoAdapter() { @Override public Object getInfo(Object tag) { return tag; } @Override public void onBindView(MapPreviewPanel mapPreviewPanel, Object info) { Customer customer = (Customer) info; mapPreviewPanel.setTitle(customer.getFirstName() + " " + customer.getLastName()); ObjectHeader objectHeader = mapPreviewPanel.getObjectHeader(); objectHeader.setHeadline(customer.getCity() + ", " + customer.getCountry()); LatLng customerLatLng = getCustomerLatLongFromAddress(customer.getCity() + ", " + customer.getCountry()); objectHeader.setBody("Latitude: " + customerLatLng.latitude); objectHeader.setFootnote("Longitude " + customerLatLng.longitude); ActionCell cell = new ActionCell(CustomersFioriMapActivity.this); cell.setText(customer.getPhoneNumber()); cell.setIcon(R.drawable.ic_phone_black_24dp); ActionCell cell2 = new ActionCell(CustomersFioriMapActivity.this); cell2.setText(customer.getEmailAddress()); cell2.setIcon(R.drawable.ic_email_black_24dp); ActionCell cell3 = new ActionCell(CustomersFioriMapActivity.this); cell3.setText(customer.getHouseNumber() + " " + customer.getStreet()); cell3.setIcon(R.drawable.ic_map_marker_unselected); ActionCell cell4 = new ActionCell(CustomersFioriMapActivity.this); cell4.setText("Additional Details"); cell4.setIcon(R.drawable.ic_list_24dp); cell4.setOnClickListener(v -> { Intent intent = new Intent(CustomersFioriMapActivity.this, CustomersActivity.class); intent.putExtra(BundleKeys.ENTITY_INSTANCE, customer); startActivity(intent); } ); mapPreviewPanel.setActionCells(cell, cell2, cell3, cell4); } }; mActionProvider.setAnnotationInfoAdapter(infoAdapter); } private void setListAdapter() { if (mMapListPanel == null) { mMapListPanel = mGoogleFioriMapView.getMapListPanel(); mMapResultsAdapter = new MapResultsAdapter(); mMapListPanel.setAdapter(mMapResultsAdapter); } } public static class ViewHolder extends RecyclerView.ViewHolder { public ObjectCell objectCell; public ViewHolder(@NonNull View itemView) { super(itemView); if (itemView instanceof ObjectCell) { objectCell = (ObjectCell) itemView; } } } class MapResultsAdapter extends RecyclerView.Adapter<ViewHolder> implements MapListPanel.MapListAdapter { List<Customer> customers = new ArrayList<>(); @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { ObjectCell cell = new ObjectCell(parent.getContext()); cell.setPreserveIconStackSpacing(true); cell.setPreserveDetailImageSpacing(false); ViewHolder viewHolder = new ViewHolder(cell); return viewHolder; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Customer customer = customers.get(position); ObjectCell resultCell = holder.objectCell; resultCell.setHeadline(customer.getFirstName() + " " + customer.getLastName()); resultCell.setSubheadline(customer.getHouseNumber() + " " + customer.getStreet() + ", " + customer.getCity()); resultCell.setStatus(customer.getCountry(), 1); resultCell.setOnClickListener(v -> { Intent intent = new Intent(CustomersFioriMapActivity.this, CustomersActivity.class); intent.putExtra(BundleKeys.ENTITY_INSTANCE, customer); startActivity(intent); }); } @Override public int getItemCount() { return customers.size(); } @Override public void clusterSelected(@Nullable List<FioriMarkerOptions> list) { customers.clear(); for (FioriMarkerOptions fmo : list) { Object tag = fmo.tag; if (tag != null) { customers.add((Customer) tag); } } } }
-
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:setupInfoProvider(); 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.
View 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.
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.kt
. -
At the top of the class, add the following variables:
private lateinit var mMapResultsAdapter: MapResultsAdapter
-
At the bottom of the class, add the following methods and the
MapResultsAdapter
class.private 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:setupInfoProvider() 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.
val 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.
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/layout
calledsettings_panel.xml
and replace its contents with the following code. This creates the layout for the settings page.<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" 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 typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.java
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonMapCreated
to navigate to theonMapCreated
method. -
Find the following line of code:
mActionProvider.setClustering(false);
-
Replace the line above with the following code.
View settingsView = inflater.inflate(R.layout.settings_panel, null); // Setup selection of a different map type ChoiceFormCell mapTypeChoice = settingsView.findViewById(R.id.map_type); mapTypeChoice.setValueOptions(new String[]{"Normal", "Satellite", "Terrain", "Hybrid"}); mapTypeChoice.setCellValueChangeListener(new FormCell.CellValueChangeListener<Integer>() { @Override public void cellChangeHandler(Integer value) { switch(value) { case 0: mMapType = GoogleMap.MAP_TYPE_NORMAL; break; case 1: mMapType = GoogleMap.MAP_TYPE_SATELLITE; break; case 2: mMapType = GoogleMap.MAP_TYPE_TERRAIN; break; case 3: mMapType = GoogleMap.MAP_TYPE_HYBRID; break; } mGoogleFioriMapView.getMap().setMapType(mMapType); } }); if (mMapType == 0) { mapTypeChoice.setValue(mMapType); } else { mapTypeChoice.setValue(mMapType - 1); } mGoogleFioriMapView.setSettingsView(settingsView); if (mMapType != GoogleMap.MAP_TYPE_NONE) { mGoogleFioriMapView.getMap().setMapType(mMapType); } // Setup clustering selection. SwitchFormCell useClusteringSwitch = settingsView.findViewById(R.id.use_clustering); useClusteringSwitch.setValue(mUseClustering); mActionProvider.setClustering(mUseClustering); useClusteringSwitch.setCellValueChangeListener(new FormCell.CellValueChangeListener<Boolean>() { @Override protected void cellChangeHandler(@NonNull Boolean value) { mUseClustering = value; 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.
-
Create a new Layout resource file in
res/layout
calledsettings_panel.xml
and replace its contents with the following code. This creates the layout for the settings page.<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" 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 typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.kt
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonMapCreated
to navigate to theonMapCreated
method. -
Find the following line of code:
mActionProvider.setClustering(false)
-
Replace the line above with the following code:
val 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.
In this section, you will test the three different types of annotations.
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.java
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonCreate
to navigate to theonCreate
method. -
Add the following code after the
mGoogleFioriMapView.onCreate
is called. This will save any points,polylines
, or polygons drawn on the map.// Handle the editor's save event. // This is where you can implement functions to save the new annotated points, polylines and polygons mGoogleFioriMapView.getEditorView().setOnSaveListener(new EditorView.OnSaveListener() { @Override public void onSaveEdit(Annotation annotation) { String message = null; if (annotation instanceof PointAnnotation) { message = "This is a point"; } else if (annotation instanceof PolylineAnnotation) { message = "This is a polyline with "+ annotation.getPoints().size()+" points"; } else if (annotation instanceof PolygonAnnotation) { message = "This is a polygon with "+annotation.getPoints().size()+" points"; } // import android.app AlertDialog AlertDialog.Builder builder = new AlertDialog.Builder(mGoogleFioriMapView.getContext(), com.sap.cloud.mobile.fiori.R.style.FioriAlertDialogStyle); builder.setMessage(message); builder.setPositiveButton("Save", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // Close the editor. mGoogleFioriMapView.setEditable(false); if (annotation instanceof PointAnnotation) { mActionProvider.addCircle( new FioriCircleOptions.Builder().center((FioriPoint) annotation.getPoints().get(0)). radius(40). strokeColor(getResources().getColor(R.color.maps_marker_color_5, null)). fillColor(getResources().getColor(R.color.maps_marker_color_6, null)). title("Editor Circle"). build()); } else if (annotation instanceof PolylineAnnotation) { mActionProvider.addPolyline( new FioriPolylineOptions.Builder().addAll(annotation.getPoints()). color(getResources().getColor(R.color.maps_marker_color_3, null)). strokeWidth(4). title("Editor Polyline"). build()); } else if (annotation instanceof PolygonAnnotation) { mActionProvider.addPolygon( new FioriPolygonOptions.Builder().addAll(annotation.getPoints()). strokeColor(getResources().getColor(R.color.maps_marker_color_3, null)). fillColor(getResources().getColor(R.color.maps_marker_color_4, null)). strokeWidth(4). title("Editor Polygon"). build()); } } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.cancel(); } }); AlertDialog dialog = builder.create(); dialog.show(); } });
-
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 Waterloo
and 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.
-
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 addresscom.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 API
in the search bar and you’ll be redirected to the following page.If you enabled Places API but still nothing happens after tapping, use
https://maps.googleapis.com/maps/api/place/details/json?place_id=ChIJN1t_tDeuEmsRUsoyG83frY4&fields=name,rating,formatted_phone_number&key=YOUR_API_KEY
to 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 portion. 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 you can add a new point.
-
-
To add a
polyline
to the map, select thePolyline
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.
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.kt
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeonCreate
to navigate to theonCreate
method. -
Add the following code after the
mGoogleFioriMapView.onCreate
is called. This will save any points,polylines
, or polygons drawn on the map.// 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 if (annotation is PointAnnotation) { message = "This is a point" } else if (annotation is PolylineAnnotation) { message = "This is a polyline with " + annotation.points.size + " points" } else if (annotation 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") { dialogInterface, i -> // Close the editor. mGoogleFioriMapView.isEditable = false if (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(getResources().getColor(R.color.maps_marker_color_6, null)). title("Editor Circle"). build()) } else if (annotation 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()) } else if (annotation 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, i -> dialogInterface.cancel() } val dialog = builder.create() dialog.show() }
-
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 Waterloo
and 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.
-
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 addresscom.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 API
in the search bar and you’ll be redirected to the following page.If you enabled Places API but still nothing happens after tapping, use
https://maps.googleapis.com/maps/api/place/details/json?place_id=ChIJN1t_tDeuEmsRUsoyG83frY4&fields=name,rating,formatted_phone_number&key=YOUR_API_KEY
to 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 portion. 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 thePolyline
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.
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 typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.java
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeaddCustomerMarkerToMap
to navigate to theaddCustomerMarkerToMap
method. -
Replace the method with the following code:
private void addCustomerMarkerToMap(Customer customer) { FioriMarkerOptions customerMarker; String country = customer.getCountry(); LatLng latLng = getCustomerLatLongFromAddress(customer.getCity() + ", " + customer.getCountry()); if (latLng != null) { int color = (Color.parseColor("#E9573E")); // US if (country.equals("MX")) { color = (Color.parseColor("#FFA02B")); } else if (country.equals("CA")){ color = Color.parseColor("#2E4A62"); } customerMarker = new FioriMarkerOptions.Builder() .tag(customer) .point(new FioriPoint(latLng.latitude, latLng.longitude)) .title(customer.getFirstName() + " " + customer.getLastName()) .legendTitle(customer.getCountry()) .color(color) .build(); mActionProvider.addMarker(customerMarker); markers.put(customer.getCity() + ", " + customer.getCountry(), customerMarker); mGoogleFioriMapView.getMap().moveCamera(CameraUpdateFactory.newLatLng(latLng)); } }
-
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.
-
On Windows, press
Ctrl+N
, or, on a Mac, presscommand+O
, and typeCustomersFioriMapActivity
to openCustomersFioriMapActivity.kt
. -
On Windows, press
Ctrl+F12
, or, on a Mac, presscommand+F12
, and typeaddCustomerMarkerToMap
to navigate to theaddCustomerMarkerToMap
method. -
Replace the method with the following code:
private 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("#2E4A62") } 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.put(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 created an activity that makes use of the Fiori map control.
Next Steps
-
Step 1: Create a new screen to display a map
-
Step 2: Populate the map with customer locations
-
Step 3: Implement navigation to the customer details screen
-
Step 4: Enhance the app to use the Fiori Map control
-
Step 5: Implement the map panel
-
Step 6: Implement settings
-
Step 7: Enable annotating
-
Step 8: Customize map markers and legend
- Back to Top