I can successfully add a geofence but they never get triggered on a physical device. They get triggered on an emulator but never on my phones.
I am following Android Docs ditto ditto yet i cant get it to trigger on a real device. I have fine and background location granted, i have the receiver in my manifest. The onReceived function is called when using the emulator but never on a physical device. I don't have battery saver on either. I have spent a week on this... Any obvious thing i might have missed?
Update: A simplified version of what i am doing
package com.plutoapps.permissions
import android.Manifest
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.location.GeofencingClient
import com.google.android.gms.location.GeofencingEvent
import com.google.android.gms.location.GeofencingRequest
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import com.plutoapps.permissions.ui.theme.PermissionsTheme
const val TAG = "GeofenceApp"
class MainActivity : ComponentActivity() {
private lateinit var fusedClient: FusedLocationProviderClient
private lateinit var geofencingClient: GeofencingClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedClient = LocationServices.getFusedLocationProviderClient(this)
geofencingClient = LocationServices.getGeofencingClient(this)
val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE)
}
setContent {
PermissionsTheme {
// A surface container using the 'background' color from the theme
PermissionsApp(
fusedClient = fusedClient,
geofencingClient = geofencingClient,
geofencePendingIntent = geofencePendingIntent
)
}
}
}
}
@SuppressLint("MissingPermission")
@Composable
fun PermissionsApp(
modifier: Modifier = Modifier,
fusedClient: FusedLocationProviderClient,
geofencingClient: GeofencingClient,
geofencePendingIntent: PendingIntent
) {
val context = LocalContext.current
val listOfPermissions = listOfNotNull(
Manifest.permission.ACCESS_FINE_LOCATION,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) Manifest.permission.POST_NOTIFICATIONS else null,
)
var areAllPermissionsGranted by remember {
mutableStateOf(false)
}
var isBackgroundLocationPermissionsGranted by remember {
mutableStateOf(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED else true
)
}
val backgroundLocationPermissionsLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted ->
isBackgroundLocationPermissionsGranted = isGranted
})
val allPermissionsLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestMultiplePermissions(),
onResult = { results ->
areAllPermissionsGranted = !results.containsValue(false)
if (results.containsValue(false)) {
} else {
areAllPermissionsGranted = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
backgroundLocationPermissionsLauncher.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
}
})
LaunchedEffect(key1 = "123") {
if (!areAllPermissionsGranted) {
allPermissionsLauncher.launch(listOfPermissions.toTypedArray())
}
}
Scaffold { paddingValues ->
Column(
modifier = modifier
.padding(paddingValues)
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
if (areAllPermissionsGranted && isBackgroundLocationPermissionsGranted) {
fusedClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener {
geofencingClient.addGeofences(GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofence(
Geofence.Builder().setRequestId("GeoRequest1")
.setCircularRegion(it.latitude, it.longitude, 5f)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT or Geofence.GEOFENCE_TRANSITION_DWELL)
.setNotificationResponsiveness(1)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setLoiteringDelay(5).build()
)
}.build(), geofencePendingIntent).addOnSuccessListener {
Toast.makeText(context, "Geofence added", Toast.LENGTH_LONG).show()
}
}
}
}) {
Text(text = "Get Location and set geofence")
}
}
}
}
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "received ")
intent?.apply {
val geofencingEvent = GeofencingEvent.fromIntent(this)
if (geofencingEvent?.hasError() == true) {
val errorMessage =
GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAG, errorMessage)
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent!!.geofenceTransition
context?.apply {
NotificationHelper(this).sendNotification(
"GeoGeo",
"You ${if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) "dwelled" else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) "exited" else "entered"}"
)
}
}
}
}
class NotificationHelper(private val context: Context) : ContextWrapper(context) {
companion object {
const val channelId = "my_notification_channel"
const val channelName = "My Notification Channel"
const val importance = NotificationManager.IMPORTANCE_DEFAULT
}
fun sendNotification(title: String, message: String) {
// Create a notification channel for Android O and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, channelName, importance)
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
// Create a notification builder
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.baseline_notifications_24) // Replace with your icon
.setContentTitle(title).setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// Show the notification
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.notify(0, builder.build())
}
}
Now my manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Permissions"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Permissions">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".GeofenceBroadcastReceiver"
android:enabled="true"
android:exported="false"/>
</application>
</manifest>