Reputation: 405
I am looking into geofencing.
geofencingEvent.geofenceTransition
seems to trigger 2
(Exit) even if I set the geofence several kilometers away.
Test environments:
Things I thought could be the cause:
The radius. Maybe the radius is not read properly, but the database does specify a 50m radius and when displayed on the map the radius seems accurate.
The notification system: Seems to be easiest culprit. However, the log within onReceive()
also confirms that the user has exited the Geofence, even though he never entered it.
2024-06-22 18:08:51.982 9613-9613 GeofenceBr...stReceiver com.example.geofenceapp D User exits the geofence
onReceive()
might be wrong: Thats my current conclusion. But I cannot really find where the problem lies.
I have also added another view (SharedViewModel, which onReceive()
refers to a few times.
Any ideas?
GeofenceBroadcastReceiver
package com.example.geofenceapp.broadcastreceiver
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.example.geofenceapp.R
import com.example.geofenceapp.util.Constants.NOTIFICATION_CHANNEL_ID
import com.example.geofenceapp.util.Constants.NOTIFICATION_CHANNEL_NAME
import com.example.geofenceapp.util.Constants.NOTIFICATION_ID
import com.example.geofenceapp.util.Permissions
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.Geofence.GeofenceTransition
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.location.GeofencingEvent
class GeofenceBroadcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent == null) {
Log.e("GeofenceBroadcastReceiver", "Received null context or intent")
return
}
val geofencingEvent = GeofencingEvent.fromIntent(intent)
Log.e("geofencingEvent", "geofencingEvent is $geofencingEvent")
if (geofencingEvent == null) {
Log.e("GeofenceBroadcastReceiver", "GeofencingEvent is null. Intent details: ${intent.toUri(0)}")
return
}
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
Log.e("GeofenceBroadcastReceiver", "GeofencingEvent error: $errorMessage")
return
}
val geofenceTransition = geofencingEvent.geofenceTransition
Log.d("GeofenceBroadcastReceiver", "Received geofence event: $geofenceTransition")
// Check if the transition type is valid
if (geofenceTransition in listOf(Geofence.GEOFENCE_TRANSITION_ENTER, Geofence.GEOFENCE_TRANSITION_EXIT, Geofence.GEOFENCE_TRANSITION_DWELL)) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
val geofenceIds = triggeringGeofences?.joinToString(", ") { it.requestId }
Log.d("GeofenceBroadcastReceiver", "Triggering geofences: $geofenceIds")
// Handle specific geofence transitions
when (geofenceTransition) {
Geofence.GEOFENCE_TRANSITION_ENTER -> {
Log.d("GeofenceBroadcastReceiver", "Geofence Enter")
Log.d("GeofenceBroadcastReceiver", "User is within the geofence")
displayNotification(context, "Geofence ENTER")
}
Geofence.GEOFENCE_TRANSITION_EXIT -> {
Log.d("GeofenceBroadcastReceiver", "Geofence Exit")
Log.d("GeofenceBroadcastReceiver", "User exits the geofence")
displayNotification(context, "Geofence Exit")
}
Geofence.GEOFENCE_TRANSITION_DWELL -> {
Log.d("GeofenceBroadcastReceiver", "Geofence Dwell")
Log.d("GeofenceBroadcastReceiver", "User dwells within the geofence")
displayNotification(context, "Geofence Dwell")
}
}
} else {
Log.d("GeofenceBroadcastReceiver", "Unknown geofence transition: $geofenceTransition")
}
}
}
SharedViewModel
package com.example.geofenceapp.viewmodels
import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.location.GnssAntennaInfo.SphericalCorrections
import android.location.LocationManager
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.example.geofenceapp.broadcastreceiver.GeofenceBroadcastReceiver
import com.example.geofenceapp.data.DataStoreRepository
import com.example.geofenceapp.data.GeofenceEntity
import com.example.geofenceapp.data.GeofenceRepository
import com.example.geofenceapp.util.Permissions
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingRequest
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.maps.android.SphericalUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.math.sqrt
@HiltViewModel
class SharedViewModel @Inject constructor (
application: Application,
private val dataStoreRepository: DataStoreRepository,
private val geofenceRepository: GeofenceRepository,
):AndroidViewModel(application) {
val app = application
private var geofencingClient = LocationServices.getGeofencingClient(app.applicationContext)
var geoId:Long = 0L
var geoName: String = "Default"
var geoCountryCode: String = ""
var geoLocationName: String = "Search a City"
var geoLatLng: LatLng = LatLng(0.0, 0.0)
var geoRadius: Float = 50f
var geoSnapShot: Bitmap? = null
var geoCitySelected = false
var geofenceReady = false
var geofencePrepared = false
//DataStore
val readFirstLaunch = dataStoreRepository.readFirstLaunch.asLiveData()
fun saveFirstLaunch(firstLaunch: Boolean) =
viewModelScope.launch(Dispatchers.IO){
dataStoreRepository.saveFirstLaunch(firstLaunch)
}
//Database
val readyGeofences = geofenceRepository.readGeofences.asLiveData()
fun addGeofence(geofenceEntity: GeofenceEntity) =
viewModelScope.launch(Dispatchers.IO) {
geofenceRepository.addGeofence(geofenceEntity)
}
fun removeGeofence(geofenceEntity: GeofenceEntity) =
viewModelScope.launch(Dispatchers.IO) {
geofenceRepository.removeGeofence(geofenceEntity)
}
fun addGeofenceToDatabase(location:LatLng){
val geofenceEntity =
GeofenceEntity(
geoId,
geoName,
geoLocationName,
location.latitude,
location.longitude,
geoRadius,
geoSnapShot!!,
)
addGeofence(geofenceEntity)
}
private fun setPendingIntent(geoId:Int): PendingIntent{
val intent = Intent(app, GeofenceBroadcastReceiver::class.java)
Log.d("setPendingIntent", "Creating PendingIntent for geoId: $geoId with action $intent")
// Choose FLAG_IMMUTABLE or FLAG_MUTABLE based on your needs
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
Log.d("Flag is","flag is ${flags}")
return PendingIntent.getBroadcast(
app,
geoId,
intent,
flags,
).also {
Log.d("setPendingIntent", "PendingIntent created: $it")
}
}
fun startGeofence(
latitude: Double,
longitude: Double,
){
if(Permissions.hasBackgroundLocationPermission(app)){
val geofence = Geofence.Builder()
.setRequestId(geoId.toString())
.setCircularRegion(
latitude,
longitude,
geoRadius,
)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(
Geofence.GEOFENCE_TRANSITION_ENTER
or Geofence.GEOFENCE_TRANSITION_EXIT
or Geofence.GEOFENCE_TRANSITION_DWELL
)
.setLoiteringDelay(5000)
.build()
val geofencingRequest = GeofencingRequest.Builder()
.setInitialTrigger(
GeofencingRequest.INITIAL_TRIGGER_ENTER
or GeofencingRequest.INITIAL_TRIGGER_EXIT
or GeofencingRequest.INITIAL_TRIGGER_DWELL
)
.addGeofence(geofence)
.build()
geofencingClient.addGeofences(geofencingRequest, setPendingIntent(geoId.toInt())).run{
addOnSuccessListener {
Log.d("Geofence","Successfully added.")
}
addOnFailureListener{
Log.d("Geofence", it.message.toString())
}
}
}else{
Log.d("Geofence","Permission not granted.")
}
}
fun getBounds(center:LatLng, radius:Float): LatLngBounds{
val distanceFromCenterToCorner = radius * sqrt(2.0)
//to find south west corner of a point
//we apply 225 degrees from north point
//then apply a square root of the radius
val southWestCorner = SphericalUtil.computeOffset(center, distanceFromCenterToCorner, 225.0)
//to find north east corner of a point
//we apply 45 degrees from north point
//then apply a square root of the radius
val northEastCorner = SphericalUtil.computeOffset(center, distanceFromCenterToCorner,45.0)
return LatLngBounds(southWestCorner, northEastCorner)
}
fun checkDeviceLocationSettings(context: Context):Boolean{
return if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
val LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
LocationManager.isLocationEnabled
}else{
val mode: Int = Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_OFF
)
mode != Settings.Secure.LOCATION_MODE_OFF
}
}
}
Upvotes: 0
Views: 22