Skip to Content
Technical Articles

Customize SAP Asset Manager for iOS with An MDK Extension Control

SAP Asset Manager is built on a metadata based platform – Mobile Development Kit (MDK) – that allows the application to be defined in metadata which is interpreted during runtime and rendered as native UI elements on iOS or Android. MDK platform itself is based on SAP Cloud Platform SDK (SCP SDK) and NativeScript. As such, MDK toolkit comes with several built-in UI controls, from SCP SDK and NativeScript, required in a mobile app. These are sufficient in majority of cases, however, there are always cases where app requires some specific controls, usually complex, that are not built in. These are what we call “Extension controls” and MDK allows application developers to implement them in Native OS and integrate these into an MDK application (client) as plugins.

In order for these extension controls to be added into the client (iOS or Android app), the client needs to be rebuilt.

This blog attempts to outline steps required for developing an MDK Extension Control that integrates with SAP Asset Manager (SAM) for iOS.

Here, we will implement a RFID Control as an example. However, the steps can be used as a guidance for developing any extension control in general.

Use Case

A user of SAP Asset Manager application will be able to search an equipment using an RFID scanner connected to the iOS device (iPhone or iPad), usually via Bluetooth LE, and display the equipment details.

Design for RFID Control

For the purpose of this blog, we will add just the necessary UI elements in SAP Asset Manager that is sufficient for the above use case. The focus is more on functionality rather than UI. As such a simplistic approach is taken.

  1. In the equipment list page, add a menu option for “RFID”
  2. Upon clicking on this menu, App presents a page with a scan button and text box
  3. Clicking on scan starts RFID scan and text box is populated with scanned value
    1. For demo purposes, due to lack of an actual RFID scanner, a Text input field will be added where user can type in equipment id, which otherwise would come from the scanner result.
  4. If no error, user is navigated to Equipment detail page.

Implementation

The extension control can be implemented in different ways:

  1. Plugin approach – Plugin approach is the recommended approach as it makes the control re-usable in multiple apps, does not depend on the NativeScript project and can be used to manage the same control for multiple platforms. Currently there are 2 ways one can create a plugin that integrates into MDK client:
    1. Using MDKExtension protocol, which simplifies the underlying infrastructure of instantiating the plugin and communication between MDK JavaScript engine and Native control
    2. Extending IControl class of MDK, which is a base class for any MDK control. This approach provides more flexibility, as one can control the communication between the JavaScript/TypeScript extension and native control, use various APIs exposed in the control’s context, perform OData CRUD operations within the extension, add specific logic if needed etc. However, this also makes it comparatively complicated in implementation as compared to the first approach. This topic may be covered in a future blog, if there are enough requests for it.
  2. Non-Plugin approach – Directly adding a View Controller class in the Xcode project created by NativeScript. This approach, although works, is not recommended, since firstly, a NativeScript Xcode project is not permanent, but recreated every time you run “tns” command, as well as with new versions of NativeScript, the project structure may change, get recreated etc. Secondly, this approach will create a control that can only be used with one application, and is not reusable.

In this blog, we will take a look at how this can be achieved using the first method – creating a plugin by implementing MDKExtension.

There are 3 high level steps required in this approach:

  1. Create native control plugin
  2. Add it to MDK project as extension control and build client
  3. Update metadata to consume the extension control

The following sections detail each of these steps.

Create Native RFID Control Plugin in iOS

First, create a native swift framework that implements the RFID Scanner functionality. This framework will be wrapped into a NativeScript Plugin and added to MDK project to be consumed into the client build.

Create Swift control in XCode

Create a XCode framework project – RFIDScannerControl – that will implement the native control.

Add a class RFIDScannerViewController that extends UIViewController and implements MDKExtension protocol.

Implement the scanning functionality in this class. It may be required to add 3rd party scanning library for RFID scanner integration. Usually, the scanner hardware being used provides an iOS SDK (library) to be used with it. Alternatively, you can make use of iOS SDK’s Core NFC framework to read NFC tags. NFC is a subset of RFID technology.

Call SwiftExtensionDelegate’s setControlValue() to set the scan value in the extension control, which can later be read in a rule or metadata.

self.delegate?.setControlValue(value: result as NSString);​

Call SwiftExtensionDelegate’s executeActionOrRule() to call a metadata rule (that will be defined in later steps), which will read the control value set above and call the navigation action.

self.delegate?.executeActionOrRule(definitionPath: "...", callback: {...})​

