Skip to content

Extending the React Native Module: Exposing New Pointr SDK Methods (Static Wayfinding Example)

Our React Native plugin is designed to cover most use cases out of the box. In most scenarios, you won’t need to extend the functionality. However, if you require additional customization or functionality that isn’t supported by the default plugin, this guide will walk you through how to extend the module and implement your own functionality.

In this guide, we will demonstrate how to expose a method from the Pointr SDK through the React Native module. We will add showPath method to PTRNativeLibrary to display a static path between two pois.

Prerequisites

To achieve this, you will need the site identifier where the points of interest (POIs) are located (siteExternalIdentifier), as well as the identifiers for both the source and destination POIs. In this example, external identifiers are utilized. To use these external identifiers, ensure that the map content is configured with external identifiers through the Pointr Cloud Dashboard.

Native implementation

Pointr React Native module is created by following the official guideline on React Native developer site for native module implementation. It is recommended to go through the tutorial before reading this guide.

Native module is implemented under PTRNativeLibrary.(m/h) files. Pointr/PointrApp/PointrApp.swift is the helper file for implementation.

animationType parameter defines how to display the site. For more information about animation types, please refer to the React Native API reference. Parameter callback is going to let Typescript side to be informed about the errors; i.e whether the operation is successful or not; and if not, the description for reason of failure.

RCT_EXPORT_METHOD(showPath:(NSString *)siteExternalIdentifier sourcePoiExternalIdentifier: (NSString *)sourcePoiExternalIdentifier destinationPoiExternalIdentifier: (NSString*)destinationPoiExternalIdentifier animationType:(int)animationType callback:(RCTResponseSenderBlock)callback) {
      [PointrApp.sharedApp showPath:siteExternalIdentifier sourcePoiExternalIdentifier:sourcePoiExternalIdentifier destinationPoiExternalIdentifier:destinationPoiExternalIdentifier  animationType:animationType completion:^(NSString *error) {
        callback(@[error]);
      }];
    }

Now we need to add the corresponding function to PointrApp class. For usage of Pointr iOS SDK check the API reference.

       func showPath(siteExternalIdentifier id: String, sourcePoiExternalIdentifier: String, destinationPoiExternalIdentifier: String, animationType:Int, completion: @escaping (String?) -> Void) {
            // All operations on PTRMapWidgetViewController must be done after Pointr instance is initialized. 
            // This internal method is called only after Pointr instance starts running.
            func showPathWithCompletion(completion: @escaping (String?) -> Void) {
                self.initializeMapWidget()
                self.viewController!.showSite(siteExternalIdentifier: id, animationType: self.getAnimationTypeFromInt(value: animationType)) { error in
                    guard error != nil else {
                        completion(self.getErrorStringFromMapWidgetError(error: error))
                        return
                    }
                    guard let site = Pointr.shared.siteManager?.site(withExternalIdentifier: id) else {
                        completion("Site \(id) not configured")
                        return
                    }
                    guard let sourcePoi = Pointr.shared.poiManager?.pois(for: site, withExternalIdentifier: sourcePoiExternalIdentifier) else {
                        completion("Poi \(sourcePoiExternalIdentifier) not configured")
                        return
                    }
                    guard let destinationPoi = Pointr.shared.poiManager?.pois(for: site, withExternalIdentifier: destinationPoiExternalIdentifier) else {
                        completion("Poi \(destinationPoiExternalIdentifier) not configured")
                        return
                    }
                    guard let path = Pointr.shared.pathManager?.calculatePath(fromLocation: sourcePoi, toNearestLocationIn: [destinationPoi]) else {
                        completion("Unable to calculate path")
                        return
                    }
                    // show path on the mapp
                    self.viewController?.mapViewController.currentPath = path
                    // focus to the source poi
                    self.viewController?.mapViewController.focusPoi(poi: sourcePoi, shouldZoom: true)
                }
                self.presentMapWidgetIfNotPresented()
            }
            if Pointr.shared.state != .running {
                start() { state in
                    if state == "running" {
                        showPathWithCompletion(completion: completion)
                    } else {
                        completion("starting Pointr resulted in \(state) state")
                    }
                }
            } else {
                showPathWithCompletion(completion: completion)
            }
        }

Now new method should be accessible from Typescript. We can test this by adding a button and when the button is pressed, we can make a call to the new method in App.js.

    <Button
      title="Show Path from POI to POI"
      onPress={() => PTRNativeLibrary.showPath("officebuilding", "Lobby", "Lobby2", 1, error => {
          console.log(error);
        })
      }
    />

Pointr React Native module is created by following the official guideline on React Native developer site for native module implementation. It is recommended to go through the tutorial before reading this guide.

Native module is implemented under PointrLibraryModule.java file. PointrLibraryModule.kt is the helper file that contains static methods.

animationType parameter defines how to display the site. For more information about animation types, please refer to the React Native API reference. Parameter callback is going to let Typescript side to be informed about the errors; i.e whether the operation is successful or not; and if not, the description for reason of failure.

Define the new method that is annotated as ReactMethod in module PointrLibraryModule.java.

        @ReactMethod
        public void showPath(String siteExternalIdentifier, String sourcePoiExternalIdentifier, String destinationPoiExternalIdentifier, int animationType, Callback callback) {
            Activity currentActivity = getCurrentActivity();
            if (currentActivity == null) {
                callback.invoke("Activity cannot be null");
                return;
            }
            PointrLibraryModuleKt.showPath(siteExternalIdentifier, sourcePoiExternalIdentifier, destinationPoiExternalIdentifier, animationType, currentActivity, callback);
        }

