Skip to content

ably/ably-asset-tracking-android

Repository files navigation

Ably Asset Tracking SDKs for Android

.github/workflows/check.yml .github/workflows/emulate.yml .github/workflows/assemble.yml .github/workflows/docs.yml

Ably is the platform that powers synchronized digital experiences in realtime. Whether attending an event in a virtual venue, receiving realtime financial information, or monitoring live car performance data – consumers simply expect realtime digital experiences as standard. Ably provides a suite of APIs to build, extend, and deliver powerful digital experiences in realtime for more than 250 million devices across 80 countries each month. Organizations like Bloomberg, HubSpot, Verizon, and Hopin depend on Ably’s platform to offload the growing complexity of business-critical realtime data synchronization at global scale. For more information, see the Ably documentation.

Beta

This SDK is currently in beta and contains a subset of the anticipated final functionality. All APIs are subject to change.

The latest release of this SDK is available for download on Maven Central, with release notes available on the releases page.

Overview

Ably Asset Tracking SDKs provide an easy way to track multiple assets with realtime location updates powered by Ably realtime network and Mapbox Navigation SDK with location enhancement.

Ably Asset Tracking is:

  • easy to integrate - comprising two complementary SDKs with easy to use APIs, available for multiple platforms:
    • Asset Publishing SDK, for embedding in apps running on the courier's device
    • Asset Subscribing SDK, for embedding in apps running on the customer's observing device
  • extensible - as Ably is used as the underlying transport, you have direct access to your data and can use Ably integrations for a wide range of applications in addition to direct realtime subscriptions - examples include:
    • passing to a 3rd party system
    • persistence for later retrieval
  • built for purpose - the APIs and underlying functionality are designed specifically to meet the requirements of a range of common asset tracking use-cases

In this repository there are two SDKs for Android devices:

Example Apps

This repository also contains example apps that showcase how the Ably Asset Tracking SDKs can be used:

To build these apps from source you will need to specify credentials in Gradle properties.

The following secrets need to be injected into Gradle by either storing them in ~/.gradle/gradle.properties, or by using one of many other ways to do this:

  • ABLY_API_KEY: On your Ably accounts page, select your application, and paste an API Key from the API Keys tab (with relevant capabilities for either subscriber/ publisher). This API key needs the following capabilities: publish, subscribe, history and presence.
  • MAPBOX_DOWNLOADS_TOKEN: On the Mapbox Access Tokens page, create a token with the DOWNLOADS:READ secret scope.
  • MAPBOX_ACCESS_TOKEN: On the Mapbox Access Tokens page, create a token with all public scopes or use the default public token automatically generated for you.
  • GOOGLE_MAPS_API_KEY: Create an API key in Google Cloud, ensuring it has both Geolocation and Maps SDK for Android API.

To do this, create a file in your home folder if it doesn't exist already, ~/.gradle/gradle.properties, add the following code, and update the values:

ABLY_API_KEY=get_value_from_ably_dashboard
MAPBOX_DOWNLOADS_TOKEN=create_token_with_downloads_read_secret_scope
MAPBOX_ACCESS_TOKEN=create_token_with_all_public_scopes
GOOGLE_MAPS_API_KEY=create_api_key_with_geolocation_maps_sdk

Our Gradle build configuration also has support for uploading example apps to Firebase App Distribution with Crashlytics support - see CONTRIBUTING.md: Secrets Required to Distribute the Example Apps for more detail on this.

Usage

Please see our Upgrade / Migration Guide for notes on changes you need to make to your code to update the Ably Asset Tracking SDKs.

If you're not writing your application in Kotlin then please see our Java API Guide for notes on using the Ably Asset Tracking SDKs from Java code.

Maven / Gradle Dependencies

Kotlin users will want to add either publishing-sdk or subscribing-sdk, according to the needs of their project. Java users should add either publishing-sdk-java or subscribing-sdk-java. See Android Runtime Requirements for more details.

You need to declare the repository from which the Ably Asset Tracking SDK dependency will be installed. We support both Maven Central and GitHub Packages.

