Show a Callout on the Map with an Activity
Some activities may need access to the map embedded in the VertiGIS Studio Mobile Application. The MapProviderBase
class can be injected in an activity and used to access the Map and MapView for the application.
This article will walk you through accessing the map from an activity and displaying a callout at the map center point.
Prerequisites
Extending VertiGIS Studio Workflow for Mobile requires development and deployment of a custom VertiGIS Studio Mobile Application using the VertiGIS Studio Mobile SDK
Follow the instructions in the VertiGIS Studio Mobile SDK page to set up the environment for extending Workflow for VertiGIS Studio Mobile.
A working knowledge of C# and .NET Standard is recommended before extending Workflow for VertiGIS Studio Mobile
Set up the Activity
First, the basic activity needs to be setup and registered. Below is an example of an activity setup to take text to display in the map callout.
- Activity
- Registration
using App1.Workflow;
using VertiGIS.Mobile.Composition;
using VertiGIS.Workflow.GIS.ArcGISRuntime;
using VertiGIS.Workflow.Runtime;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
[assembly: Export(typeof(PlaceCalloutAtCenter))]
namespace App1.Workflow
{
public class PlaceCalloutAtCenter : IActivityHandler
{
public static string Action { get; } = "uuid:<uuid>::PlaceCalloutAtCenter";
public async Task<IDictionary<string, object?>> Execute(IDictionary<string, object?> inputs, IActivityContext context)
{
var calloutText = (string)inputs["calloutText"];
return new Dictionary<string, object?>();
}
}
}
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using App1.Workflow;
using VertiGIS.Mobile.Composition;
using VertiGIS.Workflow.Runtime;
using VertiGIS.Workflow.Runtime.Definition;
using VertiGIS.Workflow.Runtime.Execution;
[assembly: Export(typeof(ActivityLoader), SingleInstance = true, AsImplementedInterfaces = true)]
namespace App1.Workflow
{
public class ActivityLoader : IActivityHandlerFactory
{
/// <summary>
/// Gets a mapping of action names to implementations of <see cref="IActivityHandler"/>s.
/// </summary>
private Dictionary<string, Func<IActivityHandler>> RegisteredActivities { get; } = new Dictionary<string, Func<IActivityHandler>>();
public ActivityLoader(Func<PlaceCalloutAtCenter> placeCalloutAtCenterFactory)
{
RegisteredActivities[PlaceCalloutAtCenter.Action] = placeCalloutAtCenterFactory;
}
/// <summary>
/// Creates an <see cref="IActivityHandler"/>.
/// </summary>
/// <param name="action">The name of the action to create.</param>
/// <param name="token">The cancellation token.</param>
/// <param name="inspector">The <see cref="ProgramInspector"/> for the program.</param>
/// <returns>The activity handler for the given action.</returns>
public Task<IActivityHandler> Create(string action, CancellationToken token, ProgramInspector inspector = null)
{
if (action == null || token.IsCancellationRequested)
{
return Task.FromResult<IActivityHandler>(null);
}
if (RegisteredActivities.TryGetValue(action, out Func<IActivityHandler> handlerType))
{
return Task.FromResult(handlerType());
}
else
{
return Task.FromResult<IActivityHandler>(null);
}
}
}
}
Access the MapProviderBase
Accessing the map for the application uses the built-in dependency injection pattern to inject a MapProviderBase
class which exposes the Map and MapView.
To inject the MapProviderBase, add a new Autofac factory property that injects a MapProviderBase
into your custom activity.
using App1.Workflow;
using VertiGIS.Mobile.Composition;
using VertiGIS.Workflow.GIS.ArcGISRuntime;
using VertiGIS.Workflow.Runtime;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
[assembly: Export(typeof(PlaceCalloutAtCenter))]
namespace App1.Workflow
{
public class PlaceCalloutAtCenter : IActivityHandler
{
public static string Action { get; } = "uuid:<uuid>::PlaceCalloutAtCenter";
[ProviderFactoryAttribute(typeof(MapProviderBase))]
public Func<MapProviderBase> MapProviderFactory { get; set; }
public async Task<IDictionary<string, object?>> Execute(IDictionary<string, object?> inputs, IActivityContext context)
{
var calloutText = (string)inputs["calloutText"];
return new Dictionary<string, object?>();
}
}
}
Load the MapView
Next, we need to load the MapView
from the MapProviderFactory
.
using App1.Workflow;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Xamarin.Forms;
using VertiGIS.Mobile.Composition;
using VertiGIS.Workflow.GIS.ArcGISRuntime;
using VertiGIS.Workflow.Runtime;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VertiGIS.Mobile.Infrastructure.Workflow;
[assembly: Export(typeof(PlaceCalloutAtCenter))]
namespace App1.Workflow
{
public class PlaceCalloutAtCenter : IActivityHandler
{
public static string Action { get; } = "uuid:<uuid>::PlaceCalloutAtCenter";
[ProviderFactoryAttribute(typeof(MapProviderBase))]
public Func<MapProviderBase> MapProviderFactory { get; set; }
public async Task<IDictionary<string, object?>> Execute(IDictionary<string, object?> inputs, IActivityContext context)
{
var calloutText = (string)inputs["calloutText"];
// Get an instance of the MapProvider
var mapProvider = MapProviderFactory();
// Ensure the map has loaded
await mapProvider.Load();
// ArcGIS runtime Map
Map map = mapProvider.Map;
// ArcGIS runtime MapView
MapView mapView = (MapView)((WorkflowMapProvider)mapProvider).View;
return new Dictionary<string, object?>();
}
}
}
Add a Callout at the Map Center
Finally, we can use the MapView
to get the map center and add a callout with the user's text.
Operations related to UI activities have to run in Xamarin's main thread, else they will throw an error.
using App1.Workflow;
using VertiGIS.Mobile.Composition;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Xamarin.Forms;
using VertiGIS.Workflow.GIS.ArcGISRuntime;
using VertiGIS.Workflow.Runtime;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VertiGIS.Mobile.Infrastructure.Workflow;
[assembly: Export(typeof(PlaceCalloutAtCenter))]
namespace App1.Workflow
{
public class PlaceCalloutAtCenter : IActivityHandler
{
public static string Action { get; } = "uuid:<uuid>::PlaceCalloutAtCenter";
[ProviderFactoryAttribute(typeof(MapProviderBase))]
public Func<MapProviderBase> MapProviderFactory { get; set; }
public async Task<IDictionary<string, object?>> Execute(IDictionary<string, object?> inputs, IActivityContext context)
{
var calloutText = (string)inputs["calloutText"];
// Get an instance of the MapProvider
dynamic mapProvider = MapProviderFactory();
// Ensure the map has loaded
await mapProvider.Load();
// ArcGIS runtime Map
Map map = mapProvider.Map;
// ArcGIS runtime MapView
MapView mapView = (MapView)((WorkflowMapProvider)mapProvider).View;
Xamarin.Essentials.MainThread.BeginInvokeOnMainThread(() =>
{
var currentEnvelope = (Envelope)mapView.GetCurrentViewpoint(Esri.ArcGISRuntime.Mapping.ViewpointType.BoundingGeometry).TargetGeometry;
mapView.ShowCalloutAt(currentEnvelope.GetCenter(), new CalloutDefinition("Hey!", $"Listen! {calloutText}"));
});
return new Dictionary<string, object?>();
}
}
}
Test your Activity
Now you can build a workflow for VertiGIS Studio Mobile that uses your new activity!
If you want your custom activity to show up with a friendly user interface in VertiGIS Studio Workflow Designer, check out Registering .NET Activities with VertiGIS Studio Workflow Designer.
The RunActivity
activity can be used to execute your activity by the name defined in PlaceCalloutAtCenter.cs
(for this example, uuid:<uuid>::PlaceCalloutAtCenter
).
You can
download this demo workflow
that runs the custom activity and
import it into the VertiGIS Studio Workflow Designer.
Next you need to run the workflow you just created in your VertiGIS Studio Mobile SDK project.
You can do this by configuring the layout and app config to run a workflow. You will need to copy the ID of the the workflow you created into the app.json
https://apps.vertigisstudio.com/workflow/designer/#workflow= 44010fc421dd4659b74fb921e09ba594
- App Config
- Layout
- UI
{
"schemaVersion": "1.0",
"items": [
{
"$type": "layout",
"id": "desktop-layout",
"url": "resource://layout-large.xml",
"tags": ["large"]
},
{
"$type": "workflow",
"id": "custom-workflow",
"title": "Custom Workflow",
"target": "#taskbar",
"portalItem": "<your-workflow-id>"
},
{
"$type": "menu",
"id": "iwtm",
"items": [
{
"title": "Run Custom Workflow",
"isEnabled": true,
"iconId": "workflow",
"action": {
"name": "workflow.run",
"arguments": {
"id": "custom-workflow"
}
}
}
]
}
]
}
<?xml version="1.0" encoding="utf-8" ?>
<layout
xmlns="https://geocortex.com/layout/v1"
xmlns:gxm="https://geocortex.com/layout/mobile/v1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://geocortex.com/layout/v1 ../../ViewerSpec/layout/layout-mobile.xsd">
<gxm:taskbar id="taskbar" orientation="vertical">
<map slot="main">
<stack margin="0.8" slot="top-right" halign="end">
<iwtm config="iwtm"/>
</stack>
</map>
</gxm:taskbar>
</layout>