Route 1: Supplied React Native library (recommended)
Recommended for the easiest integration. Install @last-lock/link-mobile to discover nearby locks, present BLE credentials, and run Proxy from JavaScript or TypeScript. Last Lock maintains the TurboModule that forwards those calls to the iOS and Android SDKs and pins their versions with each package release. Your app owns the UI, backend authorization, event subscriptions, and startup lifecycle.
Compare all routes and complete the app allowlisting first.
Two integration paths
Both options install the same package and provide the same JavaScript methods and events. Choose Option A when Expo prebuild owns the native projects: the plugin applies the SDK settings each time those projects are generated. Choose Option B when your team owns the native files: you apply those settings to the existing Xcode, Podfile, and Gradle configuration. Both paths require a native rebuild before JavaScript can use the SDK:
| Option A: Expo plugin | Option B: Manual native configuration | |
|---|---|---|
| Choose when | You use Expo prebuild to generate ios/ and android/. | You maintain ios/ and android/ directly, including customized or existing React Native apps. |
| Your setup work | Add the supplied config plugin and run prebuild. | Apply the supplied integration helper and native project settings yourself. |
| Bridge implementation | Supplied by Last Lock. | Supplied by Last Lock. |
| After setup | Call the JavaScript API below. | Call the same JavaScript API below. |
Option B is still Route 1. Editing a Podfile or Gradle configuration does not mean you are writing a custom TurboModule. Expo apps need a native development build; Expo Go cannot load this SDK.
SDK and project setup (both A and B)
The library requires React Native's New Architecture, iOS 17+, Android API 26+, and Java 17. For an app supporting both platforms, complete both native builds. Configure signing, Bluetooth permissions, and device attestation, then validate on physical devices. Expo requires a native development build.
Use the production SDK distribution, link-mobile-sdk-prod, and the native versions pinned by your library release. Keep the production connection settings: plane.lastlock.com and BLE service UUID FC5A. Change them only when Last Lock provides deployment-specific instructions.
React Native and SDK versions
Find the React Native example app, TimeKeeper, in the production SDK repository's example apps. Use its package.json and lockfile as a reference for the React Native, React, Expo, and library versions used together.
Check your library release's compatibility information before choosing React Native and Expo versions. The library declares wildcard React and React Native peer dependencies; that declaration does not establish compatibility with every React Native version or the legacy architecture.
| Component | Version source |
|---|---|
| React Native application | Your application's package.json and lockfile. |
| JavaScript library | Your installed @last-lock/link-mobile package version and lockfile. |
| iOS native SDK | The native version listed for your package release; confirm it in Xcode's resolved Swift package dependencies. |
| Android native SDK | The native version listed for your package release; confirm the resolved bridge-android version in Gradle's dependency report. |
Include these versions when reporting integration issues. Upgrading the JavaScript library can change the native SDK versions and requires a native rebuild.
Package registry setup
Configure npm to resolve the package from GitHub Packages. This step is required for both iOS and Android, since both builds use the JavaScript package.
GitHub Packages requires authentication even for public npm and Maven packages. Create a personal access token (classic) with read:packages; a fine-grained token is not supported by these registries. Public SDK repository access does not require the repo scope. See GitHub's npm registry and Gradle registry instructions.
In your user-level ~/.npmrc, configure:
# ~/.npmrc
@last-lock:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=<auth_token>
Replace <auth_token> with the classic PAT. Keep this user-level file out of source control; use a secret store for CI.
For Android, configure global Maven credentials using the same classic PAT with read:packages:
# ~/.gradle/gradle.properties
gpr.user=YOUR_GITHUB_USERNAME
gpr.key=YOUR_GITHUB_PAT
npm and Gradle are separate package clients: Gradle does not read ~/.npmrc, so a successful npm install does not authenticate the Android SDK download. Set both Gradle properties above before syncing or building Android. In CI, the library can instead read GITHUB_ACTOR (GitHub username) and GITHUB_TOKEN (a token with package read access) from the Gradle process environment. A workflow token needs packages: read and access to the package; setting an npm token alone is insufficient.
Install the React Native library
Replace PACKAGE_VERSION with the published version provided by Last Lock:
npm install --save-exact @last-lock/link-mobile@PACKAGE_VERSION
The package pins its native SDK versions. Use those matching versions rather than substituting an independently selected Swift or Maven release.
Option A: Expo generates your native projects
Add the plugin to your Expo configuration, preserving any other plugins:
{
"expo": {
"newArchEnabled": true,
"ios": {
"bundleIdentifier": "com.example.app"
},
"android": {
"package": "com.example.app"
},
"plugins": ["@last-lock/link-mobile/app.plugin.js"]
}
}
Generate the native projects and build for your target device:
npx expo prebuild
npx expo run:ios --device
# Or build Android:
npx expo run:android --device
The plugin sets iOS deployment to 17.0, supplies Bluetooth usage descriptions, integrates the supplied iOS bridge into the app target, and installs a CocoaPods post_integrate hook that links the pinned Swift package. It sets Android's minimum SDK to 26 and adds the Maven repository. Android registers the module through autolinking.
Review generated projects before building: the plugin expects the standard Expo Android repository block, and customized templates may need a manual Maven entry. Configure iOS signing and App Attest on the generated app target.
After changing native dependencies, regenerate as appropriate for your project, run pod install from ios/, and rebuild the application. A Metro reload updates JavaScript only. If you use expo prebuild --clean, preserve native changes first because that command regenerates the native projects.
Option B: You maintain the native projects
If you manage ios/ and android/ directly, install the same package and apply its native setup manually:
-
Enable the New Architecture and keep the package included in autolinking and codegen on both platforms.
-
Set iOS deployment to 17.0 in the Podfile and app target, and add
NSBluetoothAlwaysUsageDescriptionandNSBluetoothPeripheralUsageDescriptiontoInfo.plistwith explanations appropriate to your app. -
In the iOS Podfile's app target, add the following hook, replacing
YourAppwith the Xcode project and target name:post_integrate do |installer|load File.expand_path('../node_modules/@last-lock/link-mobile/ios/integrate_link_mobile.rb', __dir__)integrate_link_mobile('YourApp')end -
Run
pod install. The supplied helper integrates the bridge in the app target and links the pinnedLinkMobileSwift package. TheLinkMobileRNpod anchors codegen; installing that pod alone does not compile the bridge implementation. Confirm the install processesLinkMobileSpec. -
Set Android's minimum SDK to 26, use Java 17, and add the package's GitHub Packages Maven repository with credentials to your project's dependency repositories. Follow the Android setup guide for the repository pattern, using the repository and coordinates required by your React Native release.
-
Configure signing, permissions, and attestation, then rebuild both native applications.
Add this Android repository entry:
// Add to the repositories used for dependency resolution in your project.
maven {
url 'https://maven.pkg.github.com/last-lock/link-mobile-sdk-prod'
credentials {
username = providers.gradleProperty('gpr.user').orNull ?: System.getenv('GITHUB_ACTOR')
password = providers.gradleProperty('gpr.key').orNull ?: System.getenv('GITHUB_TOKEN')
}
}
Set the minimum SDK in your app's existing Android configuration:
// android/app/build.gradle — merge into the existing android block.
android {
defaultConfig {
minSdk 26
}
}
Keep newArchEnabled=true in android/gradle.properties and use Java 17. The supplied package declares its pinned native dependency; you do not need to implement a Kotlin module or add a second SDK dependency to replace it. Run pod install from ios/, then build your native app using your project's usual iOS and Android commands.
Android BLE runtime permissions
The supplied React Native Android module requests permissions before starting BLE:
- Android 12+ (API 31+): request
BLUETOOTH_SCANandBLUETOOTH_CONNECT; on Android 13+ notification permission may also be requested for full startup. - Android 10–11 (API 29–30): request foreground
ACCESS_FINE_LOCATIONfirst. For fullstart(), requestACCESS_BACKGROUND_LOCATIONseparately after foreground location is granted. Android 11 may require granting Allow all the time from Settings. Do not request both in one batch. - Android 9 and older: use the legacy Bluetooth permissions and foreground location. The
startLimited()path never needs background location.
If a user denies background location, explain why full background scanning requires it, and let them retry or use startLimited() for foreground-only scanning. See Android permissions.
Example: the same JavaScript for A and B
After either setup, register listeners before starting. Call this once from an application-level owner while the app is visible, and handle failures in your UI:
import * as LinkMobile from '@last-lock/link-mobile';
const discovery = LinkMobile.onDeviceDiscovered((device) => {
console.log('Lock:', device.identifier, 'RSSI:', device.rssi);
});
export async function startLinkMobile() {
await LinkMobile.initialize();
await LinkMobile.start();
}
// In the owner's cleanup:
export function removeDiscoveryListener() {
discovery.remove();
}
These calls use the production defaults. Override them only when instructed by Last Lock. Startup acceptance does not mean a lock was discovered or Proxy connected. Removing a JavaScript listener does not stop the native SDK.
Continue to JavaScript usage and API for a React component with error handling, discovery, credential exchange, firmware updates, and event payloads. Those examples apply to both A and B.