Skip to Content
Welcome to this step-by-step, end-to-end guide to building a barcode scanner hybrid Kapsel app for Android!
What sets this blog apart from other similar blogs [1][2]? Here is a set of challenges we will tackle here:

  • SAP AS 4.7 back-end system (SAP 7.4 gateway system)
  • The current barcode scanner plug-in (phonegap-plugin-barcodescanner 6.0.1) does not return a barcode if the app is restarted after scanning
  • SAP Fiori Launchpad does not provide a web app manifest for the Android Home screen

The presented app is not a reference. Its purpose is to demonstrate key solutions involved in providing the required functionality.
This blog was made possible by Bertelsmann Media Sp. z o.o. Oddział arvato Polska. Special thanks to Piotr Jakubowski, Andrzej Nowak and Krystian Brydzki for supporting the creation of this blog.

Overview

App specification

  • Demonstrate that it is possible to couple modern SAP UX technologies to an old 4.7 SAP application server, using an SAP NetWeaver 7.4 gateway system
  • Using the camera of a mobile Android device, scan a bar code and show the corresponding material from a 4.7 SAP application server
  • Start the material scanning app from a Fiori Launchpad tile
  • Access the Fiori Launchpad as an Android web app

Design considerations

The SAP Fiori Client app [SFC] is available for Android. It has built-in scanner support, which allows a pure SAPUI5 app to perform scanner operations, as long as it runs in the Fiori Client. Therefore a hybrid app may not be necessary to perform the required scanning task. However, at the time of writing, the Fiori Client has a certain drawback that discourages its use:

  • The Android operating system may kill the Fiori Client any time it is in the background. This can happen while scanning, taking a photo or switching over to another app. When Android restarts the Fiori Client, the app returns to the initial screen of the Launchpad instead of the last screen it displayed. This is:
    • inconvenient for the user
    • makes it difficult to handle Android ‘resume’ events, as the ‘resume’ handler would have to be registered from the Launchpad page, as opposed to the SAPUI5 app that was ‘pause’d
  • It is true that the Android OS will not always destroy an activity that is in the background. However, in our tests this occurred frequently enough to make us decide that correct handling of Android lifecycle events is mandatory:
    • Upon app resume, after the reconstruction of the activity, the last screen the user saw must be restored
    • Handling of plugin data returned on ‘resume’ after the reconstruction of the activity must be possible from the app that called the plugin (and got ‘pause’d)

We decided not to attempt to fix the Fiori Client app. Instead, fortunately, the Chrome browser offered a way forward:

  • Chrome can be used to open the Fiori Launchpad
  • Chrome returns to the last view (e.g. of a SAPUI5 app) the user saw before the activity was destroyed
  • Chrome allows the Fiori Launchpad to be added to the Home screen. Given the right web app manifest ‘manifest.json’ file, the Launchpad is then opened full screen, like a native app.

However Chrome is not equipped with a scanner plugin.

  • Fortunately, it is possible to start a native app from Chrome, using an ‘intent’ [INT]. This allows for a Fiori Launchpad tile that opens a hybrid Kapsel scanner app that performs the scanning.

Tool set

Install and configure your tool set according to the OpenSAP ‘mobile2’ course document ‘openSAP_mobile2_Week_0_Unit_4_SYDE2_Guide.pdf’ [M2W0U4].

App development

  1. Material OData service (blog post part 1)
  2. Barcode scanner Kapsel hybrid app (blog post part 2)

Table of contents

Creating the material OData service

