How to properly add and trigger geofences?

69 views Asked by At

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>
0

There are 0 answers