Android SDK

The Radar SDK abstracts away cross-platform differences between location services, allowing you to add geofencing, location tracking, trip tracking, geocoding, and search to your apps with just a few lines of code.

Learn how to integrate the Android SDK below. You can also see the source and a detailed SDK reference on GitHub.

Install SDK

The best way to add the SDK to your project is via Gradle in Android Studio.

For details on the latest SDK releases, see the releases page on GitHub. You can also star ⭐️ and watch 👀 the repo.

The SDK is small and typically adds less than 500 KB to your compiled app.

Add the SDK to the dependencies section of your app's build.gradle file:

dependencies {
    implementation 'io.radar:sdk:3.1.+'
}

Add manually

You can also add the SDK to your project manually. Download the current release and unzip the package. The package contains an aar file. In Android Studio, add the SDK as a module using File > New Module > Import .JAR/.AAR Package.

Dependencies

The SDK depends on AndroidX and Play Services Location version 17.1.0 and higher. These libraries will be automatically included as transitive dependencies by Gradle. Learn more about managing dependencies in Gradle here.

If you haven't already configured your project for Play Services, follow the instructions here.

The SDK currently supports API level 16 and higher.

Initialize SDK

When your app starts, initialize the SDK with your publishable API key, found on the Settings page.

Use your Test Publishable key for testing and non-production environments. Use your Live Publishable key for production environments.

Note that you should always use your publishable API keys, which are restricted in scope, in the SDK. Do not use your secret API keys, which are unrestricted in scope, in any client-side code.

Java Kotlin

import io.radar.sdk.Radar;

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        Radar.initialize(this, "prj_test_pk...");
    }

}
import io.radar.sdk.Radar

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        Radar.initialize(this, "prj_test_pk...")
    }

}

Request permissions

Radar respects standard Android location permissions.

For foreground tracking or trip tracking with continuous mode, Radar requires the ACCESS_FINE_LOCATION permission. This permission is automatically added by the SDK manifest along with the INTERNET, ACCESS_NETWORK_STATE, and RECEIVE_BOOT_COMPLETED permissions.

For background tracking or geofencing with responsive mode, and if targeting API level 29 or higher, Radar also requires the new ACCESS_BACKGROUND_LOCATION permission. You must add the ACCESS_BACKGROUND_LOCATION permission to your manifest manually:

<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.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

</manifest>
If your app already requests the necessary location permissions, you can skip this step.

To request foreground and background permissions:

Java Kotlin

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION }, 0);
} else {
    ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, 0);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION), 0)
} else {
    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
}
Build and run the app to make sure permissions prompts are displayed!
Not seeing permissions prompts? First, make sure you've added permissions to your manifest. Second, check your device settings to make sure you haven't already granted location permissions.

Foreground tracking

Once the user has granted foreground permissions, you can track the user's location in the foreground.

To track the user's location in the foreground, call:

Java Kotlin

Radar.trackOnce(new RadarCallback() {
    @Override
    public void onComplete(RadarStatus status, Location location, RadarEvent[] events, RadarUser user) {
        // do something with location, events, user
    }
});
Radar.trackOnce { status, location, events, user ->
    // do something with location, events, user
}

You may provide an optional instance of RadarCallback with an implementation of onComplete() that receives the request status, the user's location, the events generated, if any, and the user. The request status can be:

  • RadarStatus.SUCCESS: success
  • RadarStatus.ERROR_PUBLISHABLE_KEY: SDK not initialized
  • RadarStatus.ERROR_PERMISSIONS: location permissions not granted
  • RadarStatus.ERROR_LOCATION: location services error or timeout (10 seconds)
  • RadarStatus.ERROR_NETWORK: network error or timeout (10 seconds)
  • RadarStatus.ERROR_BAD_REQUEST: bad request (missing or invalid params)
  • RadarStatus.ERROR_UNAUTHORIZED: unauthorized (invalid API key)
  • RadarStatus.ERROR_PAYMENT_REQUIRED: payment required (organization disabled or usage exceeded)
  • RadarStatus.ERROR_FORBIDDEN: forbidden (insufficient permissions)
  • RadarStatus.ERROR_NOT_FOUND: not found
  • RadarStatus.ERROR_RATE_LIMIT: too many requests (rate limit exceeded)
  • RadarStatus.ERROR_SERVER: internal server error
  • RadarStatus.ERROR_UNKNOWN: unknown error
