Helo there.
I'm working with ArcGIS Runtime for Android (written in Kotlin) and most of basic functions works fine. I can show device position (with high accuracy) and load a WebMap where are two FeatureServices. Those services are providing features for geofences.
First test looks like it was working fine, the device is inside one of the geofences a triggers GeotriggerMonitor notification for ENTER event. Continuing with the test, the device leaves geofence and nothing happen.
Also, I tested the opposite situation, starts out of geofence and then enter but, nothing happen either.
The source is similar to this example: this .
There're same syntax differences but, the main difference cloud be related with where MapView is placed. It's in a Fragmente instead of in an Activity.
My code:
GeotriggerMonitor setup:
private fun setupComplejoGeotriggers(){
val locationFeed = LocationGeotriggerFeed(mapView.locationDisplay.locationDataSource)
//Geofences Layer
complejosLayer?.let { layer ->
val fenceParameters = FeatureFenceParameters(layer.featureTable)
val predioGeotrigger = FenceGeotrigger(locationFeed, FenceRuleType.ENTER_OR_EXIT,fenceParameters, null, "EnterComplejo")
val geotriggerMonitor = GeotriggerMonitor(predioGeotrigger)
geotriggerMonitor.addGeotriggerMonitorNotificationEventListener(onComplejoEnterEvent)
geotriggerMonitor.addGeotriggerMonitorWarningChangedEventListener(onGeotriggerEvent)
val future = geotriggerMonitor.startAsync()
// check to see if the GeotriggerMonitor failed to start
future.addDoneListener {
if (geotriggerMonitor.status == GeotriggerMonitorStatus.FAILED_TO_START) {
val message = "GeotriggerMonitor failed to load: " +
geotriggerMonitor.warning.message +
geotriggerMonitor.warning.additionalMessage
Log.e("GEOPDM", message)
}
}
Log.d(TAG, "Complejo dection ready")
}
}
Notifications Handler:
private val onComplejoEnterEvent =
GeotriggerMonitorNotificationEventListener { complejoEnterEvt ->
val notifInfo =
complejoEnterEvt.geotriggerNotificationInfo as FenceGeotriggerNotificationInfo
val fence = notifInfo.fenceGeoElement
val truck = notifInfo.feedLocation.position
val prefdioFeature = fence as ArcGISFeature
val idLugar = prefdioFeature.attributes["ID_LUGAR"]
showTost("Destino ${notifInfo.fenceNotificationType} - $idLugar")
}
Warning Handler:
private val onGeotriggerEvent =
GeotriggerMonitorWarningChangedEventListener { warnEvt ->
Log.d(TAG, "Trigger w")
Log.d(TAG, "Predio $warnEvt")
Log.d(TAG, "Predio $warnEvt", warnEvt.warning)
}
Thank you very much!
Hope somebody can help me!
Solved! Go to Solution.
Finally, It's works. Thanks you guys. I have used a combination of both answers. First off all, I defined a GeotriggerMonitor as a fragmente level variable and then, I moved all Monitor setup code inside of a coroutine main scope.
class MapFragment : Fragment(), TokenizedFragment, PatenteComponent {
/******** Internals ********/
private var geotriggerMonitorPredios : GeotriggerMonitor?=null
private var prediosLayer: FeatureLayer? = null
/******** Fragment Lifecycle ********/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_map, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Map
mapView = view.findViewById<MapView>(R.id.mapView)
tv_debug = view.findViewById<TextView>(R.id.tv_debug)
/***** Live location *****/
var criteria = Criteria()
criteria.powerRequirement = Criteria.POWER_HIGH
criteria.accuracy = Criteria.ACCURACY_FINE
criteria.isSpeedRequired = true
criteria.isAltitudeRequired = false
criteria.isBearingRequired = true
criteria.isCostAllowed = true
locationDataSource = AndroidLocationDataSource(context, criteria, 1000, 1.0F)
locationDataSource.addLocationChangedListener(LocationDataSource.LocationChangedListener {
Log.d("GEOPDM", "Pos: ${it.location?.position}")
var speed = it.location?.velocity?.times(3.6)
if(speed == null){
speed = 0.0
}
tv_debug.text = "Pos: ${it.location?.position}\n${it.location.velocity}\n" +
" ${it.location.course}"
})
mapView.locationDisplay.addDataSourceStatusChangedListener {
// if LocationDisplay isn't started or has an error
if (!it.isStarted && it.error != null) {
// check permissions to see if failure may be due to lack of permissions
requestPermissions(it)
}
}
mapView.locationDisplay.locationDataSource = locationDataSource
//mapView.locationDisplay.locationDataSource = sim
mapView.locationDisplay.autoPanMode = LocationDisplay.AutoPanMode.COMPASS_NAVIGATION
initOnlineMap()
}
private fun initOnlineMap(){
val portal = Portal(Copiloto.PORTAL_URL, false)
val portalItem = PortalItem(portal, Copiloto.MAP_ITEMID)
mapView.apply {
map = ArcGISMap(portalItem)
}
mapView.map?.addDoneLoadingListener {
Log.d("PDM", "[MapFragment] Map loaded")
initUI()
}
}
private fun initUI(){
setOwnPositionSymbol()
MainActivity.viewModel?.fences?.let { fences ->
filterPredios(fences)
val ioScope = CoroutineScope(Dispatchers.Main)
ioScope.launch {
setupPredioGeotriggers()
}
}
}
/******** Map - Geotriggers ********/
private val onPredioEnterEvent =
GeotriggerMonitorNotificationEventListener { predioEnterEvt ->
val notifInfo =
predioEnterEvt.geotriggerNotificationInfo as FenceGeotriggerNotificationInfo
val fence = notifInfo.fenceGeoElement
val truck = notifInfo.feedLocation.position
val prefdioFeature = fence as ArcGISFeature
val idLugar = prefdioFeature.attributes["ID_LUGAR"]
showTost("Origen ${notifInfo.fenceNotificationType}")
}
private val onGeotriggerEvent =
GeotriggerMonitorWarningChangedEventListener { warnEvt ->
Log.d("GEOPDM", "Trigger w")
Log.d("GEOPDM", "Predio $warnEvt")
Log.d("GEOPDM", "Predio $warnEvt", warnEvt.warning)
}
private fun setupPredioGeotriggers(){
val ioScope = CoroutineScope(Dispatchers.Main)
ioScope.launch {
//val locationFeed = LocationGeotriggerFeed(mapView.locationDisplay.locationDataSource)
var criteria = Criteria()
criteria.powerRequirement = Criteria.POWER_HIGH
criteria.accuracy = Criteria.ACCURACY_FINE
criteria.isSpeedRequired = true
criteria.isAltitudeRequired = false
criteria.isBearingRequired = true
criteria.isCostAllowed = true
val locationDs = AndroidLocationDataSource(context, criteria, 1000, 1.0F)
locationDs.startAsync()
val locationFeed = LocationGeotriggerFeed(locationDs)
//mapView.locationDisplay.locationDataSource.startAsync()
prediosLayer?.let { layer ->
val fenceParameters = FeatureFenceParameters(layer.featureTable)
val predioGeotrigger = FenceGeotrigger(locationFeed, FenceRuleType.ENTER_OR_EXIT,fenceParameters, ArcadeExpression("\$fenceFeature.ID_LUGAR"), "EnterPredio")
geotriggerMonitorPredios = GeotriggerMonitor(predioGeotrigger)
geotriggerMonitorPredios!!.addGeotriggerMonitorNotificationEventListener(onPredioEnterEvent)
geotriggerMonitorPredios!!.addGeotriggerMonitorWarningChangedEventListener(onGeotriggerEvent)
val future = geotriggerMonitorPredios!!.startAsync()
// check to see if the GeotriggerMonitor failed to start
future.addDoneListener {
if (geotriggerMonitorPredios!!.status == GeotriggerMonitorStatus.FAILED_TO_START) {
val message = "GeotriggerMonitor Predios failed to load: " +
geotriggerMonitorPredios!!.warning.message +
geotriggerMonitorPredios!!.warning.additionalMessage
Log.e("GEOPDM", message)
}
}
Log.d("GEOPDM", "Predios deteccion ready")
}
}
}
}
Hi - thanks for posting.
Looking at your code, I think the issue is that you are letting the geotrigger monitor go out of scope here:
val geotriggerMonitor = GeotriggerMonitor(predioGeotrigger)
I suspect the monitor is getting garbage collected and then stops sending notifications because it is at local scope. Can you try changing your code to persist the geotrigger monitor? You need to hang on to it for as long as you want to monitor conditions.
Hope that helps,
Luke
Hi Luke, thank you for your answer.
That idea also came to me and I already tried it(fragment level variable) but, same result sadly.
Also, I tried to create an instance of GeotriggerMonitor in a main dispatcher coroutine scope but, didn't works, same issue (only works first time with device inside geofence).
Hi Pablo! Would love to help you more. Just so I understand, could you explain what happens in your test? Does the activity running the geotrigger get backgrounded during the test?
We actually recommend keeping your GeotriggerMonitor in a Foreground Service if you need to keep it running when the app is in the background. This section of the docs explains it further.
Hi @Hudson_Miears. Thanks for your answer. GeotriggerMonitor is running in a fragment, this fragment is mounted in a FrameLayout inside main Activity. Should this situation make Geotrigger get backgrounded? (Sorry, I'm not an expert android developer.)
No background location is needed at this point (maybe in a future version of this app) but , I could try this approach.
If background location is not needed at this point, it would be helpful to understand a few more things. If you plug directly into your LocationDataSource, are you receiving LocationChangedEvents as expected?
Yes. I recibe position, velocity and course correctly.
var criteria = Criteria()
criteria.powerRequirement = Criteria.POWER_HIGH
criteria.accuracy = Criteria.ACCURACY_FINE
criteria.isSpeedRequired = true
criteria.isAltitudeRequired = false
criteria.isBearingRequired = true
criteria.isCostAllowed = true
locationDataSource = AndroidLocationDataSource(context, criteria, 1000, 2.0F)
locationDataSource.addLocationChangedListener(LocationDataSource.LocationChangedListener {
Log.d(TAG, "Pos: ${it.location?.position}")
setVelocidadToVelocidadView(it.location.velocity.toInt(), MainActivity.viewModel?.maxVelocidad)
tv_debug.text = "Pos: ${it.location?.position}\n${it.location.velocity}\n" +
" ${it.location.course}"
})
Finally, It's works. Thanks you guys. I have used a combination of both answers. First off all, I defined a GeotriggerMonitor as a fragmente level variable and then, I moved all Monitor setup code inside of a coroutine main scope.
class MapFragment : Fragment(), TokenizedFragment, PatenteComponent {
/******** Internals ********/
private var geotriggerMonitorPredios : GeotriggerMonitor?=null
private var prediosLayer: FeatureLayer? = null
/******** Fragment Lifecycle ********/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_map, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Map
mapView = view.findViewById<MapView>(R.id.mapView)
tv_debug = view.findViewById<TextView>(R.id.tv_debug)
/***** Live location *****/
var criteria = Criteria()
criteria.powerRequirement = Criteria.POWER_HIGH
criteria.accuracy = Criteria.ACCURACY_FINE
criteria.isSpeedRequired = true
criteria.isAltitudeRequired = false
criteria.isBearingRequired = true
criteria.isCostAllowed = true
locationDataSource = AndroidLocationDataSource(context, criteria, 1000, 1.0F)
locationDataSource.addLocationChangedListener(LocationDataSource.LocationChangedListener {
Log.d("GEOPDM", "Pos: ${it.location?.position}")
var speed = it.location?.velocity?.times(3.6)
if(speed == null){
speed = 0.0
}
tv_debug.text = "Pos: ${it.location?.position}\n${it.location.velocity}\n" +
" ${it.location.course}"
})
mapView.locationDisplay.addDataSourceStatusChangedListener {
// if LocationDisplay isn't started or has an error
if (!it.isStarted && it.error != null) {
// check permissions to see if failure may be due to lack of permissions
requestPermissions(it)
}
}
mapView.locationDisplay.locationDataSource = locationDataSource
//mapView.locationDisplay.locationDataSource = sim
mapView.locationDisplay.autoPanMode = LocationDisplay.AutoPanMode.COMPASS_NAVIGATION
initOnlineMap()
}
private fun initOnlineMap(){
val portal = Portal(Copiloto.PORTAL_URL, false)
val portalItem = PortalItem(portal, Copiloto.MAP_ITEMID)
mapView.apply {
map = ArcGISMap(portalItem)
}
mapView.map?.addDoneLoadingListener {
Log.d("PDM", "[MapFragment] Map loaded")
initUI()
}
}
private fun initUI(){
setOwnPositionSymbol()
MainActivity.viewModel?.fences?.let { fences ->
filterPredios(fences)
val ioScope = CoroutineScope(Dispatchers.Main)
ioScope.launch {
setupPredioGeotriggers()
}
}
}
/******** Map - Geotriggers ********/
private val onPredioEnterEvent =
GeotriggerMonitorNotificationEventListener { predioEnterEvt ->
val notifInfo =
predioEnterEvt.geotriggerNotificationInfo as FenceGeotriggerNotificationInfo
val fence = notifInfo.fenceGeoElement
val truck = notifInfo.feedLocation.position
val prefdioFeature = fence as ArcGISFeature
val idLugar = prefdioFeature.attributes["ID_LUGAR"]
showTost("Origen ${notifInfo.fenceNotificationType}")
}
private val onGeotriggerEvent =
GeotriggerMonitorWarningChangedEventListener { warnEvt ->
Log.d("GEOPDM", "Trigger w")
Log.d("GEOPDM", "Predio $warnEvt")
Log.d("GEOPDM", "Predio $warnEvt", warnEvt.warning)
}
private fun setupPredioGeotriggers(){
val ioScope = CoroutineScope(Dispatchers.Main)
ioScope.launch {
//val locationFeed = LocationGeotriggerFeed(mapView.locationDisplay.locationDataSource)
var criteria = Criteria()
criteria.powerRequirement = Criteria.POWER_HIGH
criteria.accuracy = Criteria.ACCURACY_FINE
criteria.isSpeedRequired = true
criteria.isAltitudeRequired = false
criteria.isBearingRequired = true
criteria.isCostAllowed = true
val locationDs = AndroidLocationDataSource(context, criteria, 1000, 1.0F)
locationDs.startAsync()
val locationFeed = LocationGeotriggerFeed(locationDs)
//mapView.locationDisplay.locationDataSource.startAsync()
prediosLayer?.let { layer ->
val fenceParameters = FeatureFenceParameters(layer.featureTable)
val predioGeotrigger = FenceGeotrigger(locationFeed, FenceRuleType.ENTER_OR_EXIT,fenceParameters, ArcadeExpression("\$fenceFeature.ID_LUGAR"), "EnterPredio")
geotriggerMonitorPredios = GeotriggerMonitor(predioGeotrigger)
geotriggerMonitorPredios!!.addGeotriggerMonitorNotificationEventListener(onPredioEnterEvent)
geotriggerMonitorPredios!!.addGeotriggerMonitorWarningChangedEventListener(onGeotriggerEvent)
val future = geotriggerMonitorPredios!!.startAsync()
// check to see if the GeotriggerMonitor failed to start
future.addDoneListener {
if (geotriggerMonitorPredios!!.status == GeotriggerMonitorStatus.FAILED_TO_START) {
val message = "GeotriggerMonitor Predios failed to load: " +
geotriggerMonitorPredios!!.warning.message +
geotriggerMonitorPredios!!.warning.additionalMessage
Log.e("GEOPDM", message)
}
}
Log.d("GEOPDM", "Predios deteccion ready")
}
}
}
}
That's great to hear it's working! Thank you for sharing your code as well. If you have any other issues, do let us know!