Skip to Content

Extend Your MDK App With a Map Custom Control (Using Metadata Approach)

test
0 %
Extend Your MDK App With a Map Custom Control (Using Metadata Approach)
Details

Extend Your MDK App With a Map Custom Control (Using Metadata Approach)

September 10, 2020
Created by
March 23, 2020
Build and run the Mobile Development Kit client with Map custom control functionality for Android and iOS platforms.

You will learn

  • How to register and consume an Extension control in MDK Metadata
  • How to write an extension via NativeScript
  • How to build a Mobile development kit client for iOS and Android
  • How to connect to SAP Cloud Platform Mobile application

Prerequisites

You may clone an existing metadata project from GitHub repository and start directly with step 5 in this tutorial.


To extend the functionality, or customize the look and feel, and behavior of your client app, you can create extension controls other than the already existing MDK built-in controls using the following:

  • NativeScript (TypeScript/JavaScript applicable for both Android and iOS)

  • Swift class (iOS only)

In this tutorial, you will create a Map extension via NativeScript (in TypeScript language), you will view the Map in Apple Maps on iOS devices and in Google Maps on Android devices.

MDK
Step 1: Create a new MDK project in SAP Business Application Studio

This step includes creating the mobile development kit project in the editor.

  1. Launch the Dev space in SAP Business Application Studio.

  2. If you do not see the Welcome page, navigate to View menu → Find Command → search with Welcome to launch the Welcome page.

    MDK
  3. In Welcome page, click New project from template .

    MDK
  4. Select MDK Project and click Next.

    MDK
  5. In Basic Information step, select or provide the below information and click Next:

    Field Value
    MDK Template Type Select List Detail from the dropdown
    Your Project Name MDK_Maps
    Your Project Name <default name is same as Project name, you can provide any name of your choice>
    MDK

    More details on MDK template is available in help documentation.

  6. In SAP Cloud Platform Connection step, you will see your Cloud Foundry Organization and Space information. If you are not logged on yet, provide required credentials to retrieve your details. Click Next.

    MDK
  7. In Service Configuration step, provide or select the below information and click Next:

    Field Value
    Service File Name <You can continue with default name or provide any name of your choice>
    OData Source Select Mobile Services from the dropdown
    Application Id Select com.sap.mdk.demo from the dropdown
    Destination Select com.sap.edm.sampleservice.v2 from the dropdown
    Enable Offline Choose No
    MDK

    Regardless of whether you are creating an online or offline application, this step is needed app to connect to an OData service. When building an Mobile Development Kit application, it assumes the OData service created and the destination that points to this service is setup in Mobile Services and SAP Cloud Platform.

    Since you will create an online based app, hence Enable Offline Store option is unchecked.

  8. In OData Collections step, select Customers (if not selected by default). Click Next to finish the project creation.

    MDK
  9. After clicking Next, the wizard will generate your MDK Application based on your selections. You should now see the MDK_Maps project in the project explorer. As you have already opened the workspace, there is no need to open the generated project in new workspace or to add it to workspace. Ignore the pop-up or click the cross icon to hide the window.

    MDK
Log on to answer question
Step 2: Register an Extension Control

The extension control that you will be creating to extend the functionality of your app can be used as base controls by registering it using the MDK editor.

  1. Expand MDK_Maps project, right-click Extensions | select MDK: Register Extension Control.

    MDK
  2. In Template Selection step, select New and register Metadata Extension Control. Click Next.

    MDK
  3. Provide the below information:

    Field Value
    Control Name mdk_maps
    Module MyMapModule
    Control MyMapExtension
    Class MyMapClass
    Display leave it blank

    Here is the basic definition for properties you defined above:

    Module: It is used to identify the extension control.
    The path to the extension module under <MetadataProject>/Extensions/.

    Control: The name of the file under the <MetadataProject>/Extensions/<Module>/controls that contains the extension class. If not specified, module name would be used as the value for this property.

    Class: The class name of your custom extension class. The client will check for this class at runtime and if it’s found, your extension will be instantiated. Otherwise, a stub with an error message will be shown.

    Display: This property is used for the image to be displayed on the page editor to represent the extension control. Use the binding button to select an image from the \MDK_Maps\Images folder.

    MDK

    You will refer all these properties in step 4.

  4. In Schema Information window, click Next. For this tutorial, you will not need any schema.

    MDK

    Here you can define the properties of the extension control or import a property sample.

  5. Click Finish to confirm.

    Some additional files and folders are added to the Extensions folder. You will learn more about it in following steps.

    MDK

    You can find more details about registering extension control in this guide.

