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);
})
}
/>