Build the project. It should create RFIDScannerControl.framework.

Refer to Sample implementation below

//
//  RFIDScannerViewController.swift
//  RFIDScannerControl
//
//  Created by Mehta, Kunal on 12/2/18.
//  Copyright © 2018 Mehta, Kunal. All rights reserved.
//

import UIKit
import SAPMDC

@objc(RFIDScannerViewController)
open class RFIDScannerViewController: UIViewController, MDKExtension {
    public var delegate: SwiftExtensionDelegate?
    public var txtField: UITextField!
    public func update(with params: NSDictionary) {
        // do something with params
    }
    
    public func controlValueChanged(with binding: NSDictionary) {
        // do something with binding
    }
    
    override open func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        setupView()
        setupScanner()
        
    }
    
    public func setupView() {
        // set up view.
        self.view.autoresizingMask = [UIView.AutoresizingMask.flexibleHeight, UIView.AutoresizingMask.flexibleWidth]

        txtField = UITextField(frame: CGRect(x: 100, y: 100, width: 200, height: 30))
        txtField.placeholder = "Equipmemt Id"
        txtField.borderStyle = .roundedRect
        self.view.addSubview(txtField)

        let button = UIButton(frame: CGRect(x: 100, y: 150, width: 100, height: 50))
        button.backgroundColor = .green
        button.setTitle("Start Scan", for: .normal)
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)

        self.view.addSubview(button)
    }
    
    @objc func buttonAction(sender: UIButton!) {
        scan { (result: String) in
            let alertController = UIAlertController(title: "RFID Scanner", message: "\(result)", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), style: .default, handler: { _ in
                self.delegate?.setControlValue(value: result as NSString);
                self.delegate?.executeActionOrRule(definitionPath: "/SAPAssetManager/Rules/RFIDScannerRule.js", callback: { (ruleResult:String) in
                    // log if result is error
                })
            }))
            self.present(alertController, animated: true, completion: nil)
        }
    }
    
    public func setupScanner() {
        // initialize scanner as needed
    }
    
    public func scan(with callback: @escaping (String) -> Void ) {
        // add code to start rfid scanner
        // passthe callback for results
        
        // for purposes of demo, call the callback with hardcoded value
        callback(txtField.text ?? "10000000")
    }
}​

 

Create NativeScript {N} Plugin

The above framework must now be wrapped in a {N} plugin to make it reusable, as well as to integrate with the MDK client.

Create Plugin-RFIDScanner with following folder structure, and add the RFIDScannerControl.framework to Plugin-RFIDScanner/platforms/ios as illustrated.

If the RFID Scanner implementation requires a 3rd party library framework, then that framework should also be added under ios folder, and included in the build.xconfig file.

If any additional permissions are required for the scanner, they should be added to the info.plist optional file, as one would when building an actual application instead of plugin. During build process the info.plist will be merged with the app’s info.plist.

Plugin-RFIDScanner
    |- package.json
    |- platforms
          |-ios
              |- RFIDScannerControl.framework
              |- build.xcconfig – text file with following 2 lines:
                                  EMBEDDED_CONTENT_CONTAINS_SWIFT = Yes
                                  OTHER_LDFLAGS = $(inherited) -framework " RFIDScannerControl" 
              |-info.plist (optional)
​

Now that we have NativeScript plugin for RFID control, that is ready to be added to the application. The next section outlines the steps for creating a MDK client with the plugin.

Add to MDK project and Build SAM Client (IPA)

Setup build environment

Prepare environment to build SAM client as per Building SAP Asset Manager Client. The MDK create-client script uses a .mdkproject to build the client. The above instructions prepares the SAM.mdkproject.

Add to SAM.mdkproject

Copy the Plugin-RFIDScanner folder into SAM mdkproject as per the structure shown below:

SAM.mdkproject
   |- extensions
        |- extension-RFIDScanner
            |- plugin
                |- Plugin-RFIDScanner

 

Build Client

Finally, follow steps from Building SAP Asset Manager Client to create SAM client (IPA).

Once the client is build, sync the client with the backend. Since we haven’t yet added the control in metadata, you will not see it yet in the app. Next we will update SAM metadata to add this control.

SAM Metadata changes to Display RFID control

Now, we will update the SAM metadata to consume the RFID extension control we just built into the client. For this, we will add a new page that will contain the relevant metadata.

First, Import SAM metadata in WebIDE

 