Log on to answer question
Step 3: Consume Extension Control in MDK Metadata

You will add this registered control in the generated Customers_Detail.page.

  1. Navigate to Pages folder | Customers | Customers_Detail.page.

  2. Remove the body section of the page.

    MDK
  3. Expand Compound, drag & drop Section Extension control on the page area.

    MDK

    You can find more details about the Section Extension in this guide.

  4. You will now set the height and bind it the registered Extension control properties.

    In the Properties section, provide the below information:

    Field Value
    Height 600
    Module MyMapModule
    Control MyMapExtension
    Class MyMapClass

    Scroll down to the Extension Properties, and paste the following information:

    {
      	"Prop": {
      	"City": "{City}",
      	"Country": "{Country}",
      	"HouseNumber": "{HouseNumber}",
      	"LastName": "{LastName}",
      	"PostalCode": "{PostalCode}",
      	"Street": "{Street}"
      	  }
    }
    
    MDK
Log on to answer question
Step 4: Implement Extension using metadata approach
  1. Navigate to Extensions | MyMapModule | controls | MyMapExtension.ts, replace the code with the following.

    import * as app from 'tns-core-modules/application';
    import { IControl } from 'mdk-core/controls/IControl';
    import { BaseObservable } from 'mdk-core/observables/BaseObservable';
    import { EventHandler } from 'mdk-core/EventHandler'
    
    export class MyMapClass extends IControl {
        private _observable: BaseObservable;
        private _mapView: any;
        private _geo: any;
        private _gMap: any;
        private _marker: any;
        private _customerInfo = {
            lastName: "",
            houseNumber: "",
            street: "",
            city: "",
            country: "",
            postalCode: "",
            latitiude: "",
            longitude: ""
        }
    
        public initialize(props: any): any {
            super.initialize(props);
    
            //Access the properties passed from Customers_Detail.page to the extension control.
            //in this tutorial, you will be accessing the customer's last name and address
            if (this.definition().data.ExtensionProperties.Prop) {
                var property = this.definition().data.ExtensionProperties.Prop;
                this._customerInfo.lastName = property.LastName;
                this._customerInfo.houseNumber = property.HouseNumber;
                this._customerInfo.street = property.Street;
                this._customerInfo.city = property.City;
                this._customerInfo.country = property.Country;
                this._customerInfo.postalCode = property.PostalCode;
            }
    
            if (app.android) {
                //You will display the Google Maps in a MapView.For more details on Google Maps API for android, visit
                //https://developers.google.com/android/reference/com/google/android/gms/maps/package-summary
    
                this._mapView = new com.google.android.gms.maps.MapView(this.androidContext());
                var localeLanguage = java.util.Locale;
    
                //GeoCoder is required to convert a location to get latitude and longitude
                this._geo = new android.location.Geocoder(this.androidContext(), localeLanguage.ENGLISH);
                this._mapView.onCreate(null);
                this._mapView.onResume();
    
                //when mapview control is used, all the lifecycle activities has to be frowaded to below methods.
                app.android.on(app.AndroidApplication.activityPausedEvent, this.onActivityPaused, this);
                app.android.on(app.AndroidApplication.activityResumedEvent, this.onActivityResumed, this);
                app.android.on(app.AndroidApplication.saveActivityStateEvent, this.onActivitySaveInstanceState, this);
                app.android.on(app.AndroidApplication.activityDestroyedEvent, this.onActivityDestroyed, this);
                var that = this;
    
                //A GoogleMap must be acquired using getMapAsync(OnMapReadyCallback).
                //The MapView automatically initializes the maps system and the view
    
                var mapReadyCallBack = new com.google.android.gms.maps.OnMapReadyCallback({
                    onMapReady: (gMap) => {
                        console.log("inside onMapReady function");
                        that._gMap = gMap;
                        var zoomValue = 6.0;
                        that._gMap.setMinZoomPreference = zoomValue;
                        var customerAddress = that._customerInfo.houseNumber + ' ' + that._customerInfo.street + ' ' + that._customerInfo.city + ' ' +
                            that._customerInfo.country + ' ' + that._customerInfo.postalCode;
                        var data = that._geo.getFromLocationName(customerAddress, 1);
                        var latLng = new com.google.android.gms.maps.model.LatLng(data.get(0).getLatitude(), data.get(0).getLongitude());
                        that._gMap.addMarker(new com.google.android.gms.maps.model.MarkerOptions().position(latLng).title(this._customerInfo.lastName +
                            "'s " + "location"));
                        that._gMap.moveCamera(new com.google.android.gms.maps.CameraUpdateFactory.newLatLng(latLng));
                    }
                });
                this._mapView.getMapAsync(mapReadyCallBack);
            }
    
            if (app.ios) {
    
                /*initiating Apple Maps
                For more details on the Apple Maps visit
                https://developer.apple.com/documentation/mapkit */
                this._mapView = MKMapView.alloc().initWithFrame(CGRectMake(0, 0, 1000, 1000));
            }
        }
    
        private onActivityPaused(args) {
            console.log("onActivityPaused()");
            if (!this._mapView || this != args.activity) return;
            this._mapView.onPause();
        }
    
        private onActivityResumed(args) {
            console.log("onActivityResumed()");
            if (!this._mapView || this != args.activity) return;
            this._mapView.onResume();
        }
    
        private onActivitySaveInstanceState(args) {
            console.log("onActivitySaveInstanceState()");
            if (!this._mapView || this != args.activity) return;
            this._mapView.onSaveInstanceState(args.bundle);
        }
    
        private onActivityDestroyed(args) {
            console.log("onActivityDestroyed()");
            if (!this._mapView || this != args.activity) return;
            this._mapView.onDestroy();
        }
    
        //In case of iOS you'll use CLGeocoder API to convert a address to get latitude and longitude.
        //NOTE - API getlatlang is called only on ios devices
    
        private getlatlang(customerAddress) {
            const that = this;
            return new Promise((resolve, reject) => {
                var latLng = new CLGeocoder();
                latLng.geocodeAddressStringCompletionHandler(customerAddress, function (placemarks, error) {
                    if (error === null && placemarks && placemarks.count > 0) {
                        var pm = placemarks[0];
                        var cordinates = {
                            latitiude: "",
                            longitude: ""
                        }
                        cordinates.latitiude = pm.location.coordinate.latitude;
                        cordinates.longitude = pm.location.coordinate.longitude;
                        resolve(cordinates);
                    } else {
                        reject();
                    }
                });
            });
        }
    
        public view() {
            this.valueResolver().resolveValue([this._customerInfo.houseNumber, this._customerInfo.street, this._customerInfo.city, this._customerInfo
                .country, this._customerInfo.postalCode, this._customerInfo.lastName
            ], this.context)
                .then((address) => {
    
                    this._customerInfo.houseNumber = address[0];
                    this._customerInfo.street = address[1];
                    this._customerInfo.city = address[2];
                    this._customerInfo.country = address[3];
                    this._customerInfo.postalCode = address[4];
                    this._customerInfo.lastName = address[5];
    
                    var customerAddress = address[0] + ' ' + address[1] + ' ' + address[2] + ' ' + address[3] + ' ' + address[4];
                    console.log("customer's address = " + customerAddress);
    
                    if (app.ios) {
                        return this.getlatlang(customerAddress)
                            .then((cordinates) => {
                                /* below code is for the apple maps */
                                var latlong = CLLocationCoordinate2DMake(cordinates.latitiude, cordinates.longitude);
                                var annotation = MKPointAnnotation.alloc().init();
                                annotation.coordinate = latlong;
                                annotation.title = this._customerInfo.lastName + "'s" + " location";
                                this._mapView.centerCoordinate = latlong;
                                this._mapView.addAnnotation(annotation);
                            });
                    }
                });
    
            if (app.android) {
                return this._mapView;
            }
            if (app.ios) {
                return this._mapView;
            }
        }
    
        public viewIsNative() {
            return true;
        }
    
        public observable() {
            if (!this._observable) {
                this._observable = new BaseObservable(this, this.definition(), this.page());
            }
            return this._observable;
        }
    
        public setContainer(container: IControl) {
            // do nothing
        }
    
        public setValue(value: any, notify: boolean, isTextValue?: boolean): Promise<any> {
            // do nothing
            return Promise.resolve();
        }
    }
    

    In your import function, if you see errors related to tns-core-modules or mdk-core, you can ignore them. There is currently no reference of such libraries in the MDK editor.

  2. Save the MyMapExtension.ts file.

