Build on Android
Build with Reader SDK on Android to process in-person payments with Square hardware.
Related Products
Process Overview
- Prerequisites
- Assumptions
- Device Permissions
- Step 1: Request Reader SDK credentials
- Step 2: Configure your Android project for Reader SDK
- Step 3: Extend the Android Application class
- Step 4: Add code to request and use a mobile authorization code
- Step 5: Add code to handle authorization results
- Step 6: Create a CheckoutActivity
- Step 7: Add code to start the checkout flow
- Step 8: Add code to handle checkout results
- Optional: Connect a Contactless Reader
- Optional: Deauthorize Reader SDK
- Optional: Configure APK splits across ABIs
Prerequisites
To build with Reader SDK, the following must be true:
- Your application minSdkVersion is API 21 (Lollipop 5.0) or higher.
- Your application targetSdkVersion is API 26 (Oreo, 8.0) or lower.
- You are using the Android Gradle Plugin version 3.0.0 or greater. Reader SDK may work with 2.3.0 or greater, but stability is not guaranteed.
- Your application uses support library version 26.0.2 and Google Play Services version 12.0.1. Square cannot guarantee that the SDK will work with different versions of these libraries.
- You are not using Proguard for code optimization. Compressing the Reader SDK binary removes critical bytecode elements and results in runtime errors.
Assumptions
This guide makes the following assumptions:
- You have read the Reader SDK: What It Does guide.
- Your version of Reader SDK adheres to Square's update policy. To limit risk to developers and their users, Square enforces an update policy for Reader SDK requiring developers keep their version of Reader SDK current.
- You have a Square Account enabled for payment processing. If you have not enabled payment processing on your account (or you're not sure), visit squareup.com/activate.
- You are building with Android Studio and Gradle. Android Studio is used because it is the most common development environment for Android apps, but Reader SDK is IDE agnostic and can be used with other development environments if desired.
- You are generally familiar with developing apps on Android. If you are new to Android development, we recommend reading the Getting Started Guide at the Android Developers site before continuing.
Device Permissions
To work with Square Readers, apps must have the device permissions noted below. If the required device permissions are not granted when the checkout flow initiates, Reader SDK prompts the user to grant the necessary permissions.
Android device permission | Purpose |
---|---|
Location | To confirm payments are occurring in a supported country. |
Audio | To connect Magstripe Readers. |
Bluetooth | To connect Contactless Readers. |
Device Storage | To store information during checkout. |
Phone Access | To identify the device sending information to Square servers. |
Step 1: Request Reader SDK credentials
- Open the Square Application Dashboard. You will be prompted to login or create a new account.
- Create a new Square application.
- Click on the new application to bring up the Square application settings pages.
- Open the Reader SDK page and click "Request Credentials" to generate your Reader SDK repository password.
You will need the Application ID and Repository password from the Reader SDK page to configure Reader SDK in the next steps:

Step 2: Configure your Android project for Reader SDK
Configure Gradle
- Update the
gradle.properties
file in the root folder of your project to increase the max heap size provided to the Gradle daemon and set variables for the Square application ID and repository password:
SQUARE_READER_SDK_APPLICATION_ID={APPLICATION ID FROM STEP 1} SQUARE_READER_SDK_REPOSITORY_PASSWORD={REPO PASSWORD FROM STEP 1} org.gradle.jvmargs=-Xmx4g
- Add the Reader SDK variables from your properties file and confirm that the Google repository is set properly in the
build.gradle
file of your:app
module:
repositories {
google()
maven {
url "https://sdk.squareup.com/android"
credentials {
username SQUARE_READER_SDK_APPLICATION_ID
password SQUARE_READER_SDK_REPOSITORY_PASSWORD
}
}
jcenter()
}
Configure the build dependencies
- Reader SDK and its dependencies contain more than 65k methods, so your build script must enable Multidex. If your minSdkVersion is less than 21, you also need to include the multidex dependency:
android { defaultConfig { minSdkVersion 21 targetSdkVersion 26 multiDexEnabled true } } dependencies { // Add this dependency if your minSdkVersion < 21 implementation 'com.android.support:multidex:1.0.3' // ... }
- Configure the Multidex options:
android { // ... dexOptions { // Ensures incremental builds remain fast preDexLibraries true // Required to build with Reader SDK jumboMode true // Required to build with Reader SDK keepRuntimeAnnotatedClasses false } // ... }
- Add the Reader SDK dependencies:
dependencies { def readerSdkVersion = "1.1.3" implementation "com.squareup.sdk.reader:reader-sdk-$SQUARE_READER_SDK_APPLICATION_ID:$readerSdkVersion" runtimeOnly "com.squareup.sdk.reader:reader-sdk-internals:$readerSdkVersion" // ... }
Working with higher support library versions
Reader SDK is guaranteed to work with support library version 26.0.2. If you want to use a higher version of support
library, you will need to update build.gradle
to add the targeted version of the required libraries (e.g. design
,
support-v4
, recyclerview-v7
).
If you use a higher version of the support library, we recommend establishing a resolution strategy in build.gradle
to
ensure all support library dependencies are on the same version. For
example:
configurations.all { resolutionStrategy { eachDependency { details -> // Force all primary support libraries to the same version if (details.requested.group == 'com.android.support' && details.requested.name != 'multidex' && details.requested.name != 'multidex-instrumentation') { // Force the version that works for you. // Square has tested ReaderSDK with 26.0.2 details.useVersion '26.0.2' } } } }
Step 3: Extend the Android Application
class
-
Create an
Application
class that extendsandroid.app.Application
and specify theandroid:name
property for the<application>
node inAndroidManifest.xml
(for more help with this step, see Understanding the Android Application Class on Codepath). -
Import and initialize Reader SDK:
import com.squareup.sdk.reader.ReaderSdk; import com.squareup.sdk.reader.authorization.AuthorizationManager; import com.squareup.sdk.reader.authorization.AuthorizeErrorCode; import com.squareup.sdk.reader.authorization.Location; import com.squareup.sdk.reader.core.CallbackReference; import com.squareup.sdk.reader.core.Result; import com.squareup.sdk.reader.core.ResultError; public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); ReaderSdk.initialize(this); } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Required if minSdkVersion < 21 MultiDex.install(this); } }
Step 4: Add code to request and use a mobile authorization code
To authorize the SDK, you must build an authorization service to retrieve a mobile authorization code from the Mobile Authorization API and return it to your application.
Mobile authorization codes are short lived and should be used immediately to authorize Reader SDK. Authorization is
valid until it is explicitly revoked by calling deauthorize
or your application fails to take a payment within 90
days. Mobile authorization codes do not need to be manually refreshed under normal operations.
Create a new AuthorizeActivity
with 2 skeleton functions (retrieveAuthorizationCode()
and
onAuthorizationCodeRetrieved()
), which can be customized to call your authorization
service:
public class AuthorizeActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.authorize_activity); View authorizeButton = findViewById(R.id.authorize_button); authorizeButton.setOnClickListener(view -> retrieveAuthorizationCode()); } private void retrieveAuthorizationCode() { // TODO: Asynchronous code to retrieve a mobile authorization code. // Calls onAuthorizationCodeRetrieved(String) with the resulting code. } private void onAuthorizationCodeRetrieved(String authorizationCode) { showAuthorizationInProgress(true); ReaderSdk.authorizationManager().authorize(authorizationCode); } private void showAuthorizationInProgress(boolean inProgress) { // TODO: Display or hide a loading indicator } }
Step 5: Add code to handle authorization results
- Add a callback (
onAuthorizeResult()
) to the Authorization Manager in theonCreate
method of your authorization activity usingAuthorizationManager.addAuthorizeCallback()
:
public class AuthorizeActivity extends Activity { // ... private CallbackReference authorizeCallbackRef; @Override protected void onCreate(Bundle savedInstanceState) { //... AuthorizationManager authManager = ReaderSdk.authorizationManager(); authorizeCallbackRef = authManager.addAuthorizeCallback(this::onAuthorizeResult); } // ... private void onAuthorizeResult(Result<Location, ResultError<AuthorizeErrorCode>> result) { // TODO: Handle the authorization result }
- Implement the
onAuthorizeResult()
callback.AuthorizeCallback.onResult()
is invoked asynchronously on the main thread with aResult
object. The result includes the authorizedLocation
(in case of success) or aResultError
(in case of error):
private void onAuthorizeResult(Result<Location, ResultError<AuthorizeErrorCode>> result) { showAuthorizationInProgress(false); if (result.isSuccess()) { goToCheckoutActivity(); } else { ResultError<AuthorizeErrorCode> error = result.getError(); switch (error.getCode()) { case NO_NETWORK: showDialog(getString(R.string.no_network), error.getMessage()); break; case USAGE_ERROR: String dialogMessage = error.getMessage(); if (BuildConfig.DEBUG) { dialogMessage += "\n\nDebug Message: " + error.getDebugMessage(); Log.d("Auth", error.getDebugCode() + ", " + error.getDebugMessage()); } showDialog(getString(R.string.error_dialog_title), dialogMessage); break; } } }
- Add code to clear the callback reference in your
onDestroy()
method to avoid memory leaks:
public class AuthorizeActivity extends Activity { // ... @Override protected void onDestroy() { super.onDestroy(); authorizeCallbackRef.clear(); } }
Step 6: Create a CheckoutActivity
Create a new activity, CheckoutActivity
, with a button that calls startCheckout()
to begin the checkout
flow:
import com.squareup.sdk.reader.checkout.AdditionalPaymentType; import com.squareup.sdk.reader.checkout.CheckoutErrorCode; import com.squareup.sdk.reader.checkout.CheckoutManager; import com.squareup.sdk.reader.checkout.CheckoutParameters; import com.squareup.sdk.reader.checkout.CheckoutResult; import com.squareup.sdk.reader.checkout.CurrencyCode; import com.squareup.sdk.reader.checkout.Money; import com.squareup.sdk.reader.core.CallbackReference; import com.squareup.sdk.reader.core.Result; import com.squareup.sdk.reader.core.ResultError; public class CheckoutActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!ReaderSdk.authorizationManager().getAuthorizationState().isAuthorized()) { goToAuthorizeActivity(); } setContentView(R.layout.checkout_activity); View startCheckoutButton = findViewById(R.id.start_checkout_button); startCheckoutButton.setOnClickListener(view -> startCheckout()); } private void goToAuthorizeActivity() { // TODO: Reader SDK is not authorized, move to Authorize Activity } }
Step 7: Add code to start the checkout flow
- Implement the
startCheckout()
method by adding code to build theCheckoutParameters
object and configure the checkout experience. In the code below we useCurrencyCode#current
to obtain the currency code of the authorized location and setCASH
as one of the additional tender types, which lets us test payments without charging a card.
public class CheckoutActivity extends Activity { // ... private CallbackReference checkoutCallbackRef; private void startCheckout() { CheckoutManager checkoutManager = ReaderSdk.checkoutManager(); Money amountMoney = new Money(100, CurrencyCode.current()); CheckoutParameters.Builder parametersBuilder = CheckoutParameters.newBuilder(amountMoney); if (BuildConfig.DEBUG) { parametersBuilder.additionalPaymentTypes(AdditionalPaymentType.CASH); } checkoutManager.startCheckoutActivity(this, parametersBuilder.build()); } }
- Add a callback (
onCheckoutResult()
) to the Checkout Manager in theonCreate
method ofCheckoutActivity
usingCheckoutManager.addCheckoutActivityCallback
:
@Override protected void onCreate(Bundle savedInstanceState) { // ... CheckoutManager checkoutManager = ReaderSdk.checkoutManager(); checkoutCallbackRef = checkoutManager.addCheckoutActivityCallback(this::onCheckoutResult); } }
- Add code to clear the checkout callback reference in your
onDestroy()
method to avoid memory leaks:
public class CheckoutActivity extends Activity { // ... @Override protected void onDestroy() { super.onDestroy(); checkoutCallbackRef.clear(); } }
Step 8: Add code to handle checkout results
Implement the onCheckoutResult
callback to parse the checkout response. CheckoutActivityCallback.onResult()
is
invoked asynchronously on the main thread with a Result
object. The result includes the CheckoutResult
(in case of
success) or a ResultError
(in case of
error):
public class CheckoutActivity extends Activity { // ... private void onCheckoutResult(Result<CheckoutResult, ResultError<CheckoutErrorCode>> result) { if (result.isSuccess()) { CheckoutResult checkoutResult = result.getSuccessValue(); showCheckoutResult(checkoutResult); } else { ResultError<CheckoutErrorCode> error = result.getError(); switch (error.getCode()) { case SDK_NOT_AUTHORIZED: goToAuthorizeActivity(); break; case CANCELED: Toast.makeText(this, "Checkout canceled", Toast.LENGTH_SHORT).show(); break; case USAGE_ERROR: showErrorDialog(error); break; } } } private void showCheckoutResult(CheckoutResult checkoutResult) { // TODO: Display the checkout result. } private void showErrorDialog(ResultError<?> error) { String dialogMessage = error.getMessage(); if (BuildConfig.DEBUG) { dialogMessage += "\n\nDebug Message: " + error.getDebugMessage(); Log.d("Checkout", error.getDebugCode() + ", " + error.getDebugMessage()); } showDialog(getString(R.string.error_dialog_title), dialogMessage); } private void showDialog(CharSequence title, CharSequence message) { // TODO: Display a dialog to the user. } }
The example code above prompts for authorization if Reader SDK is not authorized, displays the transaction details from
CheckoutResult
in the case of a successful transaction, and displays an error dialog (showErrorDialog) in the case of
failure.
Optional: Connect a Contactless Reader
If you are using a Magstripe Reader, you do not need to write explicit code to connect the Reader. If you are using a Contactless + Chip Reader, you must add code to handle pairing.
- Add code to your Checkout activity to display a button that launches the Square Card Reader settings flow by calling
ReaderManager#startReaderSettingsActivity()
and passing in the activity:
import com.squareup.sdk.reader.hardware.ReaderManager; import com.squareup.sdk.reader.hardware.ReaderSettingsErrorCode; public class CheckoutActivity extends Activity { private CallbackReference readerSettingsCallbackRef; @Override protected void onCreate(Bundle savedInstanceState) { // ... View readerSettingsButton = findViewById(R.id.reader_settings_button); readerSettingsButton.setOnClickListener(view -> startReaderSettings()); } private void startReaderSettings() { ReaderManager readerManager = ReaderSdk.readerManager(); readerManager.startReaderSettingsActivity(this); }
- Add a callback (
onReaderSettingsResult()
) to the Reader Manager in theonCreate()
method of your checkout activity usingReaderManager.addReaderSettingsActivityCallback
:
@Override protected void onCreate(Bundle savedInstanceState) { // ... ReaderManager readerManager = ReaderSdk.readerManager(); readerSettingsCallbackRef = readerManager.addReaderSettingsActivityCallback( this::onReaderSettingsResult ); private void onReaderSettingsResult( Result<Void, ResultError<ReaderSettingsErrorCode>> result) { // TODO: Handle the pairing result } } }
- Implement the
onReaderSettingsResult()
method by adding code to respond to the pairing result:
private void onReaderSettingsResult( Result<Void, ResultError<ReaderSettingsErrorCode>> result) { if (result.isError()) { ResultError<ReaderSettingsErrorCode> error = result.getError(); switch (error.getCode()) { case SDK_NOT_AUTHORIZED: goToAuthorizeActivity(); break; case USAGE_ERROR: showErrorDialog(error); break; } } }
- Add code to clear the reader settings callback reference in your
onDestroy()
method to avoid memory leaks:
public class CheckoutActivity extends Activity { // ... @Override protected void onDestroy() { super.onDestroy(); readerSettingsCallbackRef.clear(); } }
Optional: Deauthorize Reader SDK
To switch Square locations or to deauthorize the current location, you will need to deauthorize Reader SDK.
- Add code to your Checkout activity to display a button that deauthorizes Reader SDK by calling
AuthorizationManager#deauthorize()
:
import com.squareup.sdk.reader.authorization.AuthorizationManager; import com.squareup.sdk.reader.authorization.DeauthorizeErrorCode; public class CheckoutActivity extends Activity { // ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.checkout_activity); View deauthorizeButton = findViewById(R.id.deauthorize_button); deauthorizeButton.setOnClickListener(view -> deauthorize()); } private void deauthorize() { AuthorizationManager authorizationManager = ReaderSdk.authorizationManager(); if (authorizationManager.getAuthorizationState().canDeauthorize()) { authorizationManager.deauthorize(); } else { showDialog("Unable to deauthorize", "There are Square payments on this device that need to be " + "uploaded. Please ensure you are connected to the Internet."); } } }
- Add a callback (
onDeauthorizeResult()
) to the Authorization Manager in theonCreate
method of your checkout activity usingAuthorizationManager.addDeauthorizeCallback
:
public class CheckoutActivity extends Activity { private CallbackReference deauthorizeCallbackRef; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... AuthorizationManager authorizationManager = ReaderSdk.authorizationManager(); deauthorizeCallbackRef = authorizationManager.addDeauthorizeCallback(this::onDeauthorizeResult); } private void onDeauthorizeResult( Result<Void, ResultError<DeauthorizeErrorCode>> result) { // TODO: Handle the deathorize result } }
- Implement the
onDeauthorizeResult
method by adding code to respond to the deauthorization result:
private void onDeauthorizeResult( Result<Void, ResultError<DeauthorizeErrorCode>> result) { if (result.isSuccess()) { goToAuthorizeActivity(); } else { ResultError<DeauthorizeErrorCode> error = result.getError(); if (error.getCode()) { showErrorDialog(error); } } }
- Add code to clear the deauthorization callback reference in your
onDestroy()
method to avoid memory leaks:
public class CheckoutActivity extends Activity { // ... @Override protected void onDestroy() { super.onDestroy(); deauthorizeCallbackRef.clear(); } }
Optional: Configure APK splits across ABIs
You can configure APK splitting for each of the different supported ABI architectures. If you do this, you will have to ship multiple APKs but they will be 35 – 40MB smaller than a universal APK.
android { // ... splits { abi { enable true reset() // clear from 'all ABIs' to 'none' include 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' universalApk true } } } // Map for the version code that gives each ABI a value. ext.abiCodes = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'x86':4, 'x86_64':5] import com.android.build.OutputFile // For each APK output variant, override versionCode with a combination of // ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode // is equal to defaultConfig.versionCode. If you configure product flavors that // define their own versionCode, variant.versionCode uses that value instead. android.applicationVariants.all { variant -> // Assigns a different version code for each output APK // other than the universal APK. variant.outputs.each { output -> // Stores the value of ext.abiCodes that is associated with the ABI for this variant. def baseAbiVersionCode = // Determines the ABI for this variant and returns the mapped value. project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes, // the following code does not override the version code for universal APKs. // However, because we want universal APKs to have the lowest version code, // this outcome is desirable. if (baseAbiVersionCode != null) { // Assigns the new version code to versionCodeOverride, which changes the version code // for only the output APK, not for the variant itself. Skipping this step simply // causes Gradle to use the value of variant.versionCode for the APK. output.versionCodeOverride = baseAbiVersionCode * 1000 + variant.versionCode } } }