Downloading from Maven Central

We publish to Maven Central, which is the public repository that most users will choose to download the Ably Asset Tracking SDK from.

To install the dependency you need to make sure that you have declared the Maven Central repository in your Gradle build script:

repositories {
    mavenCentral()
}

Downloading from GitHub Packages

We publish to GitHub Packages for this repository, which is an alternative option for those who do not wish to download the Ably Asset Tracking SDK from Maven Central.

To install the dependency you will first need to authenticate to GitHub Packages. You have to get either a GITHUB_TOKEN or a "Personal Access Token" (with the read:packages permission). Then use that token to authenticate with the Ably Asset Tracking GitHub Packages repository in your Gradle build script:

repositories {
    maven {
        name = "Ably Asset Tracking"
        url = uri("https://maven.pkg.github.com/ably/ably-asset-tracking-android")
        credentials {
            username = '<GITHUB_USERNAME>'
            password = '<GITHUB_TOKEN>'
        }
    }
}

Downloading Transitive Dependencies from the Mapbox Repository

In order to resolve all dependencies required by the Ably Asset Tracking SDK, you will also need to authenticate with the Mapbox repository in your Gradle build script:

repositories {
    maven {
        name = "Mapbox"
        url 'https://api.mapbox.com/downloads/v2/releases/maven'
        authentication {
            basic(BasicAuthentication)
        }
        credentials {
            username = '<MAPBOX_USERNAME>'
            password = '<MAPBOX_DOWNLOADS_TOKEN>'
        }
    }
}

Adding Implementation Dependencies

Once you have configured Gradle to know where it can download dependencies from (see above), you can then add the Ably Asset Tracking dependency that you require in your Gradle build script:

dependencies {
    // Publishers, developing in Kotlin, will need the Publishing SDK
    implementation 'com.ably.tracking:publishing-sdk:1.7.0'

    // Subscribers, developing in Kotlin, will need the Subscribing SDK
    implementation 'com.ably.tracking:subscribing-sdk:1.7.0'

    // Subscribers, developing in Kotlin, can optionally use the UI utilities
    implementation 'com.ably.tracking:ui-sdk:1.7.0'
}

It's likely that for most application use cases you will need one or the other (i.e. either the Publishing SDK, or the Subscribing SDK).

Publishing SDK

The Asset Publishing SDK is used to get the location of the assets that need to be tracked.

Here is an example of how the Asset Publishing SDK can be used:

// Prepare Resolution Constraints for an asset that will be used in the Resolution Policy
val exampleConstraints = DefaultResolutionConstraints(
    DefaultResolutionSet( // this constructor provides one Resolution for all states
        Resolution(
            accuracy = Accuracy.BALANCED,
            desiredInterval = 1000L,
            minimumDisplacement = 1.0
        )
    ),
    proximityThreshold = DefaultProximity(spatial = 1.0),
    batteryLevelThreshold = 10.0f,
    lowBatteryMultiplier = 2.0f
)

// Prepare the default resolution for the Resolution Policy
val defaultResolution = Resolution(Accuracy.BALANCED, desiredInterval = 1000L, minimumDisplacement = 1.0)

// Initialise and Start the Publisher
val publisher = Publisher.publishers() // get the Publisher builder in default state
    // Required configuration
    .connection(ConnectionConfiguration(Authentication.basic(CLIENT_ID, ABLY_API_KEY))) // provide Ably configuration with credentials
    .map(MapConfiguration(MAPBOX_ACCESS_TOKEN)) // provide Mapbox configuration with credentials
    .androidContext(this) // provide Android runtime context
    .resolutionPolicy(DefaultResolutionPolicyFactory(defaultResolution, this)) // provide either the default resolution policy factory or your custom implementation
    .backgroundTrackingNotificationProvider(
      object : PublisherNotificationProvider {
        override fun getNotification(): Notification {
            // TODO: create the notification for location updates background service
        }
      },
      NOTIFICATION_ID
    )
    // Optional configuration
    .profile(RoutingProfile.DRIVING) // provide mode of transportation for better location enhancements
    .logHandler(object : LogHandler {
        override fun logMessage(level: LogLevel, message: String, throwable: Throwable?) {
          // TODO: log the message to internal or external loggers
        }
      })
    .rawLocations(false) // send raw location updates to subscribers
    .sendResolution(true) // send calculated trackable network resolution to subscribers
    .constantLocationEngineResolution(constantLocationEngineResolution) // provide a constant resolution for the GPS engine
    .vehicleProfile(VehicleProfile.CAR) // provide vehicle type for better location enhancements
    .locationSource(LocationSourceRaw.create(historyData)) // use an alternative location source for GPS locations
    // Create and start the publisher
    .start()

