Kartikey Shukla
Kartikey Shukla

Reputation: 11

My geofencing is working but the geofencetransition object is null

Android.Manifest.xml

<?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_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

    <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.Geo"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">

        </activity>

        <uses-library
            android:name="org.apache.http.legacy"
            android:required="false" />

        <service
            android:name=".MapsActivity"
            android:foregroundServiceType="location" /> <!-- Any inner elements would go here. -->
        <!--
             Before you run your application, you need a Google Maps API key.

             To get one, follow the directions here:

                https://developers.google.com/maps/documentation/android-sdk/get-api-key

             Once you have your API key (it starts with "AIza"), define a new property in your
             project's local.properties file (e.g. MAPS_API_KEY=Aiza...), and replace the
             "YOUR_API_KEY" string in this file with "${MAPS_API_KEY}".
        -->
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <activity
            android:name=".MapsActivity"
            android:exported="true"
            android:label="@string/title_activity_maps">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".GeofenceBroadcastReceiver"
            android:exported="true"
            android:permission="TODO">
            <intent-filter>
                <action android:name="com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

MapsActivity

package com.example.geo

import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

import androidx.annotation.RequiresApi

import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import com.example.geo.databinding.ActivityMapsBinding
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.location.Location
import android.os.Looper
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.common.internal.Constants
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingClient
import com.google.android.gms.location.GeofencingRequest
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.CircleOptions
import com.google.android.gms.maps.model.Marker

const val CHANNEL_ID = "GeoChannel"
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mMap: GoogleMap
    private lateinit var binding: ActivityMapsBinding
    private lateinit var geofencingClient: GeofencingClient
    private val REQUEST_CODE = 0
    private val geo_Id = "MyGeofence"
    private val entry_latitude = 26.267775
    private val entry_longitude = 81.505404
    private val geoRad = 282F
    private val expiration_time = 10000000000000
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationCallback: LocationCallback



    private var permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.POST_NOTIFICATIONS
    )

    private var geofencelist  : MutableList<Geofence> = mutableListOf()



    @RequiresApi(Build.VERSION_CODES.Q)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMapsBinding.inflate(layoutInflater)
        setContentView(binding.root)

        //Notification channel
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Create the NotificationChannel.
            val name = "Work"
            val descriptionText = "geofencing"
            val importance = NotificationManager.IMPORTANCE_HIGH

            val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
            mChannel.description = descriptionText
            // Register the channel with the system. You can't change the importance
            // or other notification behaviors after this.
            val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

            notificationManager.createNotificationChannel(mChannel)
            //mChannel.
        }
        val textTitle = "You hae checked in "
        val textContent = "dggdgfdf"

        // Create an explicit intent for an Activity in your app.
        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)

//        val builder = NotificationCompat.Builder(this, CHANNEL_ID)
//            .setSmallIcon(R.drawable.notification_icon)
//            .setContentTitle("My notification")
//            .setContentText("Hello World!")
//            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
//            // Set the intent that fires when the user taps the notification.
//            .setContentIntent(pendingIntent)
//            .setAutoCancel(true)

        //Location
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        //Location permission
        when {
            permissions.all {
                ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
            } -> {
                // Fine and coarse location permissions granted

            }
            permissions.any {
                ActivityCompat.shouldShowRequestPermissionRationale(this, it)
            } -> {
                // Show rationale for fine/coarse location
               // showInContextUI(...)
            }
            else -> {
                // Request fine/coarse location permissions
                ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE)
            }
        }



        //For background location
//       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//            // Check for background location on API 29+
//           ActivityCompat.requestPermissions(
//               this,
//               arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
//               REQUEST_CODE
//           )
//       }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            if (
                ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                // All permissions granted, perform action
                // performAction(...)
            }