Define the corresponding method in PointrLibraryModule.kt.

    fun showPath(
        siteExternalIdentifier: String, 
        sourcePoiExternalIdentifier: String, 
        destinationPoiExternalIdentifier: String, 
        animationType: Int, 
        activityContext: Activity,
        callback: Callback
    ) {
        val intent = Intent(activityContext, PointrMapWidgetActivity::class.java)
        intent.action = PointrMapWidgetActivity.SHOW_PATH
        intent.putExtra(PointrMapWidgetActivity.siteExternalIdentifierKey, siteExternalIdentifier)
        intent.putExtra(PointrMapWidgetActivity.sourcePoiExternalIdentifierKey, sourcePoiExternalIdentifier)
        intent.putExtra(PointrMapWidgetActivity.destinationPoiExternalIdentifierKey, destinationPoiExternalIdentifier)
        intent.putExtra(PointrMapWidgetActivity.animationTypeKey, animationType)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        PointrMapWidgetActivity.callback = callback
        activityContext.startActivity(intent)
    }

This code creates a new activity (PointrMapWidgetActivity) that has a place holder for PTRMapWidgetFragment. It is important to have the new action (SHOW_PATH) under PointrMapWidgetActivity. Required information is passed to the activity with the intent.

We need to add the action to take inside PointrMapWidgetActivity. Define the constants first:

    class PointrMapWidgetActivity : AppCompatActivity() {
        companion object {
            const val SHOW_PATH = "showPath"
            ...
            const val animationTypeKey = "animationType"
            const val siteExternalIdentifierKey = "siteExternalId"
            const val sourcePoiExternalIdentifierKey = "sourcePoiExternalId"
            const val destinationPoiExternalIdentifierKey = "destinationPoiExternalId"
            ...
            var callback: Callback? = null
        }
        ...
    }

Define the action under private method PointrMapWidgetActivity.showMapWidgetBasedOnAction.

        private fun showMapWidgetBasedOnAction() {
            val mapWidgetConfig = PTRMapWidgetConfiguration.defaultConfiguration()
            val ptrMapWidgetFragment =
                PTRMapWidgetFragment.newInstance(configuration = mapWidgetConfig).show(
                    supportFragmentManager,
                    R.id.fragment_container
                )
            when (intent.action) {
                SHOW_PATH -> {
                    val siteExternalIdentifier =
                        intent.getStringExtra(siteExternalIdentifierKey) ?: run {
                            val error = "Site External Id not provided"
                            Plog.e(error)
                            callback?.invoke(error)
                            callback = null
                            return
                        }
                    val sourcePoiExternalIdentifier = intent.getStringExtra(
                        sourcePoiExternalIdentifierKey
                    ) ?: run {
                        val error = "Source Poi External Id not provided"
                        Plog.e(error)
                        callback?.invoke(error)
                        callback = null
                        return
                    }
                    val destinationPoiExternalIdentifier = intent.getStringExtra(
                        destinationPoiExternalIdentifierKey
                    ) ?: run {
                        val error = "Destination Poi External Id not provided"
                        Plog.e(error)
                        callback?.invoke(error)
                        callback = null
                        return
                    }
                    val animationTypeInt = intent.getIntExtra(animationTypeKey, 0)
                    val animationType = try {
                        PTRMapAnimationType.values()[animationTypeInt]
                    } catch (ex: IllegalArgumentException) {
                        PTRMapAnimationType.flyOver
                    }
                    ptrMapWidgetFragment.showSite(
                        siteExternalIdentifier = siteExternalIdentifier,
                        animationType = animationType
                    ) { error ->
                        error?.let {
                            callback?.invoke(it)
                            callback = null
                            return@showSite
                        }
                        val site =
                            pointr?.siteManager?.getSite(siteExternalIdentifier) ?: return@showSite
                        val sourcePoi = pointr?.poiManager?.getPoiByExternalIdentifier(
                            site,
                            sourcePoiExternalIdentifier
                        ) ?: run {
                            callback?.invoke("Poi $sourcePoiExternalIdentifier not configured")
                            callback = null
                            return@showSite
                        }
                        val destinationPoi = pointr?.poiManager?.getPoiByExternalIdentifier(
                            site,
                            destinationPoiExternalIdentifier
                        ) ?: run {
                            callback?.invoke("Poi $destinationPoiExternalIdentifier not configured")
                            callback = null
                            return@showSite
                        }
                        val path = pointr?.pathManager?.calculatePath(sourcePoi, listOf(destinationPoi))
                            ?: run {
                                callback?.invoke("Unable to calculate path")
                                callback = null
                                return@showSite
                            }
                        //
                        ptrMapWidgetFragment.mapFragment?.currentPath = path
                        ptrMapWidgetFragment.mapFragment?.scrollToLocation(
                            GeoPoint(
                                sourcePoi.latitude,
                                sourcePoi.longitude
                            ), zoomLevel = ptrMapWidgetFragment.mapFragment?.defaultLocationZoomLevel,
                        ) { error1 ->
                            error1?.let {
                                callback?.invoke("Unable to focus to source poi")
                                callback = null
                                return@scrollToLocation
                            }
                            callback = null
                        }
                    }
                }
                ...
            }
            ...
        }

Usage

Now new method should be accessible from Typescript. As an example, we can test this by adding a button and when the button is pressed, we can make a call to the new method in App.js.

    <Button
        title="Show Path from POI to POI"
        onPress={() => PTRNativeLibrary.showPath("officebuilding", "Lobby", "Lobby2", 1, error => {
            console.log(error);
        })
        }
    />

Last update: October 2, 2024
Back to top