The SAP system that stores our material data is called ‘PL6’, an old 4.7 application server. We will use ‘EL6’, a 7.4 gateway system, to provide OData access to the data on PL6.

  1. Set up a trusted RFC ‘PL6_TRUSTED’ between EL6 and PL6, PL6 being the trusting system (note 128447)
    1. Assign yourself and your test user ‘FIORDEMO’ a role with an appropriate instance of the authorization object S_RFCACL (RFC_EQUSER = ‘Y’)
  2. Use SEGW transaction on EL6 to create a new OData service ‘ZTUT00_MAT_00’ in package ZTUT00
    1. Create a new package for the tutorial, e.g. ‘ZTUT00’
  3. Create one entity type for the material collection: right click ‘Data Model’, choose ‘Import’ / ‘RFC/BOR Interface’
    1. Entity type name: ‘Material’
    2. Data Source Attributes: ‘Remote Function Call’
    3. Name: BAPI_MATERIAL_GET_DETAIL
    4. Check ‘Create Default Entity Set’
    5. Next
    6. Select parameters
      1. Check: MATERIAL
      2. Expand MATERIAL_GENERAL_DATA
        1. Check: MATL_DESC, GROSS_WT, NET_WEIGHT, UNIT_OF_WT, VOLUME, VOLUMEUNIT
      3. Next
    7. Mark ‘MATERIAL’ as ‘Is Key’
    8. Finish
    9. Double click ‘Data Model / Entity Types / Material / Properties’
      1. Rename ‘Material’ in column ‘Name’ to ‘MatNr’
      2. Mark ‘MatNr’ as ‘Sortable’ and ‘Filterable’
    10. Note that an ‘Entity Set’, ‘MaterialSet’ has been created automatically
    11. Mark the ‘Entity Sets / MaterialSet’ as ‘Pageable’ and ‘Addressable’

Implementing the OData service

The RFC function module (FM) ‘BAPI_MATERIAL_GETLIST’ returns a list of material numbers based on a given set of selection criteria. It supports a MAXROWS import parameter. Unfortunately, at least in version ‘26.01.2005 620 26.01.2005 02:23:28 SAP’, the FM does not return the top MAXROWS materials as ordered by MATNR, even though records in the MATNRLIST result table are ordered by MATNR. Because of this, we can not use the MAXROWS import parameter of BAPI_MATERIAL_GETLIST as a ‘Max Hits’ parameter.

BAPI_MATERIAL_GETLIST does not return the same set of material fields that were chosen from BAPI_MATERIAL_GET_DETAIL above [OBP]. Because of this, it can not be used directly to implement ‘GetEntitySet (Query)’. A new FM, Z_TUT00_MAT_00_BAPI_MATERIAL00, will be created for this purpose both on PL6 and EL6.

Creating a helper function module

  1. Make a copy of structure BAPIMATLST to ZTUT00_MAT_00_BAPIMATLST in package ZTUT00
    1. Append new fields GROSS_WT, NET_WEIGHT, UNIT_OF_WT, VOLUME, VOLUMEUNIT after the last field, copying from structure BAPIMATDOA
    2. Save and activate
  2. Make a copy of BAPI_MATERIAL_GETLIST as Z_TUT00_MAT_00_BAPI_MATERIAL00 in a new function group, say ZTUT00
    1. Change Z_TUT00_MAT_00_BAPI_MATERIAL00 table MATNRLIST type to ZTUT00_MAT_00_BAPIMATLST
    2. Replace the source of Z_TUT00_MAT_00_BAPI_MATERIAL00:

        call function ‘BAPI_MATERIAL_GETLIST’
      exporting
      maxrows                      = maxrows
      tables
      matnrselection               = matnrselection
      materialshortdescsel         = materialshortdescsel
      manufacturerpartnumb         = manufacturerpartnumb
      plantselection               = plantselection
      storagelocationselect        = storagelocationselect
      salesorganisationselection   = salesorganisationselection
      distributionchannelselection = distributionchannelselection
      matnrlist                    = matnrlist
      return                       = return.  if return is initial.

      ” Call BAPI_MATERIAL_GET_DETAIL to fill in missing matnrlist fields, overwrite MATL_DESC
      field-symbols: <matnrlistitem> like line of matnrlist.
      loop at matnrlist assigning <matnrlistitem>.

      data: return1 type bapireturn.
      data: material_general_data type bapimatdoa.
      clear: return1, material_general_data.

      call function ‘BAPI_MATERIAL_GET_DETAIL’
      exporting
      material              = <matnrlistitem>-material
      importing
      material_general_data = material_general_data
      return                = return1.

      if return1-type eq ‘S’.
      <matnrlistitem>-matl_desc = material_general_data-matl_desc.
      <matnrlistitem>-gross_wt = material_general_data-gross_wt.
      <matnrlistitem>-net_weight = material_general_data-net_weight.
      <matnrlistitem>-unit_of_wt = material_general_data-unit_of_wt.
      <matnrlistitem>-volume = material_general_data-volume.
      <matnrlistitem>-volumeunit = material_general_data-volumeunit.
      endif.
      endloop.
      endif.

    3. On EL6 the source of the FM can be left empty. Only the interface needs to be defined.
    4. Save and activate

