1

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.

2 Answers2

0

On android 13 you have to ask for Post Notification permission I think this is the problem

Add this permission to the manifest file:

   <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

  • Thank you, I have already added this. When I try to do it on an event, like a button click or activity load, I'm able to post a notification, but when I call this class, it's not working, even though the class is working fine and the code is progressing properly. – Abdulrahman Hasanato Jun 14 '23 at 14:17
0

FIrst off- that's a service. So it has to be running to do something. It doesn't need to be in the foreground, but it has to be running.

Secondly- in modern Android, Services are killed after 2 minutes in the background. You need to use a Foreground Service to keep it alive longer (there is no way to keep it alive permanently).

Third- you will need background location permission. You may be being granted it, but double check that you are.

Fourth- you can't do the permission granting like that. Creating an Activity doesn't work- only the Android framework can correctly initialize one. You need to request permission from an Activity in your app, you can't do it from a Service.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • I'm requesting permission from the user on the activity. I just want my application to send a notification when the user leaves a specified area, that's why I have to track his location. I'm not sure if it's the same logic, but look at Google Maps or Facebook for example, it's accessing the location 7/24 and without being on stand-by or opened. Also, I'm already using `startForeground` to to keep it active. – Abdulrahman Hasanato Jun 14 '23 at 14:23
  • 1
    If that's what you want, you should be using a geofence instead. https://developer.android.com/training/location/geofencing. Geofencing will remove a lot of boilerplate code, and leave you with a more efficient less battery draining app – Gabe Sechan Jun 14 '23 at 16:54
  • Also, Google maps not Facebook by default access location 24/7. GM only does it while routing or open in the foreground. Facebook only does it if you allow it to. Both use Foreground services. And both can and are killed by the OS relatively frequently – Gabe Sechan Jun 14 '23 at 16:58
  • I will have a look at that, I appreciate it. But I guess Google Maps is accessing all the time because wherever I go it sends a notification to rate that place or that kind of thing. – Abdulrahman Hasanato Jun 15 '23 at 06:15