// Start tracking an asset
try {
    publisher.track(
        Trackable(
            trackingId, // provide a tracking identifier for the asset
            constraints = exampleConstraints // provide a set of Resolution Constraints
        )
    )
    // TODO handle asset tracking started successfully
} catch (exception: Exception) {
    // TODO handle asset tracking could not be started
}

Subscribing SDK

Asset Subscribing SDK is used to receive the location of the required assets.

Here is an example of how Asset Subscribing SDK can be used:

// Initialise and Start the Subscriber
val subscriber = Subscriber.subscribers() // Get an AssetSubscriber
    // Required configuration
    .connection(ConnectionConfiguration(Authentication.basic(CLIENT_ID, ABLY_API_KEY))) // provide Ably configuration with credentials
    .trackingId(trackingId) // provide the tracking identifier for the asset that needs to be tracked
    // Optional configuration
    .resolution( // request a specific resolution to be considered by the publisher
      Resolution(Accuracy.MAXIMUM, desiredInterval = 1000L, minimumDisplacement = 1.0)
    )
    .logHandler(object : LogHandler {
      override fun logMessage(level: LogLevel, message: String, throwable: Throwable?) {
        // TODO: log the message to internal or external loggers
      }
    })
    // Create and start the subscriber
    .start() // start listening for updates

// Listen for location updates
locations
    .onEach { locationUpdate -> print(locationUpdate) } // provide a function to be called when enhanced location updates are received
    .launchIn(scope) // coroutines scope on which the locations are received

// Listen for asset state changes
trackableStates
    .onEach { trackableState -> print(trackableState) } // provide a function to be called when the asset changes its state
    .launchIn(scope) // coroutines scope on which the statuses are received

// Request a different resolution when needed.
try {
    subscriber.resolutionPreference(Resolution(Accuracy.MAXIMUM, desiredInterval = 100L, minimumDisplacement = 2.0))
    // TODO change request submitted successfully
} catch (exception: Exception) {
    // TODO change request could not be submitted
}

Publisher presence

Publisher presence is provided as an experimental API for subscribers which you can use to get information about whether the publisher is online or offline. This API is not yet stable and may change in the future.

To use the API you must explicitly opt in to it by adding the following to your build.gradle file:

android {
    kotlinOptions {
        freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
    }
}

Then you can annotate your element of the desired scope with @OptIn(Experimental::class) annotation.

An example usage of the API is shown below:

subscriber.publisherPresence
    .onEach { isOnline -> print(isOnline) } // provide a function to be called when the asset's presnec is changed
    .launchIn(scope) // coroutines scope on which the statuses are received

UI utilities

Location Animator