Which option allow you to create MDK extension controls for Android platform?
×
Step 5: Deploy and activate application

So far, you have learned how to build an MDK application in the SAP Business Application Studio editor. Now, we deploy this application definition to Mobile Services.

Right-click Application.app and select MDK: Deploy.

MDK

You should see Deploy Succeeded message.

MDK
Log on to answer question
Step 6: Get the API Key to use the Maps SDK for Android (Required only for Android client)

Since you will display the customer’s address in Google Maps on Android device, you will need to provide an API key in generated MDK project (step 8).

  1. Visit the Google Cloud Platform Console.

  2. Click the project drop-down and select or create the project for which you want to add an API key.

    MDK
  3. Click Explore and enable APIs.

    MDK
  4. Click Enable APIS and SERVICES.

    MDK
  5. Click Maps SDK for Android API.

    MDK
  6. Click ENABLE.

    MDK
  7. Open Credentials console, click CREATE CREDENTIALS and click API Key.

    MDK
  8. Copy this generated key and save it locally. This will be required in step 8.

Log on to answer question
Step 7: Create Your Branded MDK Client (Required only for Android)

For Android, you will pass the API key to the MDK client, there is no way public store client can access it, hence you will create a branded client using MDK SDK. Follow steps 1 to 4 from this tutorial.

