Geotrigger only works once (when device's position starts inside geofence)

885
8
Jump to solution
11-02-2022 05:34 AM
PabloAndrésDiazMazzaro
New Contributor II

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! 

0 Kudos
1 Solution

Accepted Solutions
PabloAndrésDiazMazzaro
New Contributor II

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")
            }
        }
    }
}

 

View solution in original post

0 Kudos
8 Replies
LukeSmallwood
Esri Contributor

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

PabloAndrésDiazMazzaro
New Contributor II

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).

0 Kudos
Hudson_Miears
New Contributor II

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.

 

PabloAndrésDiazMazzaro
New Contributor II

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.

0 Kudos
Hudson_Miears
New Contributor II

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?

 

0 Kudos
PabloAndrésDiazMazzaro
New Contributor II

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}"
        })
0 Kudos
PabloAndrésDiazMazzaro
New Contributor II

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")
            }
        }
    }
}

 

0 Kudos
Hudson_Miears
New Contributor II

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!

 

0 Kudos