Skip to content

Integrating the Tapkey Mobile SDK for Android

Latest Version

The latest version of the Tapkey Mobile SDK for Android is


This documentation refers to version and later. This documentation may not be appropriate for older versions. Take a look at the upgrade section for upgrading to the latest version of the Tapkey Mobile SDK.

Sample App

A Tapkey Mobile SDK sample app for Android is available on GitHub.


The Tapkey Mobile SDK has been tested and compiled against Android API level 31.

Adding Dependencies

The Tapkey Mobile SDK is published in a private Maven repository. To tell Gradle where to find the artifacts, add as repository and add the latest version of as dependency to the project's build.gradle file.

repositories {
    maven { url "" }

dependencies {
    implementation ''

android {

Bootstrapping the SDK

The Tapkey Mobile SDK expects the Application class of the target App to implement the TapkeyAppContext interface, which returns a singleton instance of the AndroidTapkeyServiceFactory. The singleton should be created in the Application.onCreate() method using the TapkeyServiceFactoryBuilder. As the Tapkey Mobile SDK needs to communicate with the Tapkey Trust Service, the android.permission.INTERNET permission has to be added to the App Manifest.

  1. Let the Application implement TapkeyAppContext.
  2. Use TapkeyServiceFactoryBuilder to build an instance of TapkeyServiceFactory in Application.onCreate().
  3. Implement getTapkeyServiceFactory() which returns the TapkeyServiceFactory singleton.

public class App extends Application implements TapkeyAppContext {

     * The TapkeyServiceFactory holds all required services.
    private TapkeyServiceFactory tapkeyServiceFactory;

    public void onCreate() {

         * Create an instance of TapkeyServiceFactory. Tapkey expects that a single instance of
         * TapkeyServiceFactory exists inside an application that can be retrieved via the
         * Application instance's getTapkeyServiceFactory() method.
        TapkeyServiceFactoryBuilder b = new TapkeyServiceFactoryBuilder(this);
        this.tapkeyServiceFactory =;

    public TapkeyServiceFactory getTapkeyServiceFactory() {
        return tapkeyServiceFactory;
4. Add <uses-permission android:name="android.permission.INTERNET"/> to AndroidManifest.xml.

<manifest  >    <uses-permission android:name="android.permission.INTERNET"/>
    <application  ></manifest>

Polling for Data

To allow unlocking during the device has bad or no network connection, it is essential to frequently fetch and cache data.

Tapkey provides an Android BroadcastReceiver that automatically caches such data frequently in background. To enable this feature, PollingScheduler.register() has to be called in Application.onCreate().

public class App extends Application implements TapkeyAppContext{

    public void onCreate() {


         * Register polling scheduler.
         * The PollingScheduler polls for notifications from the Tapkey Trust Service. Note that
         * the job ID must be unique across the app. The default interval is 8 hours.
        PollingScheduler.register(this, 1, PollingScheduler.DEFAULT_INTERVAL);

Using the SDK's PollingScheduler class requires the android.permission.RECEIVE_BOOT_COMPLETED permission. Otherwise, background polling won't work after the device reboots.

<manifest  >    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <application  ></manifest>

While the PollingScheduler is periodically looking for updated keys, it is recommended to poll for updates whenever updates are expected. NotificationManager#pollForNotificationsAsync() can be used to manually check for updates. Manually checking for updated keys is recommended in the following situations:

  • After a user has been logged in to the SDK.
  • After a new grant has been issued for a logged-in user.
  • After a grant that affects the current user has been modified or revoked.
  • After grants, affecting locks that one of the logged-in users has access to, have been revoked. The logged-in users might receive updated revocation information.

Logging In Users

The SDK takes a Tapkey access token to log in a user. Retrieving such an access token is part of the implementing application. Usually, either the Token Exchange or Authentication Code with PKCE grant type will be used. For more information about authenticating against Tapkey, take a look at the authentication section.

Once an access token has been obtained, it can be used to log in a user as outlined below:

UserManager userManager = tapkeyServiceFactory.getUserManager();
userManager.logInAsync("eyJhbGciOiJSUz…O-YbBq8F7086rQi-kEbERp4dA3r0WonpHnmYcXEnA", ct)
    .continueOnUi(userId -> {
        // User logged in, use user's ID here.

Subsequent calls to userManager.getUsers() will yield a list of IDs of all logged-in users. In most applications, only one user will be logged in at a time.

String userId = userManager.getUsers().get(0);
// Fetch information about the user using the ID

Note that a token refresh handler must be registered with the TapkeyServiceFactory prior to logging in any users. This can be done using the TapkeyServiceFactoryBuilder's setTokenRefreshHandler(TokenRefreshHandler tokenRefreshHandler) method.

// In Application.onCreate():
TapkeyServiceFactoryBuilder b = new TapkeyServiceFactoryBuilder();
b.setTokenRefreshHandler(new TokenRefreshHandler() {
    public Promise<String> refreshAuthenticationAsync(String userId, CancellationToken ct) {
        PromiseSource res = new PromiseSource();

            newAccessToken -> res.setResult(newAccessToken),
            exception -> {
                if (isPersistentError(exception))
                    throw new TkException (AuthenticationHandlerErrorCodes.TokenRefreshFailed);

        return res.getPromise();

    public void onRefreshFailed(String userId) {
         * Will be called if re-authentication failed and a manual call to
         * updateAccessToken() is required.
this.tapkeyServiceFactory =;

Searching for Nearby Locks

Searching for nearby locks is done using the SDK's BleLockScanner.

Depending on the Android version the app is running on, different permissions are required to scan for and communicate with BLE devices.

  • Android 12 (API Level 31) and later

    • android.permission.BLUETOOTH_CONNECT
    • android.permission.BLUETOOTH_SCAN
  • Android 10 (API Level 29) - Android 11 (API Level 30)

    • android.permission.BLUETOOTH_ADMIN
    • android.permission.BLUETOOTH
    • android.permission.ACCESS_FINE_LOCATION
  • Android 6 (API Level 23) - Android 9 (API Level 28)

    • android.permission.BLUETOOTH_ADMIN
    • android.permission.BLUETOOTH
    • android.permission.ACCESS_COARSE_LOCATION

If case the reqired permissions haven't been granted, scanning does not fail but rather returns an empty list of nearby devices. So if you're sure that you have devices nearby but they don't show up, please check whether your app has been granted all the required permissions.

Add the following uses-permission entries to target app's AndroidManifest.xml:

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

    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"

    <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"

    <uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />

    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        tools:targetApi="31" /></manifest>

The flag android:usesPermissionFlags="neverForLocation" may only be specified, if the app doesn't use Bluetooth for determining the device's location.

An instance of BleLockScanner can be obtained from the TapkeyServiceFactory.

BleLockScanner scanner = tapkeyServiceFactory.getBleLockScanner();

// request permissions e.g. via ContextCompat.checkSelfPermission(...)

// Returns an observer registration to stop scanning once finished.
bleScanObserverRegistration = scanner.startForegroundScan();

To observe locks entering and leaving the range of this device, the BleLockScanner's getLocksChangedObservable() method can be used.

// Listen for Tapkey locks coming into or leaving range.
nearbyLocksObserverRegistration = bleLockScanner.getLocksChangedObservable()
    .addObserver(locks -> {
        // Handle nearby locks here, e.g. present them to the user.

// Alternatively, getLocks() can be used to get a list of nearby locks in this moment.
nearbyLocks = bleLockScanner.getLocks();

Additionally to scanning for all locks nearby, it is also possible to check if a specific lock is nearby. Please note, that scanning must still be ongoing (via startForegroundScan()) otherwise isLockNearby() will always return false.

// Check if a specific lock is nearby.

This method can be especially used in conjunction with a list of keys. You may check, for each key the user holds, the corresponding nearby locks and show them in your user interface.

Make sure to stop scanning if the app does not require information about nearby locks anymore:

// Stop scanning for nearby locks.
if (bleScanObserverRegistration != null) {
    bleScanObserverRegistration = null;

Triggering a Lock

After a nearby lock was found with the BleLockScanner, it can be opened using the trigger lock command, which is available from the CommandExecutionFacade. Any command inside the command execution facade, requires a TLCP connection to a lock. The following example uses BleLockCommunicator to establish a TCLP connection via BLE.

// The BLE lock manager establishes a connection to the lock which is passed to the
// CommandExecutionFacade to execute a trigger lock command.
return bleLockCommunicator.executeCommandAsync(
    bluetoothAddress, physicalLockId, tlcpConnection -> {

    TriggerLockCommand triggerLockCommand = new DefaultTriggerLockCommandBuilder()

    // With the TlcpConnection to the lock established, the CommandExecutionFacade
    // asynchronously executes the trigger lock command.
    return commandExecutionFacade.executeStandardCommandAsync(
}, ct)
.continueOnUi(commandResult -> {
    switch (commandResult.getCommandResultCode()) {
        case Ok:
            return true;

        // Other cases must be handled here.

    // Let the user know, something went wrong.
    return false;
.catchOnUi(e -> {
    Log.e(TAG, "Could not execute trigger lock command.", e);

    if (!ct.isCancellationRequested()) {
        // Handle any exceptions and let the user know, something went wrong.
    return false;


By default the Tapkey Mobile Lib writes its logs to android.util.Log so they end up in Android LogCat. To be able to collect logs within the app, a custom logger can be used instead.


A custom logger replaces the Tapkey default logger, therefore logs won't be written to LogCat anymore in such cases.

Create a custom implementation of the

public class CustomLogger implements {
    public void println(int priority, String tag, String msg, Throwable tr) {
        // Custom logging implementation

The priority is divided in following static priority levels:

Add a instance of the custom implementation to This should be done preferably before creating the TapkeyServiceFactoryBuilder to avoid lost logs during bootraping.

    public void onCreate() {
        super.onCreate(); CustomLogger());

        TapkeyServiceFactory tapkeyServiceFactory = new TapkeyServiceFactoryBuilder(this)