Build and run the app, then find your user on the Users page! To trigger an event, you'll need to create a geofence if you haven't already. Not seeing your user on the Users page? Check status in the callback to see what went wrong.

Background tracking for geofencing

Once you have initialized the SDK and the user has authorized background permissions, you can start tracking the user's location in the background.

The SDK supports custom tracking options as well as three presets.

For geofencing, we recommend using RadarTrackingOptions.RESPONSIVE. This preset detects whether the device is stopped or moving. When moving, it tells the SDK to send location updates to the server every 2-3 minutes. When stopped, it tells the SDK to shut down to save battery. Once stopped, the device will need to move more than 100 meters to wake up and start moving again.

Note that location updates may be delayed significantly by Doze Mode, App Standby, and Background Location Limits, or if the device has connectivity issues, low battery, or wi-fi disabled. In practice, location updates may be sent to the server every 5-10 minutes while moving, faster if you power on the screen or if other apps are requesting location, and slower otherwise. These restrictions apply to all apps using location services, not just Radar.
Though we recommend using presets for most use cases, you can modify the presets. See the tracking options reference.

To start tracking for geofencing, call:

Java Kotlin

Radar.startTracking(RadarTrackingOptions.RESPONSIVE);
Radar.startTracking(RadarTrackingOptions.RESPONSIVE)

To stop tracking (e.g., when the user logs out), call:

Java Kotlin

Radar.stopTracking();
Radar.stopTracking()

You only need to call these methods once, as these settings will be persisted across app sessions.

To test, go for a walk or a drive! Not seeing location updates or events? Remember that, once stopped, the device will need to move more than 100 meters to wake up and start moving again. Also, check your device settings to make sure you've granted background location permissions.
Don't forget! You can always find your user on the Users page or events on the Events page. To trigger an event, you'll need to create a geofence if you haven't already.

Background tracking for trips

For trips, we recommend using RadarTrackingOptions.continuous. This preset tells the SDK to send location updates to the server every 30 seconds, regardless of whether the device is moving.

To avoid Background Location Limits, you should also start a foreground service with a notification while tracking. Make sure to include android:foregroundServiceType="location" in your manifest. If you start a foreground service with a notification, only foreground permissions are required for tracking.

To start tracking for trips, call:

Java Kotlin

startForegroundService();
Radar.startTracking(RadarTrackingOptions.CONTINUOUS);
startForegroundService()
Radar.startTracking(RadarTrackingOptions.CONTINUOUS)

To stop tracking (e.g., when the user logs out), call:

Java Kotlin

Radar.stopTracking();
stopForegroundService();
Radar.stopTracking()
stopForegroundService()

Learn more about starting, completing, and canceling trips in the trip tracking documentation.

Don't forget! You can always find your user on the Users page or events on the Events page. To trigger an event, you'll need to create a geofence or start a trip if you haven't already.

Mock tracking for testing

Can't go for a walk or a drive? You can simulate a sequence of location updates. For example, to simulate a sequence of 10 location updates every 3 seconds by car from an origin to a destination, call:

Java Kotlin

Location origin = new Location("mock");
origin.setLatitude(40.78382);
origin.setLongitude(-73.97536);

Location destination = new Location("mock");
destination.setLatitude(40.70390);
destination.setLongitude(-73.98670);

Radar.mockTracking(
    origin,
    destination,
    Radar.RadarRouteMode.CAR,
    10,
    3,
    new RadarCallback() {
        @Override
        public void onComplete(RadarStatus status, Location location, RadarEvent[] events, RadarUser user) {
            // do something with location, events, user
        }
    }
);
 val origin = Location("mock")
origin.latitude = 40.78382
origin.longitude = -73.97536

val destination = Location("mock")
destination.latitude = 40.70390
destination.longitude = -73.98670

Radar.mockTracking(
    origin,
    destination,
    Radar.RadarRouteMode.CAR,
    10,
    3
) { status, location, events, user ->
    // do something with location, events, user
}
Don't forget! You can always find your user on the Users page or events on the Events page. To trigger an event, you'll need to create a geofence if you haven't already.

Listening for events with a receiver

To listen for events, location updates, and errors client-side, create a class that extends RadarReceiver. Then, register the receiver by adding a receiver element to the application element in your manifest:

<application android:label="@string/app_name">

  <receiver
      android:name=".MyRadarReceiver"
      android:enabled="true"
      android:exported="false">
      <intent-filter>
          <action android:name="io.radar.sdk.RECEIVED" />
      </intent-filter>
  </receiver>

</application>

Your receiver should implement the following:

Java Kotlin

public class MyRadarReceiver extends RadarReceiver {

    @Override
    public void onEventsReceived(Context context, RadarEvent[] events, RadarUser user) {
        // do something with events, user
    }

    @Override
    public void onLocationUpdated(Context context, Location location, RadarUser user) {
        // do something with location, user
    }

    @Override
    public void onClientLocationUpdated(Context context, Location location, boolean stopped, RadarLocationSource source) {
        // do something with location, stopped, source
    }

    @Override
    public void onError(Context context, RadarStatus status) {
        // do something with status
    }

}
class MyRadarReceiver: RadarReceiver() {

    override fun onEventsReceived(context: Context, events: Array<RadarEvent>, user: RadarUser) {
        // do something with events, user
    }

    override fun onLocationUpdated(context: Context, location: Location, user: RadarUser) {
        // do something with location, user
    }

    override fun onClientLocationUpdated(context: Context, location: Location, stopped: Boolean, source: RadarLocationSource) {
        // do something with location, stopped, source
    }

    override fun onError(context: Context, status: RadarStatus) {
        // do something with status
    }

}
To listen for events server-side instead, add a webhook or enable an integration.

Manual tracking

If you want to manage location services yourself, you can manually update the user's location instead by calling:

Java Kotlin

Radar.trackOnce(
    location,
    new RadarCallback() {
        @Override
        public void onComplete(RadarStatus status, Location location, RadarEvent[] events, RadarUser user) {
            // do something with location, events, user
        }
    }
);
Radar.trackOnce(location) { status, location, events, user ->
    // do something with location, events, user
}

where location is a Location instance with a valid latitude, longitude, and accuracy.

Identify user

Until you identify the user, Radar will automatically identify the user by deviceId (Android ID).

To identify the user when logged in, call:

Java Kotlin

Radar.setUserId(userId);
Radar.setUserId(userId)

where userId is a stable unique ID for the user.

Do not send any PII, like names, email addresses, or publicly available IDs, for userId. See privacy best practices for more information.

To set an optional dictionary of custom metadata for the user, call:

Java Kotlin

Radar.setMetadata(metadata);
Radar.setMetadata(metadata)

where metadata is a JSONObject with up to 16 keys and values of type string, boolean, or number.

Finally, to set an optional description for the user, displayed in the dashboard, call:

Java Kotlin

Radar.setDescription(description);
Radar.setDescription(description)

You only need to call these methods once, as these settings will be persisted across app sessions.

Other APIs

The Android SDK also exposes APIs for beacons, anonymous context, geocoding, search, and distance.

Beacons

To range and monitor beacons, you must add Bluetooth permissions to your manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

</manifest>

These are non-dangerous permissions and will not show a permissions prompt.

To range beacons in the foreground, call:

Java Kotlin

Radar.trackOnce(RadarTrackingOptionsDesiredAccuracy.HIGH, true, new RadarCallback() {
    @Override
    public void onComplete(RadarStatus status, Location location, RadarEvent[] events, RadarUser user) {
        // do something with user.beacons
    }
});
Radar.trackOnce(RadarTrackingOptionsDesiredAccuracy.HIGH, true) { status, location, events, user ->
    // do something with user.beacons
}

To monitor beacons in the background, update your tracking options:

Java Kotlin

RadarTrackingOptions trackingOptions = RadarTrackingOptions.RESPONSIVE;
trackingOptions.beacons = true;
Radar.startTracking(trackingOptions);
val trackingOptions = RadarTrackingOptions.RESPONSIVE
trackingOptions.beacons = true
Radar.startTracking(trackingOptions)

Learn more about beacons.

Get location

Get a single location update without sending it to the server:

Java Kotlin

Radar.getLocation(new RadarLocationCallback() {
    @Override
    public void onComplete(RadarStatus status, Location location, boolean stopped) {
        // do something with location
    }
});
Radar.getLocation { status, location, stopped ->
    // do something with location
}

Context

With the context API, get context for a location without sending device or user identifiers to the server:

Java Kotlin

Radar.getContext(new RadarContextCallback() {
    @Override
    public void onComplete(RadarStatus status, Location location, RadarContext context) {
        // do something with context
    }
});
Radar.getContext { status, location, context ->
    // do something with context
}

