I have the code below which is supposed to send a notification when the user's location changes, but it doesn't do anything. Even though I have granted the necessary permissions. And I have already declared the service in the manifest file. I also added some logs to check the code progress, and it was progressing correctly when the app is open (only progression, no notifications sent), but if the user left the app (kept it on stand-by) or closed it, it doesn't do anything.
Here's my full code:
class LocationMonitoringService : Service() {
private lateinit var locationManager: LocationManager
private lateinit var notificationManager: NotificationManager
private var isMonitoring = false
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
sendLocationNotification(location)
}
override fun onProviderDisabled(provider: String) {}
override fun onProviderEnabled(provider: String) {}
}
override fun onCreate() {
super.onCreate()
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!isMonitoring) {
startLocationMonitoring()
isMonitoring = true
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
stopLocationMonitoring()
isMonitoring = false
}
private fun startLocationMonitoring() {
val minTime = 1000L //Minimum time interval between location updates (Milliseconds)
val minDistance = 0f //Minimum distance between location updates (Meters)
//Check if the required location providers are enabled
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if (isGpsEnabled) {
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
//Request Location permission
val profileActivity = ProfileActivity()
ActivityCompat.requestPermissions(
profileActivity, arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
), 0
)
return
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
minTime,
minDistance,
locationListener
)
}
if (isNetworkEnabled) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
minTime,
minDistance,
locationListener
)
}
}
private fun stopLocationMonitoring() {
locationManager.removeUpdates(locationListener)
}
private fun sendLocationNotification(location: Location) {
val channelId = "location_notification_channel"
val notificationId = 1
//Create an intent to open the app when the notification is tapped
val intent = Intent(this, ProfileActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
//Build the notification
val notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("Location Update")
.setContentText("New Location: ${location.latitude}, ${location.longitude}")
.setSmallIcon(R.mipmap.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
//Send the notification
notificationManager.notify(notificationId, notification)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
I have tried to ask ChatGPT and Bard for some help before I share it here, but they did not give enough information. I also tried to look everywhere and try solutions, but nothing helped me. This and this are some of the questions I have checked on StackOverflow, other than the other platforms.
EDIT: After Gabe Sechan suggested I use Geofence, I tried to do it and it actually worked partially This is the code I used:
class GeofenceActivity : AppCompatActivity() {
private lateinit var geoClient: GeofencingClient
private lateinit var locationManager: LocationManager
private lateinit var geofencePendingIntent: PendingIntent
private lateinit var container: ConstraintLayout
//region Location
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val targetLat = -53.09473787111764
private val targetLong = 73.5237222468214
private val targetLocation = Location("").apply {
latitude = targetLat
longitude = targetLong
}
private lateinit var userLocation: Location
private var userLatitude: Double = 0.0
private var userLongitude: Double = 0.0
//endregion
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_geofence)
try {
container = findViewById(R.id.qwe)
geoClient = LocationServices.getGeofencingClient(this)
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
//Request the location permission
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1
)
} else {
startGeofencing()
startLocationUpdateService()
}
//Register for location updates
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0f, locationListener
)
//Initialize userLocation
userLocation = Location("").apply {
latitude = userLatitude
longitude = userLongitude
}
} catch (e: java.lang.Exception) {
Log.e("onCreate", "${e.message}")
Snackbar.make(container, "Error ${e.message}", Snackbar.LENGTH_LONG).show()
}
}
private fun startGeofencing() {
//Create a geofence
val geofence = Geofence.Builder().setRequestId("myGeofence")
.setCircularRegion(-53.09473787111764, 73.5237222468214, 100f)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()
//Create a PendingIntent to receive geofence transition events
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
geofencePendingIntent = PendingIntent.getBroadcast(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
//Create a GeofencingRequest object
val geofencingRequest = GeofencingRequest.Builder().addGeofence(geofence)
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER).build()
//Add the geofence
if (ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
//Request Location permission
ActivityCompat.requestPermissions(
this, arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
), 0
)
return
}
geoClient.addGeofences(geofencingRequest, geofencePendingIntent).run {
addOnSuccessListener {
Log.d("Geofence", "Geofence added successfully")
}
addOnFailureListener {
Log.e("Geofence", "Error adding geofences: ${it.message}")
}
}
}
private fun startLocationUpdateService() {
val serviceIntent = Intent(this, LocationUpdateService::class.java)
ContextCompat.startForegroundService(this, serviceIntent)
}
private val locationListener = LocationListener { location ->
val distance: Float = userLocation.distanceTo(targetLocation)
val range = 100
//If user is in the area
if (distance <= range) {
Log.e(TAG, "NASDAQ: asdasdasd")
sendNotification("Welcome", "Don't forget to check in")
} else {
Log.e(TAG, "QWERTY: qweqweqwe")
sendNotification("Goodbye", "Did you check out?")
Log.e(
"Location changed", "${location.latitude}, ${location.longitude}"
)
}
}
private fun sendNotification(title: String, message: String) {
//Create the notification channel
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel("channelId", "channelName", importance)
//Customize channel settings
channel.enableVibration(true)
channel.vibrationPattern = longArrayOf(100, 200, 300)
//Register the channel with the system
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
//Create an intent to open an activity when the notification is tapped
val intent = Intent(this, GeofenceActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
//Build the notification
val notificationBuilder = NotificationCompat.Builder(this, "channelId")
.setSmallIcon(R.drawable.ic_launcher_foreground).setContentTitle(title)
.setContentText(message).setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(false).setContentIntent(pendingIntent)
//Send the notification
notificationManager.notify(1, notificationBuilder.build())
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//Permission granted, continue with geofencing operations
startGeofencing()
startLocationUpdateService()
} else {
//Permission denied, handle accordingly
Snackbar.make(container, "DUDE!", Snackbar.LENGTH_LONG).show()
}
}
inner class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val geofenceTransition = GeofencingEvent.fromIntent(intent)?.geofenceTransition
val triggeringGeofences = GeofencingEvent.fromIntent(intent)?.triggeringGeofences
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
Snackbar.make(container, "Entered Geofence", Snackbar.LENGTH_LONG).show()
} else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
Snackbar.make(container, "Exited Geofence", Snackbar.LENGTH_LONG).show()
}
}
}
}
Now, it's working when the user leaves the specified area, but when he enters the area, nothing happens until he clicks on the notification, or close and re-open the app, in short, the user has to re-create the activity to update the notification when he returns to the area.