In editor, create a new Extension control: Right click workspace->New->MDK Extension Control. Name it RFIDScannerControl, and set class name to RFIDScannerViewController, the swift class we created in XCode.

 

Create a new page – RFID Scanner page – and add a section table and add the new extension under it. Drag and drop the Extension Section as shown on the page. Set the class as “RFIDScannerViewController”

JSON would look like this.

{
	"Caption": "RFIDScanner",
	"Controls": [
		{
			"Sections": [
				{
					"Class": "RFIDScannerViewController",
					"Height": 500,
					"Module": "",
					"_Name": "SectionExtension0",
					"_Type": "Section.Type.Extension"
				}
			],
			"_Name": "SectionedTable0",
			"_Type": "Control.Type.SectionedTable"
		}
	],
	"_Name": "RFIDScanner",
	"_Type": "Page"
}

 

Add a Navigation action RFIDScannerNav.action to navigate to RFID Scanner page upon OnPress of the menu option

 

Next, open EquipmentListView.page and add a menu option for RFID Scanning. Drag and drop the action bar item as shown.

 

Under Rules, add a Rule RFIDScannerRule.js that gets called from the Swift class RFIDScannerViewController. This rule will read the control value (set in the swift view controller) and navigate to that equipment.

export default function RFIDScannerRule(context) {
    let scanvalue = '';
    let pageProxy = context.getPageProxy();
    const sectionedTable = pageProxy.getControl('SectionedTable0');
    const sections = sectionedTable.getSections();

    // get extension control from section
    sections.forEach(element => {
        if (element.getName() === 'RFIDScannerExtensionControl') {
            const extensions = element.getExtensions();
            extensions.forEach(extension => {
                scanvalue = extension.getValue();
            });
        }
    });

    //Rebind the necessary equipment data selected from the list
    return context.read('/SAPAssetManager/Services/AssetManager.service', 
                        "MyEquipments", [], 
                        "$filter=EquipId eq '" + scanvalue + "'").then(Equipment => {
        pageProxy.setActionBinding(Equipment.getItem(0));
        return context.executeAction('/SAPAssetManager/Actions/Equipment/EquipmentDetailsNav.action');
    }, error => {
        // Log error
    });
}

 

Thats it!

Build and Deploy to Mobile Services by right-clicking the project and selecting “MDK Deploy and Activate”.

Relaunch the Asset Manager app on device, it will detect a new metadata version and prompt for an update. Select OK to switch to the new metadata.

Now, test out the control from Equipment list page -> RFID menu

SAM App screenshots with the RFID control:

 

Additional Tips

The swift controller code currently makes some assumptions about its layout and rule to be called after scanning. This makes the control less reusable. You can make the control truly generic by passing these “configuration” properties from metadata using the “Extension Properties” section of the extension control schema.

{
	"Caption": "RFIDScanner",
	"Controls": [
		{
			"Sections": [
				{
					...
                    "ExtensionProperties": {
                        "button_label": "Start Scan",
                        "default_equipId": "10000000"
                    }
				}
			],
			...
		}
	],
	...
}

These properties are passed to the swift control as a dictionary in the MDKExtension protocol’s update() method, from which the values can be read as shown below

    var extensionProperties: NSDictionary?

    public func update(with params: NSDictionary) {
        extProperties = params
    }

    public func setupView() {
        // set up view.
        ...
        
        button.setTitle(extensionProperties?.value(forKey: "button_label") as! NSString, for: .normal)
        ...
    }

    public func scan(with callback: @escaping (String) -> Void ) {
        ...
        callback(txtField.text ?? extensionProperties?.value(forKey: "default_equipId") as! NSString)
    }

Resources that might be useful

  1. MDK Developer Guide
  2. SAP Asset Manager for iOS
  3. The Learning Journey – MDK
  4. Build Extension Controls in Mobile Development Kit 2.0 SP1 (Blog)
  5. NativeScript

 

To sum up…

SAP Asset Manager is a metadata based app that can be further enhanced or customized to provide great user experience and add new functionality that hasn’t been thought of. MDK provides the framework to do just that.

Go ahead and create your own extensions. Share with us your experiences and how its has made SAP Asset Manager even better.

If you enjoyed the post, please like it and provide feedback and/or suggestions. I’d be grateful if you share it with your friends, colleagues or anyone else via one of the options at the top.

 

3 Comments
You must be Logged on to comment or reply to a post.