Geocoding

With the forward geocoding API, geocode an address, converting address to coordinates:

Java Kotlin

Radar.geocode(
    "20 jay street brooklyn ny", // query
    new RadarGeocodeCallback() {
        @Override
        public void onComplete(RadarStatus status, RadarAddress[] addresses) {
            // do something with addresses
        }
    }
);
Radar.geocode("20 jay street brooklyn ny") { status, addresses ->
    // do something with addresses
}

With the reverse geocoding API, reverse geocode a location, converting coordinates to address:

Java Kotlin

Radar.reverseGeocode(
    location,
    new RadarGeocodeCallback() {
        @Override
        public void onComplete(RadarStatus status, RadarAddress[] addresses) {
            // do something with addresses
        }
    }
);
Radar.reverseGeocode(location) { status, addresses ->
    // do something with addresses
}

With the IP geocoding API, geocode the device's current IP address, converting IP address to city, state, and country:

Java Kotlin

Radar.ipGeocode(new RadarIpGeocodeCallback() {
    @Override
    public void onComplete(RadarStatus status, RadarAddress address) {
        // do something with address
    }
});
Radar.ipGeocode { status, address ->
    // do something with address
}

With the autocomplete API, autocomplete partial addresses and place names, sorted by relevance:

Java Kotlin

Radar.autocomplete(
    "brooklyn roasting", // query
    near,
    10, // limit
    new RadarGeocodeCallback() {
        @Override
        public void onComplete(RadarStatus status, RadarAddress[] addresses) {
            // do something with addresses
        }
    }
);
Radar.autocomplete(
    "brooklyn roasting", // query
    near,
    10 // limit
) { status, addresses ->
    // do something with addresses
}

With the geofence search API, search for geofences near a location, sorted by distance:

Java Kotlin

Radar.searchGeofences(
    near,
    1000, // radius (meters)
    {"store"}, // tags
    nil, //metadata
    10, // limit
    new RadarSearchGeofencesCallback() {
        @Override
        public void onComplete(RadarStatus status, Location location, RadarGeofence[] geofences) {
            // do something with geofences
        }
    }
);
Radar.searchGeofences(
    near,
    1000, // radius (meters)
    arrayOf("store"), // tags
    nil, // metadata
    10 // limit
) { status, location, geofences ->
    // do something with geofences
}

With the places search API, search for places near a location, sorted by distance:

Java Kotlin

Radar.searchPlaces(
    near,
    1000, // radius (meters)
    {"starbucks"}, // chains
    null, // categories
    null, //groups
    10, // limit
    new RadarSearchPlacesCallback() {
      @Override
      public void onComplete(RadarStatus status, Location location, RadarPlace[] places) {
          // do something with places
      }
    }
);
Radar.searchPlaces(
    near,
    1000, // radius (meters)
    arrayOf("starbucks"), // chains
    null, // categories
    null, // groups
    10 // limit
) { status, location, places ->
    // do something with places
}

Distance

With the distance API, calculate the travel distance and duration from an origin to a destination:

Java Kotlin

Radar.getDistance(
    origin,
    destination,
    EnumSet.of(RadarRouteMode.FOOT, RadarRouteMode.CAR),
    RadarRouteUnits.IMPERIAL,
    new RadarRouteCallback() {
        @Override
        public void onComplete(RadarStatus status, RadarRoutes routes) {
            // do something with routes
        }
    }
);
Radar.getDistance(
    origin,
    destination,
    EnumSet.of(RadarRouteMode.FOOT, RadarRouteMode.CAR),
    RadarRouteUnits.IMPERIAL
) { status, routes ->
  // do something with routes
}

Matrix

With the matrix API, calculate the travel distance and duration between multiple origins and destinations for up to 25 routes:

Java Kotlin

Radar.getMatrix(
    origins,
    destinations,
    RadarRouteMode.CAR,
    RadarRouteUnits.IMPERIAL,
    new RadarRouteCallback() {
        @Override
        public void onComplete(RadarStatus status, RadarRouteMatrix matrix) {
            // do something with matrix.routeBetween(originIndex, destinationIndex)
        }
    }
);
Radar.getMatrix(
    origins,
    destinations,
    RadarRouteMode.CAR,
    RadarRouteUnits.IMPERIAL
) { status, routes ->
  // do something with matrix.routeBetween(originIndex, destinationIndex)
}