Native location and device risk signals

Kade Android Core SDK

Integrate encrypted geofence checks, continuous verification, persisted fingerprints, and native device signals directly in a Kotlin application.

minSdk 23 compileSdk 34 JVM 17 No React Native in Core

Overview

GeoFenceClient.getInstance(context) is the high-level native entry point. It owns configuration, encryption, pings, continuous checks, and fingerprint submission while retaining only the application context.

Native Core

The com.videntrix.geofence.core module is a Kotlin Android library with React Native dependencies explicitly excluded.

One process-wide client

Obtain the singleton with an Android Context, start it before calling ping or training methods, and stop ongoing work when the protected flow ends.

Native Core does not expose browser-only email evaluation or network-based location estimation APIs. React Native bridge methods are also intentionally excluded from this guide.

Availability

The Android Core SDK is not currently distributed as a standalone public Maven artifact. There are no public Maven coordinates or repository installation commands to use.

Contact Kade for access. Kade will provide the approved source or integration package, dependency setup, credentials, and packaging instructions for your application.

The current source module is configured with minSdk 23, compileSdk 34, targetSdk 34, Java 17, and Kotlin JVM target 17. These facts describe the source module, not a public release.

Integration

After Kade supplies the native package, add it according to the delivery-specific instructions. Do not invent a remote dependency coordinate or include the React Native bridge in a native-only app.

Host build requirements

android {
  compileSdk 34

  defaultConfig {
    minSdk 23
  }

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }

  kotlinOptions {
    jvmTarget = '17'
  }
}

When Kade supplies Core as source, its module declares coroutines, AndroidX Core, Security Crypto, Lifecycle Process, Google Play services Location and Block Store, Play Integrity, and OkHttp dependencies. Follow the supplied packaging instructions to determine whether your host must declare any of them directly.

Quick start

Obtain the singleton, start it asynchronously, and issue a ping only after the startup callback succeeds.

import com.videntrix.geofence.core.GeoFenceClient
import com.videntrix.geofence.core.GeoFenceConfig
import com.videntrix.geofence.core.PingOptions

val client = GeoFenceClient.getInstance(applicationContext)
val config = GeoFenceConfig(
    apiToken = "your-api-token",
    regionId = 123,
)

client.start(config) { startResult ->
    startResult.fold(
        onSuccess = {
            client.ping(
                options = PingOptions(
                    userId = "player-123",
                    source = "login",
                    email = "player@example.com",
                ),
            ) { pingResult ->
                pingResult.fold(
                    onSuccess = { response ->
                        if (response.blocked == true) {
                            println("Blocked: ${response.code ?: "unknown"}")
                        } else {
                            println("Inside: ${response.inside}")
                            println("Area: ${response.area?.name ?: "none"}")
                        }
                    },
                    onFailure = { error ->
                        println("Kade ping failed: $error")
                    },
                )
            }
        },
        onFailure = { error ->
            println("Kade startup failed: $error")
        },
    )
}
start initializes on the SDK's background coroutine scope, hydrates persisted client identity, derives the encryption key, and then invokes (Result<Unit>) -> Unit. Do not treat the client as ready before a successful callback.

Permissions

Core declares network and foreground location permissions. Android still requires the host app to request dangerous location permissions at runtime.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
private val locationPermission =
    registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions(),
    ) { grants ->
        val precise =
            grants[Manifest.permission.ACCESS_FINE_LOCATION] == true

        if (precise) {
            beginVerification()
        } else {
            showPreciseLocationRequired()
        }
    }

fun requestKadeLocation() {
    locationPermission.launch(
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
        ),
    )
}
Core defines distinct exceptions for missing location permission, precise location being required, and disabled location services. Request both coarse and fine permission, verify that fine permission was granted, and handle disabled services before starting continuous checks.

If your Kade policy permits a server-side check without device location, set skipLocation = true in PingOptions. Confirm that policy with Kade; this is not a network-estimate API.

Core API

These signatures are from the native Kotlin Core module, not its React Native bridge.