//                ActivityCompat.shouldShowRequestPermissionRationale(
//                    this,
//                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
//                ) -> {
//                    // Show rationale for background location
//                    //showInContextUI(...)

            else {// Request background location permission
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                    REQUEST_CODE
                )
            }
        }


        //Geofencing
        geofencingClient = LocationServices.getGeofencingClient(this)

         geofencelist.add(Geofence.Builder()
            // Set the request ID of the geofence. This is a string to identify this
            // geofence.
            .setRequestId(geo_Id)

            // Set the circular region of this geofence.
            .setCircularRegion(
                entry_latitude,
                entry_longitude,
                geoRad
            )

            // Set the expiration duration of the geofence. This geofence gets automatically
            // removed after this period of time.
            .setExpirationDuration(expiration_time)

            // Set the transition types of interest. Alerts are only generated for these
            // transition. We track entry and exit transitions in this sample.
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT )
             .setLoiteringDelay(10000)
            // Create the geofence.
            .build())

        val geofencePendingIntent: PendingIntent by lazy {
            val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
           // intent.action = ACTION_GEOFENCE_EVENT
            val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }
            PendingIntent.getBroadcast(this, 0, intent, flags)
        }

        geofencingClient.addGeofences(getGeofencingRequest(), geofencePendingIntent).run {
            addOnSuccessListener {
                // Geofences added
                val fence = LatLng(entry_latitude, entry_longitude)
                mMap.addMarker(MarkerOptions().position(fence).title("Marker in center"))
                mMap.moveCamera(CameraUpdateFactory.newLatLng(fence))

                val geofenceCircle = mMap.addCircle(
                    CircleOptions()
                        .center(LatLng(entry_latitude, entry_longitude))
                        .radius(geoRad.toDouble())
                        .strokeColor(Color.GREEN)
                        .fillColor(Color.argb(64, 0, 125, 0))
                )
            }
            addOnFailureListener {
                // Failed to add geofences
                // ...
            }
        }



        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    /**
     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera. In this case,
     * we just add a marker near Sydney, Australia.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        enableMyLocation()
       // Live location
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                for (location in locationResult.locations) {
                    val cur = LatLng(location.latitude, location.longitude)

                    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(cur,20f))
                }
            }
        }
        startLocationUpdates()
    }

    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            REQUEST_CODE -> {
                // If request is cancelled, the result arrays are empty.
                if ((grantResults.isNotEmpty() &&
                            grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                    // Permission is granted. Continue the action or workflow
                    // in your app.
                } else {
                    // Explain to the user that the feature is unavailable because
                    // the feature requires a permission that the user has denied.
                    // At the same time, respect the user's decision. Don't link to
                    // system settings in an effort to convince the user to change
                    // their decision.
                }
                return
            }

            // Add other 'when' lines to check for other
            // permissions this app might request.
            else -> {
                // Ignore all other requests.
            }
        }
    }

    private fun getGeofencingRequest(): GeofencingRequest {
        return GeofencingRequest.Builder().apply {
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofences(geofencelist)
        }.build()
    }

    private fun startLocationUpdates() {
        // ... request location permissions ...
        val locationRequest = LocationRequest.create().apply {
            interval = 100// Update interval in milliseconds
            fastestInterval = 500
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_COARSE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return
        }
        fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
    }
    private fun enableMyLocation() {
        // Check if location permissions are granted
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {

            // Enable the blue dot on the map if permissions are granted
            mMap.isMyLocationEnabled = true
        } else {
            // Request location permissions if not granted
            ActivityCompat.requestPermissions(this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                REQUEST_CODE)
        }
    }
    companion object {
        internal const val ACTION_GEOFENCE_EVENT =
            "com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION"
    }

}
const val TAGED = "TAGED"

GeofenceBroadcastReceiver

package com.example.geo

import android.content.BroadcastReceiver
import android.content.ContentValues.TAG
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.location.GeofencingEvent
import android.Manifest
import androidx.core.app.NotificationCompat
import com.example.geo.MapsActivity.Companion.ACTION_GEOFENCE_EVENT

class GeofenceBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {

        Log.d(TAGED, "GeofenceBroadcastReceiver triggered")
//        if (intent != null) {
//            if (intent.action == ACTION_GEOFENCE_EVENT) {
                val geofencingEvent = intent?.let { GeofencingEvent.fromIntent(it) }
                if (geofencingEvent != null && geofencingEvent.hasError()) {
                    val errorMessage =
                        GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
                    Log.e(TAGED, "Geofencing error: $errorMessage")
                    return
                }

                val geofenceTransition = geofencingEvent?.geofenceTransition
                Log.d(TAGED, "Geofence transition: $geofenceTransition")

                if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ) {
                    // Get the geofences that were triggered. A single event can trigger multiple geofences.
                    val triggeringGeofences = geofencingEvent.triggeringGeofences

                    // Get the transition details as a String.
                    val geofenceTransitionDetails = triggeringGeofences?.let {
                        getGeofenceTransitionDetails(
                            context!!,
                            geofenceTransition,
                            it
                        )
                    }

                    // Send notification and log the transition details.
                    if (geofenceTransitionDetails != null) {
                        sendNotification(context!!, geofenceTransitionDetails)
                    }
                    if (geofenceTransitionDetails != null) {
                        Log.i(TAGED, geofenceTransitionDetails)
                    }
                } else {
                    Log.e(TAGED, "Geofence transition not handled: $geofenceTransition")
                }
//            }
//        }
    }

    private fun sendNotification(context:Context, message: String?) {
        val builder = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon) // Replace with your notification icon
            .setContentTitle("Geofence Alert")
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)

        val notificationManager = NotificationManagerCompat.from(context)

        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
            // Consider requesting the missing permission here
            return
        }

        val notificationId = 123 // You can use a unique ID for each notification
        notificationManager.notify(notificationId, builder.build())
    }
    private fun getGeofenceTransitionDetails(
        context: Context,geofenceTransition: Int,
        triggeringGeofences: List<Geofence>
    ): String {
        val geofenceTransitionString = when (geofenceTransition) {
            Geofence.GEOFENCE_TRANSITION_ENTER -> "Entered"
            Geofence.GEOFENCE_TRANSITION_EXIT -> "Exited"
            Geofence.GEOFENCE_TRANSITION_DWELL -> "Dwell"
            else -> "Unknown Transition"
        }

        // Get the Ids of each geofence that was triggered.
        val triggeringGeofencesIdsList = mutableListOf<String>()
        for (geofence in triggeringGeofences) {
            triggeringGeofencesIdsList.add(geofence.requestId)
        }
        val triggeringGeofencesIdsString = triggeringGeofencesIdsList.joinToString(", ")

        return "Geofence transition: $geofenceTransitionString - $triggeringGeofencesIdsString"
    }
}


I tried the get the log message and I am getting these messages

2024-08-25 23:30:36.829 13719-13719 TAGED                   com.example.geo                      D  GeofenceBroadcastReceiver triggered 2024-08-25 23:30:36.829 13719-13719 TAGED                   com.example.geo                      D  Geofence transition: null 2024-08-25 23:30:36.829 13719-13719 TAGED                   com.example.geo                      E  Geofence transition not handled: null  why I 

Upvotes: 1

Views: 63

Answers (1)

Shivam Bhosle
Shivam Bhosle

Reputation: 97

1. Check the Intent Action

<receiver
    android:name=".GeofenceBroadcastReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION" />
    </intent-filter>
</receiver>

In your GeofenceBroadcastReceiver, check for this :

if (intent?.action == "com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION") {
    // Your geofence processing code here
}

2. Check for GeofencingEvent Errors

val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
    val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
    Log.e(TAGED, "Geofencing error: $errorMessage")
    return
}

Upvotes: 0

Related Questions