The Location Animator helps to achieve smooth trackable animations on the subscriber side. For more information see our [updating guide](UPDATING.md#Animation module).

Resolution Policies

In order to provide application developers with flexibility when it comes to choosing their own balance between higher frequency of updates and optimal battery usage, we provide several ways for them to define the logic used to determine the frequency of updates:

  • by implementing a custom ResolutionPolicy - providing the greatest flexibility
  • by using the default ResolutionPolicy implementation - with the controls provided by DefaultResolutionPolicyFactory and DefaultResolutionConstraints

Using the Default Resolution Policy

The simplest way to control the frequency of updates is by providing parameters in the form of DefaultResolutionConstraints, assigned to the constraints property of the Trackable object:

val exampleConstraints = DefaultResolutionConstraints(
    DefaultResolutionSet(
        Resolution(
            accuracy = Accuracy.BALANCED,
            desiredInterval = 1000L, // milliseconds
            minimumDisplacement = 1.0 // metres
        )
    ),
    proximityThreshold = DefaultProximity(spatial = 1.0), // metres
    batteryLevelThreshold = 10.0f, // percent
    lowBatteryMultiplier = 2.0f
)

These values are then used in the default ResolutionPolicy, created by the DefaultResolutionPolicyFactory. This default policy implementation uses a simple decision algorithm to determine the Resolution for a certain state, relative to proximity threshold, battery threshold and the presence of subscribers.

Providing a Custom Resolution Policy Implementation

For the greatest flexibility it is possible to provide a custom implementation of the ResolutionPolicy interface. In this implementation the application developer can define which logic will be applied to their own parameters, including how resolution is to be determined based on the those parameters and requests from subscribers.

Please see DefaultResolutionPolicy implementation for an example.

Resources

Visit the Ably Asset Tracking documentation for a complete API reference and code examples.

You can also find reference documentation generated from the source code here.

Upgrade / Migration Guide

Please see our Upgrade / Migration Guide for notes on changes you need to make to your code to update it to use the latest version of these SDKs.

Useful links

Android Runtime Requirements

Kotlin Users

These SDKs require a minimum of Android API Level 21 at runtime for applications written in Kotlin.

Java Users

We also provide support for applications written in Java, however the requirements differ in that case:

Known Limitations

Using AAT together with Mapbox Navigation SDK

There are some limitations when you want to use both AAT SDK and Mapbox Navigation SDK in the same project.

Firstly, you have to exclude the notification module from Mapbox Navigation SDK dependency in your build.gradle file.

// The Ably Asset Tracking Publisher SDK for Android.
implementation ('com.ably.tracking:publishing-sdk:1.7.0')

// The Mapbox Navigation SDK.
implementation ('com.mapbox.navigation:android:2.11.0') {
    exclude group: "com.mapbox.navigation", module: "notification"
}

Secondly, you have to use AAT's MapboxNavigation configuration and make sure that a publisher is started before you try to use the Mapbox Navigation. As there can only be one MapboxNavigation instance per application, instead of creating a new instance you have to use the MapboxNavigationProvider to retrieve the instance created by AAT.

// Start a publisher before accessing Mapbox Navigation SDK
val publisher = Publisher.publishers()
    // add publisher configuration
    .start()

// Retrieve the instance created by the publisher
val mapboxNavigation = MapboxNavigationProvider.retrieve()

Because there is only one MapboxNavigation instance, both your app and AAT will use the same object. This means that there can be possible conflicts in usage that can lead to unexpected behaviour. Therefore, we do not advise using AAT in applications that already use Mapbox Navigation SDK.

SLF4J warning logs

AAT has a transitive dependency on the SLF4J library but we do not provide a default logger implementation. Because of that you can encounter below warning logs if you have not explicitly provided an implementation:

W/System.err: SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
W/System.err: SLF4J: Defaulting to no-operation (NOP) logger implementation

This is normal behaviour if no specific SLF4J logger implementation is provided. If you want to fix those warnings you have to provide a SLF4J logger implementation in your project.

Using multiple publishers at the same time

While it's possible to create multiple publishers and use them at the same time it is not advised. The reason is that the underlying location service is created when the publisher is created and it is destroyed when the publisher is stopped. Therefore, if you create a publisher after another publisher was created but not stopped, the new publisher's location engine configuration won't be applied, since the location service is already created and running. Additionally, since all publishers use the same location service instance, when one of them stops location updates (by removing its last trackable) they will stop for all of them.

All the above issues won't occur when you create a new publisher after stopping the previous one, which is the recommended way of using the SDK.

Contributing

For guidance on how to contribute to this project, see CONTRIBUTING.md.