Locations & Geofences

The indigitall SDK can manage the user's location. This allows you to use the location filters on the send push campaign screen ( Campaigns> Push> New push campaign > Filters> Geographical Filters)

Once we have enabled this functionality, the end user will have to give their consent to the location permission and enable the location services of their smartphone, so that the application can obtain the exact location of the user.

x

<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"/>

You can find the AndroidManifest.xml file in the following path:

Options to keep in mind
There are two ways to manage location permissions:

  • Manual: It is the default option and the developer is in charge of managing the location permissions.
  • Automatic: It is the SDK that manages the location permissions.

Here we explain how to configure location permissions in automatic mode.

The AutoRequestPermissionLocation parameter must be added when the SDK is initialized. This is done using the following code excerpt:

//When you want to initialize indigitall
Configuration config = new Configuration
    .Builder("<YOUR-APP-KEY>", "<YOUR-SENDER-ID>")
    .setAutoRequestPermissionLocation(true)
    .build();
Indigitall.init(context, config);

In Android there is a class where the status of the permissions is received after the user has changed them through the configuration. The following piece of code should be added to capture the status of the permissions:

 //In the class you use to receive the results of asking for the permissions
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    Indigitall.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}

Request background location: Android 11 and higher

When a feature in your app requests background location on a device that runs Android 10 (API level 29), the system permissions dialog includes an option named Allow all the time. If the user selects this option, the feature in your app gains background location access.

On Android 11 (API level 30) and higher, however, the system dialog doesn't include the Allow all the time option. Instead, users must enable background location on a settings page, as shown in following figure.


To handle background location updates reliably on Android, the SDK uses WorkManager, the official Android library for scheduling deferred and persistent background tasks.
The dependency:

implementation("androidx.work:work-runtime-ktx:2.9.0")

Provides:

  • Kotlin-friendly APIs using coroutines.
  • Reliable Workers that can run even if the app is closed or the device restarts.
  • Full compatibility across all Android versions and background execution restrictions.
  • Automatic retries and constraints, ensuring tasks complete under system limitations.

The SDK requires this library to schedule and execute background location tasks in a consistent and system-compliant way.

⚠️

Using a version lower than 2.9.0 may lead to critical compatibility issues. > Older versions of WorkManager do not fully support the strict background execution and "Expedited Job" requirements introduced in recent Android versions. This can result in:

  • Location updates being throttled or blocked by the OS.
  • Runtime crashes due to missing internal APIs.
  • Inconsistent behavior when the device enters Battery Saver mode.

Troubleshooting: Version Conflicts

If your project already uses a different version of WorkManager, you might encounter a Duplicate Class or Method Not Found error. You can force the project to use the compatible version by adding this to your root build.gradle or build.gradle.kts:

configurations.all {
    resolutionStrategy {
        force("androidx.work:work-runtime-ktx:2.9.0")
    }
}

ProGuard / R8 Configuration

If you are using R8 (enabled by default in most Android projects) or ProGuard, you must ensure that the WorkManager classes and your custom Workers are not obfuscated. This allows the system to instantiate them correctly in the background.

Add the following rules to your proguard-rules.pro file:

# Keep WorkManager internal classes
-keep class androidx.work.** { *; }

# Keep your specific Worker implementation 
# Replace 'com.yourpackage' with your actual package name if necessary
-keep class com.yourpackage.location.LocationWorker { *; }

Troubleshooting & Compatibility

IssuePotential CauseRecommended Solution
Worker not foundProGuard/R8 renamed the Worker classes during minification.Add the -keep rules to your proguard-rules.pro file.
Tasks not runningUsing an outdated WorkManager version (lower than 2.9.0).Update dependency to androidx.work:work-runtime-ktx:2.9.0.
Silent FailuresDevice-specific battery optimizations (e.g., "Doze Mode").Check if the app is exempt from battery optimization in system settings.
Missing UpdatesMissing ACCESS_BACKGROUND_LOCATION permission.Ensure the user has granted "Allow all the time" location access.

App targets Android 11 or higher

If your app hasn't been granted the ACCESS_BACKGROUND_LOCATION permission, and shouldShowRequestPermissionRationale() returns true, show an educational UI to users that includes the following:

  • A clear explanation of why your app's feature needs access to background location.
  • The user-visible label of the settings option that grants background location (for example, Allow all the time in previous figure). You can call getBackgroundPermissionOptionLabel() to get this label. The return value of this method is localized to the user's device language preference.
  • An option for users to decline the permission. If users decline background location access, they should be able to continue using your app.

Simple Example code without explanation background access

class MainActivity : AppCompatActivity() {

[...]

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        Indigitall.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
        if (requestCode == Constants.REQUEST_PERMISSION_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Location permission granted")
                // Show info about background permissions
                requestBackgroundPermission(this)
            } else {
                Log.e(TAG, "Location permission denied")
                Toast.makeText(
                    this,
                    "Location permission is required for this feature",
                    Toast.LENGTH_SHORT
                ).show()
            }
        } else if (requestCode == BACKGROUND_LOCATION_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Background location permission granted")
            } else {
                Log.e(TAG, "Background location permission denied")
            }
        }
    }


    // go to location permission menu
    fun requestBackgroundPermission(activity: Activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ActivityCompat.requestPermissions(
                activity,
                arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                BACKGROUND_LOCATION_REQUEST_CODE
            )
        }
    }

Complete example code

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    // Pass the result to the SDK
    Indigitall.onRequestPermissionsResult(this, requestCode, permissions, grantResults)

    when (requestCode) {
        Constants.REQUEST_PERMISSION_CODE -> {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Fine location granted")
                // Ask for background permission after fine location is granted
                requestBackgroundPermissionWithRationale()
            } else {
                Log.e(TAG, "Fine location denied")

                if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
                    // Show rationale before asking again
                    showRationaleDialog(
                        "We need your location to send you personalized notifications. Without this permission, some features will not be available."
                    ) {
                        ActivityCompat.requestPermissions(
                            this,
                            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                            Constants.REQUEST_PERMISSION_CODE
                        )
                    }
                } else {
                    Toast.makeText(
                        this,
                        "Permission is required. Enable it in Settings if you have permanently denied it.",
                        Toast.LENGTH_LONG
                    ).show()
                }
            }
        }

        BACKGROUND_LOCATION_REQUEST_CODE -> {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Background location granted")
            } else {
                Log.e(TAG, "Background location denied")
            }
        }
    }
}

private fun requestBackgroundPermissionWithRationale() {
    val permission = Manifest.permission.ACCESS_BACKGROUND_LOCATION

    when {
        ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> {
            Log.d(TAG, "Background location already granted")
        }

        shouldShowRequestPermissionRationale(permission) -> {
            // Show rationale before requesting background location
            showRationaleDialog(
                "To continue receiving notifications even when the app is closed, we need background location access."
            ) {
                requestBackgroundPermission(this)
            }
        }

        else -> {
            // Request directly if no rationale is needed
            requestBackgroundPermission(this)
        }
    }
}

fun requestBackgroundPermission(activity: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
            BACKGROUND_LOCATION_REQUEST_CODE
        )
    }
}

private fun showRationaleDialog(message: String, onPositive: () -> Unit) {
    AlertDialog.Builder(this)
        .setTitle("Permission required")
        .setMessage(message)
        .setPositiveButton("Allow") { _, _ -> onPositive() }
        .setNegativeButton("Cancel", null)
        .show()
}