Providing the implementation for the OData service

  1. Open SEGW ‘Service Implementation / MaterialSet’
    1. Operation ‘GetEntitySet (Query)’
      1. Right click ‘GetEntitySet (Query)’, choose ‘Map to Data Source’
      2. Target System = ‘Remote’, ‘RFC Destination’ = ‘PL6_TRUSTED’
      3. Type ‘Remote Function Call’, name Z_TUT00_MAT_00_BAPI_MATERIAL00, OK
      4. Drag MATNRSELECTION[] from the ‘Data Source Parameter’ area to ‘Data Source Parameter’ column of row ‘MatNr’
      5. Ok the proposed range semantics
      6. Create a new row under ‘Volumeunit’
        • ‘Entity Set property’ ‘MatNr’
      7. Expand MATNRLIST[] in the ‘Data Source Parameter’ area
        1. Drag MATL_DESC to ‘Data Source Parameter’ column of row ‘MatlDesc’
        2. Drag MATERIAL to ‘Data Source Parameter’ column of new row ‘MatNr’
          • Note how this property is an ‘Output’
        3. Drag GROSS_WT, NET_WEIGHT, UNIT_OF_WT, VOLUME, VOLUMEUNIT as well
      8. Create two new rows under ‘MatNr’ (output) for two more import parameters of Z_TUT00_MAT_00_BAPI_MATERIAL00Fig 1.png
        1. Leave ‘Entity Set property’ empty
        2. Drag PLANTSELECTION[] to the 1st new row
          1. Change all 4 ‘Semantics’ to ‘Constant’, set appropriate constants, e.g.:
            • OPTION = ‘EQ’, PLANT_HIGH = ”, PLANT_LOW = ‘1000’, SIGN = ‘I’
        3. Drag STORAGELOCATIONSELECT[] to the 2nd new row
          1. Change all 4 ‘Semantics’ to ‘Constant’, set appropriate constants, e.g.:
            • OPTION = ‘CP’, STLOC_HIGH = ”, STLOC_LOW = ‘W*’, SIGN = ‘I’
          2. Note how a second storage location, ‘P*’, can not be added this way. We will add this range definition later.
        4. Remove all unmapped ‘Entity Set properties’
      9. Unset ‘Set/Unset Max Hits’ for the MAXROWS import parameter of Z_TUT00_MAT_00_BAPI_MATERIAL00:
        fig 2.png
    2. Operation ‘GetEntity (Read)’
      1. Proceed as above
      2. RFC name ‘BAPI_MATERIAL_GET_DETAIL’
      3. Click ‘Propose Mapping’
      4. Generate runtime objects (Ctrl+F3)
      5. Make sure that all generation messages are green

Registering the OData service

  1. Right click ‘Service Maintenance / GATEWAYLOCAL’, choose ‘Register’, package ‘ZTUT00’. System alias is ‘NONE’ or ‘LOCAL’ in the case of an embedded gateway deployment.
  2. Accept everything on the screen, ‘Continue’. The service ‘Registration Status’ should now be green. The new service is called ‘ZTUT00_MAT_00_SRV’.

Setting the RFC destination for the OData service

Set PL6_TRUSTED as the destination for the new service ZTUT00_MAT_00_SRV:

  1. The RFC destination set in SEGW is only for design time. An RFC destination must be set for the registered gateway service.
  2. Open transaction SE84, ‘Enhancements / Enhancement Spots / Enhancement Spot /IWBEP/ES_DESTIN_FINDER’, double click to display the enhancement spot
  3. Create a BAdI implementation providing only the following code for the given class methods:
    1. /IWBEP/IF_DESTIN_FINDER_BADI~GET_DB_CONNECTION

      rv_db_connection = is_default_system_alias_infohana_db_connection.

    2. /IWBEP/IF_DESTIN_FINDER_BADI~GET_RFC_DESTINATION

      ev_rfc_destination = ‘PL6_TRUSTED’.

  4. Set the following filter for the BAdI implementation:
    • ‘ZTUT00_MAT_00_SRV’ = SERVICE_TECHNICAL_NAME
  5. Activate the BAdI

