# Build and test your app
Learn how to build and test your app using a DevKit.
Access the Apps on Devices sample integration repository on [GitHub](https://github.com/stripe-samples/terminal-apps-on-devices).
Use your SmartPOS DevKit device to test and iterate your application without going through the deployment, app review, or signing process.
If you need a DevKit device, you can [order up to five per user](https://docs.stripe.com/terminal/fleet/order-and-return-readers.md) from the [Readers](https://dashboard.stripe.com/terminal) section in your Dashboard.
## Set up the DevKit
Before you can use your DevKit for app development, you must do the following:
1. Follow the on-screen prompts to connect to a network.
1. [Register](https://docs.stripe.com/terminal/payments/connect-reader.md?terminal-sdk-platform=android&reader-type=internet#register-reader) the device to your Stripe account.
1. Install all available updates.
After the initial setup, you can register your DevKit to another account or location at any time. To do so, connect the DevKit to the internet and follow the steps to [register a reader](https://docs.stripe.com/terminal/payments/connect-reader.md?terminal-sdk-platform=android&reader-type=internet#register-reader).
While similar to production devices, DevKit devices:
- Can only operate in [sandboxes](https://docs.stripe.com/keys.md#test-live-modes).
- Ship with [developer options](https://developer.android.com/studio/debug/dev-options) and [Android Debug Bridge](https://developer.android.com/studio/command-line/adb) (`adb`) enabled by default.
- Display an on-screen watermark to indicate that the device is only used for testing. The watermark moves around the screen while the device is in use so that you can see all parts of the screen.
The Terminal API supports targeting registered DevKit devices.
## Develop your app for Stripe devices
Use the following steps to develop your app for Stripe Android devices, including setting up the app and handing it off to the Stripe Reader app.
## Set up the app
First, [set up your integration](https://docs.stripe.com/terminal/payments/setup-integration.md?terminal-sdk-platform=android) for in-person payments. Then, follow the guidance below for Apps on Devices integrations.
### Add dependencies
Add the following dependencies to your project’s Gradle build script. Apps on Devices integrations require [Terminal Android SDK](https://github.com/stripe/stripe-terminal-android) version `2.22.0` or later. We recommend that you integrate with the [latest version](https://github.com/stripe/stripe-terminal-android/releases).
```kotlin
dependencies {
implementation("com.stripe:stripeterminal-core:4.3.1")
implementation("com.stripe:stripeterminal-handoffclient:4.3.1")
}
```
```groovy
dependencies {
implementation "com.stripe:stripeterminal-core:4.3.1"
implementation "com.stripe:stripeterminal-handoffclient:4.3.1"
}
```
Make sure that you aren’t using any other Stripe Terminal SDK dependencies. For example, if you previously integrated the Terminal Android SDK, don’t use the top-level `com.stripe:stripeterminal` dependency (for example, com.stripe:stripeterminal:4.3.1).
See an example of [including dependencies in your app’s build script](https://github.com/stripe-samples/terminal-apps-on-devices/blob/718c2de38c7b8003fcf58c536c266bb990ad43a7/app/build.gradle.kts#L66).
### Configure your application
To inform the Stripe SDK of lifecycle events, add a [TerminalApplicationDelegate.onCreate()](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal-application-delegate/on-create.html) call to the [onCreate()](https://developer.android.com/reference/android/app/Application#onCreate\(\)) method for your application subclass.
```kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
TerminalApplicationDelegate.onCreate(this)
}
}
```
```java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
TerminalApplicationDelegate.onCreate(this);
}
}
```
In your [app manifest](https://developer.android.com/guide/topics/manifest/manifest-intro), specify the name of your `Application` subclass with the `android:name` attribute.
```xml
```
Learn more about [setting up your integration](https://docs.stripe.com/terminal/payments/setup-integration.md?terminal-sdk-platform=android) or see the Apps on Devices sample app GitHub repository for an example of [configuring the Application subclass](https://github.com/stripe-samples/terminal-apps-on-devices/blob/718c2de38c7b8003fcf58c536c266bb990ad43a7/app/src/main/java/com/stripe/aod/sampleapp/MyApp.kt#L10).
First, [set up your integration](https://docs.stripe.com/terminal/payments/setup-integration.md?terminal-sdk-platform=react-native) for in-person payments. Then, follow the guidance below for Apps on Devices integrations.
### Configure your application
To inform the Stripe SDK of lifecycle events, add a `TerminalApplicationDelegate.onCreate()` call to the [onCreate()](https://developer.android.com/reference/android/app/Application#onCreate\(\)) method for your application subclass.
```kotlin
import com.stripeterminalreactnative.TerminalApplicationDelegate
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
TerminalApplicationDelegate.onCreate(this)
}
}
```
```java
import com.stripeterminalreactnative.TerminalApplicationDelegate;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
TerminalApplicationDelegate.onCreate(this);
}
}
```
In your [app manifest](https://developer.android.com/guide/topics/manifest/manifest-intro), specify the name of your `Application` subclass with the `android:name` attribute.
```xml
```
Learn more about [setting up your integration](https://docs.stripe.com/terminal/payments/setup-integration.md?terminal-sdk-platform=react-native) or see the sample app on React Native GitHub repository for an example of [configuring the Application subclass](https://github.com/stripe/stripe-terminal-react-native/blob/main/dev-app/android/app/src/main/java/com/dev/app/stripeterminalreactnative/MainApplication.java#L58).
## Build the app
Follow the guidance below for Apps on Devices integrations.
### Discover and connect a reader
You must register a new Stripe device to your account as a new [Reader object](https://docs.stripe.com/api/terminal/readers/object.md). Use the pairing code provided in the device’s admin settings to [create the Reader object](https://docs.stripe.com/api/terminal/readers/create.md). Your app uses the Stripe Terminal Android SDK to discover and connect to your device:
See more examples of how to [discover](https://github.com/stripe-samples/terminal-apps-on-devices/blob/718c2de38c7b8003fcf58c536c266bb990ad43a7/app/src/main/java/com/stripe/aod/sampleapp/model/MainViewModel.kt#L90) and [connect](https://github.com/stripe-samples/terminal-apps-on-devices/blob/718c2de38c7b8003fcf58c536c266bb990ad43a7/app/src/main/java/com/stripe/aod/sampleapp/model/MainViewModel.kt#L106) using handoff mode in the Apps on Devices sample integration repository on [GitHub](https://github.com/stripe-samples/terminal-apps-on-devices).
1. Your app runs on your registered device.
1. Your app discovers the reader by calling [discoverReaders](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/discover-readers.html) with [HandoffDiscoveryConfiguration](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-discovery-configuration/-handoff-discovery-configuration/index.html).
1. Your app connects to the reader by using [connectReader](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/connect-reader.html).
The following example shows how to discover and connect to a Stripe reader using handoff mode in an Android app:
```kotlin
private fun discoverReaders() {
Terminal.getInstance().discoverReaders(
config = HandoffDiscoveryConfiguration(),
discoveryListener = object : DiscoveryListener {
override fun onUpdateDiscoveredReaders(readers: List) {
// In handoff discovery, the list will
// contain a single reader. Connect to
// the reader after it is discovered.
readers.firstOrNull()?.let { reader ->
connectReader(reader)
}
}
},
callback = object : Callback {
override fun onSuccess() {
// Handle successfully discovering readers
}
override fun onFailure(e: TerminalException) {
// Handle exception while discovering readers
}
}
)
}
private fun connectReader(reader: Reader) {
Terminal.getInstance().connectReader(
reader,
HandoffConnectionConfiguration(
object : HandoffReaderListener {
override fun onDisconnect(reason: DisconnectReason) {
// Optionally get notified about reader disconnects (for example, reader was rebooted)
}
override fun onReportReaderEvent(event: ReaderEvent) {
// Optionally get notified about reader events (for example, a card was inserted)
}
}
),
object : ReaderCallback {
override fun onSuccess(reader: Reader) {
// Handle successfully connecting to the reader
}
override fun onFailure(e: TerminalException) {
// Handle exception when connecting to the reader
}
}
)
}
```
```java
private void discoverReaders(@Nullable String locationId) {
Terminal.getInstance().discoverReaders(
new HandoffDiscoveryConfiguration(),
readers - > {
if (!readers.isEmpty()) {
connectReader(readers.get(0));
}
},
new Callback() {
@Override
public void onSuccess() {
// Handle successfully discovering readers
}
@Override
public void onFailure(@NotNull TerminalException e) {
// Handle exception while discovering readers
}
}
);
}
private void connectReader(@NotNull Reader reader) {
Terminal.getInstance().connectReader(
reader,
new HandoffConnectionConfiguration(
new HandoffReaderListener() {
@Override
public void onDisconnect(@NotNull DisconnectReason reason) {
// Optionally get notified about reader disconnects (for example, reader was rebooted)
}
@Override
public void onReportReaderEvent(@NotNull ReaderEvent event) {
// Optionally get notified about reader events (for example, a card was inserted)
}
}
),
new ReaderCallback() {
@Override
public void onSuccess(@NotNull Reader reader) {
// Handle successfully connecting to the reader
}
@Override
public void onFailure(@NotNull TerminalException e) {
// Handle exception when connecting to the reader
}
}
);
}
```
You must register a new Stripe device to your account as a new [Reader object](https://docs.stripe.com/api/terminal/readers/object.md). Use the pairing code provided in the device’s admin settings to [create the Reader object](https://docs.stripe.com/api/terminal/readers/create.md). Your app uses the Stripe Terminal React Native SDK to discover and connect to your device:
1. Your app runs on your registered device.
1. Your app discovers the reader by calling [discoverReaders](https://stripe.dev/stripe-terminal-react-native/api-reference/interfaces/StripeTerminalSdkType.html#discoverReaders) with `handoff` [discovery method](https://stripe.dev/stripe-terminal-react-native/api-reference/modules/Reader.Android.html#DiscoveryMethod).
1. Your app connects to the reader by using [connectReader](https://stripe.dev/stripe-terminal-react-native/api-reference/interfaces/StripeTerminalSdkType.html#connectreader-1).
The following example shows how to discover and connect to a Stripe reader using handoff mode in a React Native app:
```js
const { discoverReaders, connectReader, discoveredReaders } =
useStripeTerminal({
onUpdateDiscoveredReaders: (readers) => {
// After the SDK discovers a reader, your app can connect to it.
},
});
useEffect(() => {
const fetchReaders = async () => {
const { error } = await discoverReaders({
discoveryMethod: 'handoff',
});
}
fetchReaders();
}, [discoverReaders]);
const { reader, error } = await connectReader({reader}, 'handoff');
if (error) {
console.log('connectReader error:', error);
return;
}
console.log('Reader connected successfully', reader);
```
### Collect payments
After you connect to the reader using handoff mode, you can start [collecting payments](https://docs.stripe.com/terminal/payments/collect-card-payment.md?terminal-sdk-platform=android#create-payment).
The Stripe Reader app handles payment collection and other payment operations, such as [saving payment details](https://docs.stripe.com/terminal/features/saving-payment-details/overview.md). When initiating a payment operation, the Stripe Reader app becomes the primary and launches in full screen. Then, the Stripe Reader app guides the customer through the flow and returns control to your app after completion (success or failure) or customer cancellation. When control returns to your app, the Stripe Reader app continues to run in the background.
See an example of [collecting payment in an Apps on Devices app](https://github.com/stripe-samples/terminal-apps-on-devices/blob/718c2de38c7b8003fcf58c536c266bb990ad43a7/app/src/main/java/com/stripe/aod/sampleapp/model/CheckoutViewModel.kt#L82).
#### Collect payments while offline
Apps on Devices supports [offline payment collection](https://docs.stripe.com/terminal/features/operate-offline/collect-card-payments.md?terminal-sdk-platform=android&reader-type=internet).
## Device management
You can access the device’s admin settings by launching the `stripe://settings/` deep-link URI from your app.
See an example of [launching the admin settings deep-link URI](https://github.com/stripe-samples/terminal-apps-on-devices/blob/718c2de38c7b8003fcf58c536c266bb990ad43a7/app/src/main/java/com/stripe/aod/sampleapp/fragment/HomeFragment.kt#L30).
```kotlin
startActivity(
Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("stripe://settings/"))
)
```
```java
startActivity(
new Intent(Intent.ACTION_VIEW);
.setData(Uri.parse("stripe://settings/"))
)
```
## Instrument the app
Stripe doesn’t provide an application-level instrumentation solution. To keep track of crashes and other logs from your application, you can use a third-party library such as Sentry or Crashlytics.
## Set the device locale
The device user’s language selection (not country) informs the value returned by [Locale.getDefault()](https://developer.android.com/reference/java/util/Locale#getDefault\(\)). You can change the device language in the admin settings.
## Screen orientation
Stripe Android devices have the _Auto-rotate screen_ setting enabled by default. Your app can override this setting by locking the UI to a specific screen orientation.
This can be achieved by setting the [screenOrientation](https://developer.android.com/guide/topics/manifest/activity-element#screen) attribute on the relevant `` tags in the manifest.
```xml
```
Alternatively, this can be set programatically using [Activity::setRequestedOrientation](https://developer.android.com/reference/android/app/Activity#setRequestedOrientation\(int\)) in your `Activity` class.
```kotlin
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Lock to portrait orientation
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
// Or, lock to landscape orientation
// requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
```
```java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Lock to portrait orientation
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// Or, lock to landscape orientation
// setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
```
## Limitations
Stripe Android devices don’t render a system UI, including a back button or status bar.
If your app needs to communicate battery level, charging state, and connectivity status to the user, refer to the following Android API docs for guidance:
- [Monitor the Battery Level and Charging State](https://developer.android.com/training/monitoring-device-state/battery-monitoring)
- [Monitor connectivity status and connection metering](https://developer.android.com/training/monitoring-device-state/connectivity-status-type)
## Working with device accessories
When the Stripe reader connects or disconnects from a dock, the Android operation system triggers a [configuration change](https://developer.android.com/guide/topics/resources/runtime-changes).
By default, your app’s activity is automatically recreated on a configuration change.
To disable automatic activity recreation when connecting to or disconnecting from a dock, add `android:configChanges="uiMode"` in the `` entry in your `AndroidManifest.xml` file.
```xml
```
Your activity can be notified of configuration changes by implementing [Activity::onConfigurationChanged](https://developer.android.com/reference/android/app/Activity#onConfigurationChanged\(android.content.res.Configuration\)). This method is only called if you’ve specified configurations you want to handle with the `android:configChanges` attribute in your manifest.
```kotlin
class MainActivity : Activity() {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// implement custom configuration change handling logic
}
}
```
## Test your app
Use your S700 DevKit device to test your app in the Stripe Dashboard or using the Android Debug Bridge (`adb`).
You can connect your DevKit device to your computer using a USB-A to USB-C cable. Then, use `adb` to directly install your app’s assembled APK onto the DevKit device.
The following examples assume your application’s [package name](https://developer.android.com/studio/build/configure-app-module) is `com.example.myapp` and the [main activity](https://developer.android.com/reference/android/content/Intent.html#ACTION_MAIN) is `MainActivity`.
```
$ adb install myapp.apk
```
After installation completes, launch your app:
```
$ adb shell am start com.example.myapp/.MainActivity
```
Start admin settings:
```
$ adb shell am start -d "stripe://settings/"
```
If needed, uninstall your app:
```
$ adb uninstall com.example.myapp
```
Google’s [Android Debug Bridge documentation](https://developer.android.com/studio/command-line/adb) provides a comprehensive guide to using `adb`.
Follow these steps to test your app in the Dashboard:
1. In a sandbox, open the [Terminal readers](https://dashboard.stripe.com/test/terminal/readers) page.
1. If you haven’t already, click **Register reader** to [register](https://docs.stripe.com/terminal/payments/connect-reader.md?reader-type=internet#register-reader) the DevKit device to your account.
1. Click **Developers** > **Apps** > **Terminal apps**.
1. On the [Terminal apps](https://dashboard.stripe.com/terminal/apps_on_devices/apps) tab, choose the app that you want to deploy. You can also create a new app to deploy.
1. On the app details page, click **Deploy version**.
1. Choose the latest version of your app, then click **Next**.
1. Choose the [deploy group](https://docs.stripe.com/terminal/features/apps-on-devices/deploy-in-Dashboard.md) for your DevKit device, then click **Next**.
1. Choose your preferred kiosk app, then click **Next**. This is the default app that launches when the Stripe reader turns on. If there’s only one app to deploy, choose that app instead.
1. Confirm the deployment details, then click **Deploy**.
1. Restart your DevKit device to deploy your app to the device.
### Troubleshooting
If you previously installed your app by sideloading and attempt to deploy it again, you might receive the following error:
`Failed to apply updates. Code: A9-com.example.posapp`.
You must manually uninstall the sideloaded app by running the following command:
```
adb uninstall com.example.posapp
```
## Test payments
DevKit devices can process test payments using a Stripe physical test card, which you can order in the [Dashboard](https://dashboard.stripe.com/terminal/shop/thsku_FmpZaTqwezTFvS). When [testing payments](https://docs.stripe.com/terminal/references/testing.md#physical-test-cards), you can use decimal amounts to produce specific outcomes.
Don’t use real cards for test payments on DevKit devices.
## See Also
* [Prepare for app review](https://docs.stripe.com/terminal/features/apps-on-devices/app-review.md)
* [Submit your app](https://docs.stripe.com/terminal/features/apps-on-devices/submit.md)