For iOS, you can just use the App store client. Continue with next step.

Log on to answer question
Step 8: Run the MDK Client

Make sure you are choosing the right device platform tab above.

In this step, you will run the app on an android device.

  1. Navigate to /DemoSampleApp.mdkproject/App_Resources/Android/app.gradle.

    MDK
  2. Provide below information at the end of this file.

    dependencies { implementation 'com.google.android.gms:play-services-maps:17.0.0' }
    
    MDK
  3. Navigate to /DemoSampleApp/app/App_Resources/Android/src/main/AndroidManifest.xml.

    MDK
  4. Provide below information before application closing tag.

     <meta-data android:name="com.google.android.geo.API_KEY" android:value="Enter your API Key generated in step 6" />
    
    MDK
  5. Attach the device to your Mac or Windows machine and run tns device android command to print a list of attached devices.

    MDK

    Make sure Developer option and USB debugging option is enabled in android device.

  6. Copy the Device Identifier value for your device.

  7. In terminal or command line window, navigate to the app name folder DemoSampleApp (in MDClient_SDK path) and use tns run android --device <device identifier> command to run the MDK client on android device.

    MDK

    Once, above command gets successfully executed, you will see new MDK client up and running in Android device.

    MDK
  8. Tap AGREE on End User License Agreement.

  9. In Welcome screen, tap START to connect MDK client to SAP Cloud Platform.

    MDK
  10. Enter your credentials to login to SAP Cloud Platform and tap Log On to authenticate.

    MDK
  11. Choose a passcode with at least 8 characters for unlocking the app and tap NEXT.

    MDK
  12. Confirm the passcode and tap DONE.

    MDK

    Optionally, you can enable fingerprint to get faster access to the app data.

    MDK
  13. Tap OK to update the client with new MDK metadata.

    MDK
  14. Tap CUSTOMERS to navigate to customer list.

    MDK
  15. Tap any of customer record to navigate to details page.

    MDK
  16. In Customer Details page, you will see the Customer’s address loading in Google Maps.

    MDK
  1. SAP Business Application Studio has a feature to generate QR code for app onboarding.

    Double-click the Application.app to open it in MDK Application Editor and click Application QR Code icon to populate the QR code.

    MDKMDK

    Once you have scanned and onboarded using the onboarding URL, it will be remembered. When you Log out and onboard again, you will be asked either to continue to use current application or to scan new QR code.

  2. Follow these steps to on-board the MDK client.

  3. Once you have accepted the app update, tap Customers to navigate to customer list.

    MDK
  4. Tap any of customer record to navigate to details page.

    MDK
  5. In Customer Details page, you will see the Customer’s address loading in Apple Maps.

    MDK

Congratulations! You have completed Create Extension Controls in Mobile Development Kit (MDK) Apps mission.

Log on to answer question

Next Steps

Back to top