Testing the OData service

  1. In SEGW, click ‘Service Maintenance / GATEWAYLOCAL / SAP Gateway Client’ button
  2. Accept ‘Request URI’ ‘/sap/opu/odata/SAP/ZTUT00_MAT_00_SRV/?$format=xml’, execute query, make sure the status is ‘200 OK’
  3. Test that the following queries are successful, appending the given path to ‘/sap/opu/odata/SAP/ZTUT00_MAT_00_SRV’:
    1. Queries exercising Z_TUT00_MAT_00_BAPI_MATERIAL00 (GetEntitySet (Query)):
      1. /MaterialSet
        • Note how the call is successful, but the list is empty. This is because Z_TUT00_MAT_00_BAPI_MATERIAL00 does not return any records in case the MATNRSELECTION range is empty. We will fix this shortly.
      2. /MaterialSet?$filter=startswith(MatNr, ‘W0110’)&$format=json
        • The list is now populated with records, since we have given a MATNRSELECTION filter
      3. /MaterialSet/$count?$filter=startswith(MatNr, ‘W0110’)
        • Note the number of records
      4. /MaterialSet?$filter=startswith(MatNr, ‘W0110’)&$format=json&$skip=0&$top=3
    2. Query exercising BAPI_MATERIAL_GET_DETAIL (GetEntity (Read)):
      1. /MaterialSet(‘W011001’)?$format=json
        • Place an external breakpoint into the function module BAPI_MATERIAL_GET_DETAIL on PL6 for the user you operate the gateway client as. The break point should trigger when you execute the query on EL6. The user on PL6 – sy-uname – should be the same as the one on EL6, thanks to the trusted RFC connection PL6_TRUSTED.

Making /MaterialSet return the whole list

  1. Return to ZTUT00_MAT_00 in SEGW
  2. Expand ‘Runtime Artifacts’, right click ‘ZCL_ZTUT_MAT_00_DPC_EXT’ (the data provider class), choose ‘Go to ABAP Workbench’
  3. Expand ‘Methods / Inherited Methods’, right click ‘MATERIALSET_GET_ENTITYSET’, click ‘Redefine’. MATERIALSET_GET_ENTITYSET is the method that generates the OData response for a query to the entity set /MaterialSet.
  4. Additional code has to be injected into the middle of the default implementation of MATERIALSET_GET_ENTITYSET. In order to do this, first copy the default implementation from ZCL_ZTUT00_MAT_00_DPC to ZCL_ZTUT_MAT_00_DPC_EXT.
  5. Add the following lines to ZCL_ZTUT_MAT_00_DPC_EXT::MATERIALSET_GET_ENTITYSET, after the end of the loop marked as ‘* Maps filter table lines to function module parameters’:

    ” CUSTOM CODE HERE {
    IF matnrselection IS INITIAL.
    matnrselection = VALUE #( ( sign = ‘I’ option = ‘CP’ matnr_low = ‘*’ matnr_high = ” ) ). ” Select everything if range is INITIAL
    ENDIF.
    ” CUSTOM CODE HERE }

  6. Activate the class (Ctrl + F3)
  7. Test /MaterialSet, /MaterialSet/$count, /MaterialSet?$skip=0&$top=3&$format=json
    • The list is no longer empty

Adding another constant storage location filter

Add another constant storage location filter in addition to ‘W*’:

  1. Edit ZCL_ZTUT_MAT_00_DPC_EXT::MATERIALSET_GET_ENTITYSET
  2. Find where the constant ‘W*’ range is appended to ‘storagelocationselect’
  3. Add the following code after the APPEND:

    ” CUSTOM CODE HERE {
    ” storagelocationselect
    storagelocationselect = VALUE #(
    ( sign = ‘I’ option = ‘CP’ stloc_low = ‘W*’ stloc_high = ” )
    ( sign = ‘I’ option = ‘CP’ stloc_low = ‘P*’ stloc_high = ” ) ).
    ” CUSTOM CODE HERE }

  4. Activate the class
  5. Test /MaterialSet/$count

The OData service is now ready.

Further reading

Creating the scanner Kapsel hybrid app (blog post part 2)

Part 2 of this blog covers the steps to create the scanner Kapsel hybrid app.

Afterword to this blog post

Thank you for reading this blog post. I hope you found it useful.

To report this post you need to login first.

Be the first to leave a comment

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

Leave a Reply