GeoFenceClient.getInstance(context: Context): GeoFenceClient Returns the process-wide singleton and stores the application context.
start(config: GeoFenceConfig, callback: (Result<Unit>) -> Unit) Configures Core, hydrates identity, derives the encryption key, and prepares the ping client.
ping(options: PingOptions, callback: (Result<PingResponse>) -> Unit) Sends one geofence check. It returns a failed result when the client has not been initialized.
startContinuousPing(options: PingOptions) Starts one continuous controller. It validates location availability synchronously unless skipLocation is true.
stopContinuousPing() Stops and releases the continuous controller.
currentFingerprint(): FingerprintResult Returns the local fingerprint containing visitorId.
sendFingerprintForTraining(userId: String, callback: (Result<JSONObject>) -> Unit) Sends an encrypted fingerprint and static markers to the configured server and returns its raw JSON object.
destroy() Stops continuous work, destroys Core resources, resets active markers, and clears client configuration and key material.

Fingerprint submission

val fingerprint = client.currentFingerprint()
println("Visitor ID: ${fingerprint.visitorId}")

client.sendFingerprintForTraining("player-123") { result ->
    result.fold(
        onSuccess = { json -> println("Server response: $json") },
        onFailure = { error -> println("Submission failed: $error") },
    )
}

Configuration

GeoFenceConfig

FieldType and defaultBehavior
apiTokenString, requiredWorkspace API token.
serverUrlString, https://api.videntrix.comAPI base URL.
regionIdInt?, nullDefault region; ping-level value overrides it.
signingSecretString?, nullEnables request signing when non-empty.
locationStrategyLocationStrategy, RACELocation retrieval strategy.
locationMaxAgeMsLong, 120,000Maximum preferred cached-fix age.
locationMaxStalenessRadiusDouble, 100.0Staleness radius used by location selection.
locationAccuracyLocationAccuracy, HIGHHIGH, BALANCED, or LOW.
debugBoolean, falseEnables verbose SDK logging.

PingOptions

val options = PingOptions(
    userId = "player-123",
    silent = false,
    skipLocation = false,
    source = "deposit",
    regionId = 123,
    email = "player@example.com",
    firstName = "Player",
    lastName = "One",
)

Every option has a default. source is a free-form flow tag, regionId overrides configuration for that ping, and identity fields are associated with the server-side client record.

Lifecycle and continuous checks

Register handlers before starting continuous checks and stop them when the protected flow ends. Core includes AndroidX process lifecycle support for its continuous controller.

client.onPingResponse = { response ->
    runOnUiThread {
        updateAccessState(response)
    }
}

client.onPingError = { error ->
    runOnUiThread {
        showVerificationError(error)
    }
}

try {
    client.startContinuousPing(
        PingOptions(
            userId = "player-123",
            source = "session",
        ),
    )
} catch (error: GeoFenceSdkException) {
    handleLocationSetupError(error.errorCode)
}

// When the verified flow ends:
client.stopContinuousPing()

// When disposing the entire integration:
client.destroy()

Response handling

Branch on blocked first, tolerate nullable fields, and keep failed Result values separate from server verdicts.

fun handle(response: PingResponse) {
    if (response.blocked == true) {
        denyAccess(
            code = response.code,
            message = response.message,
        )
        return
    }

    if (response.inside) {
        allowAccess(response.area?.name)
    } else {
        showOutsideRegion(response.distanceToBorder)
    }

    if (response.requestLocation == true) {
        // Revisit permission and location services before the next check.
    }
}
FieldTypeMeaning
insideBooleanWhether the server resolved the client inside the relevant geofence.
statusIntServer response status value.
message, codeString?Optional response detail and machine-readable code.
intervalIntServer-provided interval; model default is 10,000.
distanceToBorderDoubleDistance value returned by the server.
regionIdInt?Resolved region when present.
areaPingArea?Matched area with id and name.
blockedBoolean?Explicit block decision when supplied.
requestLocationBoolean?Whether the response asks the client to obtain location.
deviceTrustTtlInt?Optional device-trust lifetime supplied by the server.

Location setup errors

fun handleLocationSetupError(code: String) {
    when (code) {
        GeoFenceErrorCodes.LOCATION_PERMISSION_MISSING ->
            requestLocationPermission()
        GeoFenceErrorCodes.LOCATION_PERMISSION_PRECISE_REQUIRED ->
            explainPreciseLocation()
        GeoFenceErrorCodes.LOCATION_SERVICES_DISABLED ->
            promptToEnableLocationServices()
        else ->
            showVerificationUnavailable()
    }
}

Get the native Android package

Talk to Kade for SDK access, approved packaging, credentials, and integration guidance for your application.

Contact Kade