Using the SDKs
This topic explains how to get started using the Ably Asset Tracking SDKs.
Supported platforms
There are two Asset Tracking SDKs, one for publishing and one for subscribing. The following platforms are supported:
- Android (Java and Kotlin) – publisher and subscriber SDK
- iOS (Objective-C and Swift) – publisher and subscriber SDK
- Web (JavaScript, with first class TypeScript support) – subscriber SDK
SDK repositories
The SDKs can be found in the following GitHub repositories:
Prerequisites
You need to have a suitable development environment installed, for example:
- Android – Android Studio or Gradle (requires Android SDK to be installed)
- iOS – Xcode
- JavaScript – any suitable environment of your choice
You also need to have suitable credentials for the various SDK components:
ABLY_API_KEY
– Your Ably API keyMAPBOX_ACCESS_TOKEN
– Mapbox public keyMAPBOX_DOWNLOADS_TOKEN
– Mapbox private key
On Android development systems you can set these values in your ~/.gradle/gradle.properties
file.
Installing the SDK
You can find information on installing the Ably Asset Tracking SDKs in the following resources:
Authentication
The client requires authentication in order to establish a connection with Ably. There are three methods that can be used:
- Basic authentication
- Token authentication
- JWT authentication
Usually a client will use either token or JWT authentication, as basic authentication would require exposing the API keys on the client.
Examples of establishing a connection using the three methods are given in the following sections. While the examples shown are for either the Publishing or Subscribing SDK, you can use the same approach for both SDKs.
Basic Authentication
The following example demonstrates establishing a connection using basic authentication:
val publisher = Publisher.publishers() // get the Publisher builder in default state
.connection(ConnectionConfiguration(Authentication.basic(CLIENT_ID, ABLY_API_KEY)))
CopyCopied!
This method should not be used on a client however, as it exposes the API key.
You can read more about basic authentication in our documentation.
Token Authentication
The following example demonstrates establishing a connection using token authentication:
val publisher = Publisher.publishers() // get the Publisher builder in default state
.connection(ConnectionConfiguration(Authentication.tokenRequest(CLIENT_ID) { requestParameters ->
// get TokenRequest from your server
getTokenRequestFromAuthServer(requestParameters); // customer implements this function
}))
CopyCopied!
You can read more about token authentication in our documentation.
JWT Authentication
The following example demonstrates establishing a connection using JWT authentication:
val publisher = Publisher.publishers() // get the Publisher builder in default state
.connection(ConnectionConfiguration(Authentication.jwt(CLIENT_ID) { tokenParameters ->
// get JWT from your server
getJWTFromAuthServer(tokenParameters); // customer implements this function
}))
CopyCopied!
You can read more about JWT authentication in our documentation.
Using the Publishing SDK
Common operations you need to carry out on the publisher include:
- Initialize the publisher.
- Start tracking an asset.
- Stop tracking an asset.
- Set the resolution constraints on an asset.
Initializing the Publisher
During initialization of the publisher various methods can be called to configure the Builder
interface of the Publisher
.
The required methods are:
- connection
- Called to provide Ably connection information, such as API keys, and any other configuration parameters as needed.
- map
- Called to provide Mapbox configuration, such as API keys, any other configuration parameters as needed.
- androidContext
- Called to provide the Android runtime context (on Android only).
- resolutionPolicy
- Sets the policy factory to be used to define the target resolution for publishers created from this builder.
- backgroundTrackingNotificationProvider
- Sets the notification that will be displayed for the background tracking service. Please note that this notification will be removed when you call the
stop
method (on Android only). - start
- Creates a
Publisher
and starts publishing. The returned publisher instance does not start in a state whereby it is actively tracking anything. If tracking is required from the outset then thePublisher.track
orPublisher.add
method must be subsequently called. In order to detect the device’s locationACCESS_COARSE_LOCATION
orACCESS_FINE_LOCATION
permission must be granted.
The optional methods are:
- profile
- Called to set the means of transport being used for the initial state of publishers created from this builder. If not set then the default value is
RoutingProfile.DRIVING
. - locationSource
- Sets the location source to be used instead of the GPS. The location source provides location updates for the
Publisher
. - logHandler
- Sets the log handler (experimental API).
- rawLocations
- Enables sending of raw location updates. This should only be enabled for diagnostics. In the production environment this should be always disabled. By default this is disabled (experimental API).
- sendResolution
- Enables sending of calculated resolutions. By default this is enabled.
- rawHistoryDataCallback
- Specifies a callback that will be called with the filepath of raw history data from the Navigation SDK component. This will be probably removed in the future. Do not use this in the production environment (experimental API).
- constantLocationEngineResolution
- Enables using a constant location engine resolution. If the
resolution
is not null then instead of usingResolutionPolicy
to calculate a dynamic resolution for the location engine theresolution
will be used as the location engine resolution. If theresolution
is null then the constant resolution is disabled and the location engine resolution will be calculated by theResolutionPolicy
. By default this is disabled. - vehicleProfile
- Set the type of vehicle being used by the publisher user. If not set then the default value is
VehicleProfile.CAR
.
Other publisher methods of note are:
- track
- Adds a
Trackable
object and makes it the actively tracked object, meaning that the state of theactive
field will be updated to this object, if that wasn’t already the case. If this object was already in this publisher’s tracked set then this method only serves to change the actively tracked object. This method returns aStateFlow
that represents theTrackableState
of the addedTrackable
. - add
- Adds a
Trackable
object, but does not make it the actively tracked object, meaning that the state of theactive
field will not change. If this object was already in this publisher’s tracked set then this method does nothing. This method returns aStateFlow
that represents theTrackableState
of the addedTrackable
. - remove
- Removes a
Trackable
object if it is known to this publisher, otherwise does nothing and returns false. If the removed object is the current activelyactive
object then that state will be cleared, meaning that for another object to become the actively tracked delivery then thetrack
method must be subsequently called. - getTrackableState
- Returns a trackable state flow representing the
TrackableState
for an already addedTrackable
. - stop
- Stops this publisher from publishing locations. Once a publisher has been stopped, it cannot be restarted. Please note that calling this method will remove the notification provided by
Builder.backgroundTrackingNotificationProvider
.
Publisher properties of note:
- active
- The actively tracked object, being the
Trackable
object whose destination will be used for location enhancement, if available. This state can be changed by calling thetrack
method. - routingProfile
- The active means of transport for this publisher.
- locations
- The shared flow emitting location values when they become available.
- trackables
- The shared flow emitting all trackables tracked by the publisher.
- locationHistory
- The shared flow emitting trip location history when it becomes available.
In the following sections you will learn how to set up some resolution constraints and then start publishing.
The following code example creates some example resolution constraints
// 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
)
CopyCopied!
The next step is create a default resolution to be used:
// Prepare the default resolution for the Resolution Policy
val defaultResolution = Resolution(Accuracy.BALANCED,
desiredInterval = 1000L,
minimumDisplacement = 1.0)
CopyCopied!
Once these are created you can then initialize the publisher with the constraints and default resolution, and start the publisher:
// Initialize 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()
CopyCopied!
Start tracking
You can start tracking an asset (a Trackable
), by calling the track
or add
method of the publisher. A Trackable
is composed of the following:
- trackingID
- The tracking identifier for the asset.
- destination
- A
Destination
object, which is a latitude and longitude. - constraints
- A set of resolution constraints.
The track
method adds a Trackable
object, and makes it the actively tracked object, meaning that the state of the active
field will be updated to this object, if that wasn’t already the case. If this object was already in this publisher’s tracked set, then this method only serves to change the actively tracked object. Takes a trackable
as a parameter, which is the object to be added to this publisher’s tracked set, if it’s not already there, and which will be made the actively tracked object.
The add
method adds a Trackable
object, but does not make it the actively tracked object, meaning that the state of the active
field will not change. If this object was already in this publisher’s tracked set then this method does nothing. Takes a trackable
as a parameter, which is the object to be added to this publisher’s tracked set, if it’s not already there.
Both of these methods return a StateFlow
that represents the TrackableState
of the added Trackable
.
The following code example demonstrates how to start tracking an asset:
// Start tracking an asset
try {
publisher.track(
Trackable(
trackingId, // provide a tracking identifier for the asset
destination, // provide a destination as a latitude and longitude
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
}
CopyCopied!
Stop tracking
You can stop tracking a trackable (asset) that is registered with the publisher using the remove
method, as shown in the following code:
publisher.remove(trackable)
CopyCopied!
Using the Subscribing SDK
Common operations you will need to carry out on the subscriber include:
- Initialize the subscriber.
- Listen for location updates sent from from the publisher.
- Listen for asset status updates sent from the publisher.
- Request a different resolution to be sent from the publisher.
Initializing the Subscriber
During initialization of the subscriber various methods can be called to configure the Subscriber
.
The required methods are:
- connection
- Called to provide Ably connection information, such as API keys, and any other configuration parameters as needed.
- trackingId
- Sets the asset to be tracked, using the unique tracking identifier of the asset.
The optional methods are:
- resolution
- Request a specific resolution of updates to be requested from the remote publisher.
- logHandler
- Sets the log handler (experimental API).
- start
- Creates a
Subscriber
and starts listening for location updates.
The following code example demonstrates initializing and starting the subscriber:
// Initialize 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
CopyCopied!
Subscribe to updates
You can subscribe to updates from the publisher, specifying a function that is called when each update is received. This is shown in the following example:
// Listen for location updates
subscriber.locations
.onEach { } // provide a function to be called when enhanced location updates are received
.launchIn(scope) // coroutines scope on which the locations are received
CopyCopied!
You can also subscribe to raw locations if these have been enabled in the publisher (see the rawLocations(true)
builder method). Once the raw locations are enabled by the publisher, you can access them using the subscriber’s rawLocations
flow, just as you do with the regular locations
flow. This feature is only ever used for debugging purposes, and should not be used in a production situation.
Note that you can also configure the update event handler during subscriber initialization
You can subscribe to asset state changes from the publisher, specifying a function that is called when each state change is received. This is shown in the following example:
// Listen for asset state changes
subscriber.trackableStates
.onEach { } // provide a function to be called when the asset changes its state
.launchIn(scope) // coroutines scope on which the statuses are received
CopyCopied!
Note that you can also configure the asset state change event handler during subscriber initialization
The subscriber can always request a different resolution preference by calling the resolutionPreference
method, passing in the required Resolution
. This is shown in the following example:
// 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
}
CopyCopied!
Stop receiving updates
Stop receiving updates from the publisher by calling the stop()
method:
await subscriber.stop()
CopyCopied!
Subscriber properties that are useful include:
- resolutions
- The shared flow emitting the publisher’s resolution values when they become available.
- nextLocationUpdateIntervals
- The shared flow emitting the estimated next location update intervals in milliseconds when they become available.
To observe the actual resolution, as opposed to the requested resolution:
subscriber.resolutions
.onEach { publisherResolution -> }
.launchIn(scope)
CopyCopied!
For the smooth location animation mechanism a flow is exposed:
subscriber.nextLocationUpdateIntervals
.onEach { interval -> }
.launchIn(scope)
CopyCopied!
See also the SDK reference docs for more Subscriber properties.
Location animator
The location animator is an extension for the subscriber that provides smooth location updates animation.
The location animator creates animation steps from location updates that are provided by the Subscriber SDK. These steps are then applied to an ongoing animation.
The animation duration is then recalculated as the sum of location update interval (the time to next location update from the publisher) and the intentional delay. The intentional delay allows for network issues or delays in the location updates. For example, if the intentional delay was set to two seconds, then a two second buffer will help the animation remain smooth and continuous, even if the publisher did not send a location update within the specified time, with a two second margin.
The location animator then recalculates each step duration to evenly take up the animation duration, so the overall animation of the trackable is smooth.
Each step is animated at 60 FPS, and this applies to the map marker.
More information can also be found in the location animator reference docs.
Create an instance
Creating an instance of the location animator:
val locationAnimator: LocationAnimator = CoreLocationAnimator(
intentionalAnimationDelayInMilliseconds,
animationStepsBetweenCameraUpdates
)
CopyCopied!
There are two optional parameters:
- intentionalAnimationDelayInMilliseconds
- The higher the delay, the more buffer there is to allow for unexpected delays of location updates from the publisher. Also moves the actual position of the trackable backwards in time. The higher the setting the more network issues and delays are allowed for, but the position updates will be less realtime in nature. By default this is set to two seconds, but you should test what value works best for your use case.
- animationStepsBetweenCameraUpdates
- By default, when a step is being animated, the camera position is also updated. But this can be changed so that the camera position updates, for example, on every five location updates.
Stop the location animator
As with other components, you can stop the location animator with the stop
method:
locationAnimator.stop()
CopyCopied!
The location animator should be stopped when no longer used to conserve resources.
The location animator exposes a positions flow:
locationAnimator.positionsFlow
.onEach { trackablePosition -> }
.launchIn(scope)
CopyCopied!
This provides data you can use to move your map marker on the map, or when you want to move your trackable in the screen. This flow is updated at 60 FPS when the animation is ongoing.
A camera positions flow is also provided:
locationAnimator.cameraPositionsFlow
.onEach { cameraPosition -> }
.launchIn(scope)
CopyCopied!
This provides data you can use to move your camera whenever a new camera position is read.
To use the animator you need to provide it with some information:
locationAnimator.animateLocationUpdate(locationUpdate, expectedIntervalBetweenLocationUpdatesInMilliseconds)
CopyCopied!
- locationUpdate
- The location update from the subscriber.
- expectedIntervalBetweenLocationUpdatesInMilliseconds
- Obtained from subscriber API, expected time within which the publisher is expected to send a location update.