# SDK Installation
Complete installation and configuration guides for the Airship SDK, including setup, advanced integration, logging, and locale configuration.
# Install and Set Up the Android SDK
> Learn how to install the Airship SDK on Android, Android TV, and Fire OS.
The Airship Android SDK is a modular, Kotlin-first SDK with support for both Jetpack Compose and XML Views. It provides a consistent API for push notifications, in-app messaging, and audience management.
For a complete reference of feature support across Android, Android TV, and Fire OS, see [Platform Support](https://www.airship.com/docs/developer/sdk-integration/android/resources/#platform-support).
> **Tip:** If you use an AI coding assistant, you can connect it to Airship with Skills and an MCP server. See [Airship AI Tools](https://www.airship.com/docs/developer/ai-tools/ai-tools/).
## Requirements
* Minimum Android version supported: `23+`
* Compile SDK version: `36+`
## SDK Installation
The Airship SDK is split into modules which allow you to choose the push
providers and features to be included in your application.
| Module | Description |
|------------------------------------------|----------------------------------------------------------------------------------|
| `urbanairship-adm` | ADM push provider |
| `urbanairship-fcm` | FCM push provider |
| `urbanairship-hms` | HMS push provider |
| `urbanairship-automation` | In-App Automation, In-App Messaging, and Landing pages (XML Views UI) |
| `urbanairship-automation-compose` | In-App Automation, In-App Messaging, and Landing pages (Compose UI) |
| `urbanairship-message-center` | Message Center (XML Views UI) |
| `urbanairship-message-center-compose` | Message Center (Compose UI) |
| `urbanairship-preference-center` | Preference Center (XML Views UI) |
| `urbanairship-preference-center-compose` | Preference Center (Compose UI) |
| `urbanairship-live-update` | Live Updates |
| `urbanairship-feature-flag` | Feature Flags |
Choose the UI framework that matches your app: use `-compose` modules if using Jetpack Compose, otherwise use the standard modules. Do not include both modules in the same app.
#### Gradle Kotlin
**app build.gradle.kts**
```kotlin
dependencies {
val airshipVersion = "androidSdkVersion"
// FCM push provider
implementation("com.urbanairship.android:urbanairship-fcm:$airshipVersion")
// In-App Messaging
implementation("com.urbanairship.android:urbanairship-automation-compose:$airshipVersion")
// Message Center
implementation("com.urbanairship.android:urbanairship-message-center-compose:$airshipVersion")
}
```
> **Note:** All Airship dependencies included in the `build.gradle.kts` file should specify the exact same version.
#### Gradle Groovy
**app build.gradle**
```groovy
dependencies {
def airshipVersion = "androidSdkVersion"
// FCM push provider
implementation "com.urbanairship.android:urbanairship-fcm:$airshipVersion"
// In-App Messaging
implementation "com.urbanairship.android:urbanairship-automation:$airshipVersion"
// Message Center
implementation "com.urbanairship.android:urbanairship-message-center:$airshipVersion"
}
```
> **Note:** All Airship dependencies included in the `build.gradle` file should specify the exact same version.
## Initialize Airship
The Airship SDK must be initialized before any Receiver, Service, or Activity
is created. The recommended way to achieve this is by using the [Autopilot](https://www.airship.com/docs/reference/libraries/android-kotlin/latest/urbanairship-core/com.urbanairship/-autopilot/index.html)
class, but it is also possible to call *takeOff* manually in **Application.onCreate**.
### Setup Autopilot
The Airship SDK will automatically launch and load an Autopilot class that can be used to provide custom Airship config and to customize the Airship instance. Autopilot is the recommended approach to integrating the Airship SDK.
To start, create a new class that extends `Autopilot`. This class
needs to be public and have a default no argument constructor.
#### Android Kotlin
```kotlin
class SampleAutopilot : Autopilot() {
}
```
#### Android Java
```java
public class SampleAutopilot extends Autopilot {
}
```
Add metadata within the `application` entry to the
`AndroidManifest.xml`. The name of the meta-data `com.urbanairship.autopilot` and
the value should be the fully qualified class name.
**Register the extended Autopilot class**
```xml
```
### Configuring Airship
Airship requires your project's [App Key](https://www.airship.com/docs/reference/glossary/#app_key) and [App Secret](https://www.airship.com/docs/reference/glossary/#app_secret)to authenticate your application. To find them, select the dropdown menu (▼) next to your project name, and then **Project details**.
The [AirshipConfigOptions](https://www.airship.com/docs/reference/libraries/android-kotlin/latest/urbanairship-core/com.urbanairship/-airship-config-options/index.html)
provides the app credentials and common settings for Airship. To provide an `AirshipConfigOptions` instance, override the method `createAirshipConfigOptions` in your autopilot class.
#### Android Kotlin
```kotlin
class SampleAutopilot : Autopilot() {
override fun createAirshipConfigOptions(context: Context) =
airshipConfigOptions {
// Set default credentials. Alternatively, you can set production and development separately.
setAppKey("YOUR_DEFAULT_APP_KEY")
setAppSecret("YOUR_DEFAULT_APP_SECRET")
// Set site. (Either Site.SITE_US or Site.SITE_EU)
setSite(Site.SITE_US)
// Notification config
setNotificationAccentColor(context.getColor(R.color.accent))
setNotificationIcon(R.drawable.ic_notification)
setNotificationChannel(NotificationProvider.DEFAULT_NOTIFICATION_CHANNEL)
}
override fun onAirshipReady(context: Context) {
// Airship is ready! Configure additional settings here.
Airship.push.userNotificationsEnabled = true
}
}
```
#### Android Java
```java
public class SampleAutopilot extends Autopilot {
@Override
public @Nullable AirshipConfigOptions createAirshipConfigOptions(@NotNull Context context) {
return AirshipConfigOptions.newBuilder()
// Set default credentials. Alternatively, you can set production and development separately.
.setAppKey("YOUR_DEFAULT_APP_KEY")
.setAppSecret("YOUR_DEFAULT_APP_SECRET")
// Set site. (Either SITE_US or SITE_EU)
.setSite(Site.SITE_US)
// Notification config
.setNotificationAccentColor(context.getColor(R.color.accent))
.setNotificationIcon(R.drawable.ic_notification)
.setNotificationChannel(NotificationProvider.DEFAULT_NOTIFICATION_CHANNEL)
.build();
}
@Override
public void onAirshipReady(@NotNull Context context) {
// Airship is ready! Configure additional settings here.
Airship.getPush().setUserNotificationsEnabled(true);
}
}
```
> **Note:** Airship config defaults to the US cloud site (`Site.SITE_US`). If your application is set up for the EU site, you must set the site on the config options to `Site.SITE_EU`.
### Customizing Airship behavior {#customizing-airship}
Airship provides common config options in `AirshipConfig`, however some more advanced configuration must be set directly on the Airship instance. Custom push handling, deep linking, etc., should be configured during `onAirshipReady` callback. This will ensure Airship is properly configured before handling any messages.
The `onAirshipReady` callback will be called on a background thread during Airship init process. Applications should not do any long-running work during this callback, or it might prevent Airship from being ready in time to process a push notification.
#### Android Kotlin
```kotlin
class SampleAutopilot : Autopilot() {
// ...
override fun onAirshipReady(context: Context) {
// Custom Message Center
MessageCenter.shared().setOnShowMessageCenterListener { messageId: String? ->
true
}
// Notification handling
Airship.push.addPushListener(MyPushListener())
Airship.push.notificationListener = MyNotificationListener()
// etc...
}
}
```
#### Android Java
```java
public class SampleAutopilot extends Autopilot {
// ...
@Override
public void onAirshipReady(@NonNull Context context) {
MessageCenter.shared().setOnShowMessageCenterListener(messageId -> {
return true;
});
Airship.getPush().addPushListener(new MyPushListener());
Airship.getPush().setNotificationListener(new MyNotificationListener());
}
}
```
## Test the integration
After completing the setup, verify your integration:
1. **Build and run your app** on your Android device or emulator.
2. **Check the logcat output** for Airship channel creation:
- Look for a log message similar to: `Airship channel created: `
- The channel ID will appear in the log output, confirming successful initialization.
- For more detailed logging, see [Logging](https://www.airship.com/docs/developer/sdk-integration/android/installation/logging/).
If you see the channel ID in the logcat and no errors, your integration is successful. You can now proceed with configuring [deep links](https://www.airship.com/docs/developer/sdk-integration/android/deep-links/), [push notifications](https://www.airship.com/docs/developer/sdk-integration/android/push-notifications/getting-started/), and other Airship features.
If you don't see a channel ID in the logcat or encounter errors during initialization, see [Troubleshooting Initialization](https://www.airship.com/docs/developer/sdk-integration/android/troubleshooting/initialization/) for common problems and solutions.
# Advanced Integration
> Additional configuration for specific use-cases.
## Configuring Airship via properties file
If no config is returned from `Autopilot.createAirshipConfigOptions`, Airship will default to loading config from the `airshipconfig.properties` file, located in your application's `assets` directory. This can be useful for in Apps with multiple build variants, or for keeping credentials out of version control.
Sample `assets/airshipconfig.properties` file:
```properties
# App credentials
app_key = YOUR_DEFAULT_APP_KEY
app_secret = YOUR_DEFAULT_APP_SECRET
# Optionally, set separate production credentials
# production_app_key = YOUR_PRODUCTION_APP_KEY
# production_app_secret = YOUR_PRODUCTION_APP_SECRET
# development_app_key = YOUR_DEVELOPMENT_APP_KEY
# development_app_secret = YOUR_DEVELOPMENT_APP_SECRET
# Set the cloud site (either SITE_US or SITE_EU)
site = SITE_US
# In production (true/false)
in_production = false
# Enable Airship debug logging (true/false)
is_airship_debug_enabled = true
# Notification configuration
notification_icon = @drawable/ic_notification
notification_accent_color = @color/accent
notification_channel = default_channel
```
The keys used in the `airshipconfig.properties` file match the field names in [AirshipConfigOptions](https://www.airship.com/docs/reference/libraries/android-kotlin/latest/urbanairship-core/com.urbanairship/-airship-config-options/index.html)
, converted to snake-case.
## URL allowlist
The [UrlAllowList](https://www.airship.com/docs/reference/libraries/android-kotlin/latest/urbanairship-core/com.urbanairship/-url-allow-list/index.html)
controls which URLs the Airship SDK is able to act on. The SDK divides up usages of URLs into two different scopes:
- `SCOPE_OPEN_URL`: Only URLs allowed for this scope can be opened from an action, displayed in landing page, or displayed in an HTML in-app message. Defaults to allowing all URLs if not specified in the config.
- `SCOPE_JAVASCRIPT_INTERFACE`: These URLs are checked before the Airship JavaScript interface is injected into the webview. Defaults to any Airship originated URLs.
Allowed URLs should be provided when configuring the Airship Config options.
**Valid URL pattern syntax**
```text
:= '*' | '://'/ | '://' | ':/' | ':///'
:=
:= '*' | '*.' |
:=
```
## Custom Firebase applications
By default, the Airship SDK will use the data in `google-services.json` to configure Firebase Messaging. If your app makes use of multiple Firebase projects, you can instruct the Airship SDK to use a specific named Firebase project for Firebase Cloud Messaging (FCM).
In order to create a secondary Firebase application instance, you'll need to manually configure `FirebaseOptions` and initialize your secondary Firebase application. The Firebase application should be initialized before `takeOff`, or during the `onAirshipReady` callback.
#### Android Kotlin
```kotlin
// Manually configure FirebaseOptions for the secondary Firebase Application.
val options = FirebaseOptions.Builder()
.setProjectId("Your Firebase Project ID")
.setApplicationId("Your Firebase Application ID")
.setApiKey("Your Firebase API key")
.setGcmSenderId("Your GCM Sender ID")
.build()
// Initialize the secondary Firebase Application.
Firebase.initialize(context, options, "secondary")
```
#### Android Java
```java
// Manually configure FirebaseOptions for the secondary Firebase Application.
FirebaseOptions options = new FirebaseOptions.Builder()
.setProjectId("Your Firebase Project ID")
.setApplicationId("Your Firebase Application ID")
.setApiKey("Your Firebase API key")
.setGcmSenderId("Your GCM Sender ID")
.build();
// Initialize the secondary Firebase Application.
FirebaseApp.initializeApp(context, options, "secondary");
```
Now that you have initialized your secondary Firebase application, you can configure
the Airship SDK to use it by setting [Firebase app name](https://www.airship.com/docs/reference/libraries/android-kotlin/latest/urbanairship-core/com.urbanairship/-airship-config-options/-builder/set-fcm-firebase-app-name.html)
name in the `AirshipConfigOptions` instance.
## Extending the FirebaseMessagingService
If your application uses its own `FirebaseMessagingService` or some other third party push provider, you will also need to forward `onNewToken` and `onMessageReceived` calls to
the Airship SDK.
#### Android Kotlin
```kotlin
fun onNewToken(token: String) {
AirshipFirebaseIntegration.processNewToken(getApplicationContext(), token)
}
fun onMessageReceived(remoteMessage: RemoteMessage) {
AirshipFirebaseIntegration.processMessageSync(getApplicationContext(), message)
}
```
#### Android Java
```java
@Override
public void onNewToken(String token) {
AirshipFirebaseIntegration.processNewToken(getApplicationContext(), token);
}
@Override
public void onMessageReceived(RemoteMessage message) {
AirshipFirebaseIntegration.processMessageSync(getApplicationContext(), message);
}
```
## Extending the HmsMessageService
If your application uses its own `HmsMessageService` or some other third party push provider, you will also need to forward `onNewToken` and `onMessageReceived` calls to
the Airship SDK.
#### Android Kotlin
```kotlin
fun onNewToken(token: String?) {
AirshipHmsIntegration.processNewToken(getApplicationContext(), token)
}
fun onMessageReceived(remoteMessage: RemoteMessage) {
AirshipHmsIntegration.processMessageSync(getApplicationContext(), message)
}
```
#### Android Java
```java
@Override
public void onNewToken(@Nullable String token) {
AirshipHmsIntegration.processNewToken(getApplicationContext(), token);
}
@Override
public void onMessageReceived(RemoteMessage message) {
AirshipHmsIntegration.processMessageSync(getApplicationContext(), message);
}
```
# Logging
> Configure log levels, privacy settings, and custom log handlers to control how the Airship SDK logs messages.
The Airship SDK provides configurable log levels to help you debug issues without overwhelming the console. If you don't configure logging, the SDK uses **Info** for development builds and **Error** for production builds with **private** privacy level.
## Log levels
The log level acts as a minimum threshold—only logs at that level and higher will be logged. Available log levels, ordered from most to least verbose:
| Log Level | Prefix | Description |
| :-------- | :----- | :---------- |
| **Verbose** | `V` | Highly detailed SDK status for deep debugging and troubleshooting |
| **Debug** | `D` | General SDK status with more detailed information than Info |
| **Info** | `I` | General SDK status and lifecycle events |
| **Warning** | `W` | API deprecations, invalid setup, and other recoverable issues |
| **Error** | `E` | Critical errors and exceptions that the SDK cannot gracefully handle |
| **Assert** | — | Disables all logging |
## Log privacy levels
Control the visibility of log contents using privacy levels. This is especially useful when debugging release builds without exposing sensitive information.
- **private** (default): Uses the standard `android.util.Log`. In production builds, `verbose` and `debug` messages are completely dropped and will not be logged. Use this for production builds to protect sensitive data.
- **public**: Sends all logs with a `public` privacy level, making it easier to capture detailed information from release builds. To ensure visibility in production builds, `verbose` and `debug` messages are automatically elevated to the `info` log level.
> **Note:** When using `public` privacy level, `verbose` and `debug` log messages are automatically elevated to `info` level to ensure all detailed logs are visible when debugging production builds.
## Configuration
You can set separate log levels and privacy levels for development and production builds in your Airship config during [takeOff](https://www.airship.com/docs/developer/sdk-integration/android/installation/getting-started/#calling-takeoff).
### Common configuration
Typical setup: more verbose logging for development, minimal logging for production:
#### Kotlin
```kotlin
airshipConfigOptions {
// Development: verbose logging for debugging
setDevelopmentLogLevel(AirshipConfigOptions.LogLevel.VERBOSE)
setDevelopmentLogPrivacyLevel(AirshipConfigOptions.PrivacyLevel.PUBLIC)
// Production: minimal logging to reduce noise
setProductionLogLevel(AirshipConfigOptions.LogLevel.ERROR)
setProductionLogPrivacyLevel(AirshipConfigOptions.PrivacyLevel.PRIVATE)
// ...
}
```
#### Java
```java
AirshipConfigOptions.newBuilder()
// Development: verbose logging for debugging
.setDevelopmentLogLevel(AirshipConfigOptions.LogLevel.VERBOSE)
.setDevelopmentLogPrivacyLevel(AirshipConfigOptions.PrivacyLevel.PUBLIC)
// Production: minimal logging to reduce noise
.setProductionLogLevel(AirshipConfigOptions.LogLevel.ERROR)
.setProductionLogPrivacyLevel(AirshipConfigOptions.PrivacyLevel.PRIVATE)
...
```
### Debugging production issues
When debugging issues in production builds, temporarily enable verbose logging to capture detailed SDK behavior:
#### Kotlin
```kotlin
airshipConfigOptions {
// Production debugging: enable verbose logs
setProductionLogLevel(AirshipConfigOptions.LogLevel.VERBOSE)
setProductionLogPrivacyLevel(AirshipConfigOptions.PrivacyLevel.PUBLIC)
// ...
}
```
#### Java
```java
AirshipConfigOptions.newBuilder()
// Production debugging: enable verbose logs
.setProductionLogLevel(AirshipConfigOptions.LogLevel.VERBOSE)
.setProductionLogPrivacyLevel(AirshipConfigOptions.PrivacyLevel.PUBLIC)
...
```
## Verifying log level
You can confirm the current log level by checking the Airship log output in your console. On Android, Airship logs use the standard log priority tags (e.g., `V`, `D`, `I`). To find them, filter your logs for the tag **`UAirship`** and observe the priority letter.
**Example (Verbose Log)**
The `V` in the output indicates a `VERBOSE` level log.
```text
Sample - UALib com.urbanairship.sample V UAirship - !SDK-VERSION-STRING!:com.urbanairship.android:urbanairship-core:16.9.0
```
## Custom log handler
You can provide a custom log handler to intercept and handle all Airship log messages. This is useful when you need to integrate Airship logs with your own logging system or customize how logs are formatted or stored.
When a custom log handler is set, the default Airship log handler is completely replaced. Log level filtering is performed before your handler is called, so your handler will only receive logs that meet the configured log level threshold.
Implement the `AirshipLogHandler` interface and set it on `UALog.logHandler` before calling `Airship.takeOff()`:
#### Kotlin
```kotlin
// Example log handler to forward logs to Android logcat and upload to a remote logging service
val customLogHandler = AirshipLogHandler { tag, logLevel, throwable, message ->
val msg = message()
// Forward to Android logcat
when (logLevel) {
Log.VERBOSE -> Log.v(tag, msg, throwable)
Log.DEBUG -> Log.d(tag, msg, throwable)
Log.INFO -> Log.i(tag, msg, throwable)
Log.WARN -> Log.w(tag, msg, throwable)
Log.ERROR -> Log.e(tag, msg, throwable)
else -> Unit // Do nothing
}
// Optionally: send to remote logging service
MyLoggingService.log(tag, logLevel, msg, throwable)
}
// Set the custom handler globally, before Airship.takeOff()
UALog.logHandler = customLogHandler
```
#### Java
```java
AirshipLogHandler logHandler = new AirshipLogHandler() {
@Override
public void log(@NotNull String tag, int logLevel, @Nullable Throwable throwable, @NotNull Function0<@NotNull String> message) {
String msg = message.invoke();
// Forward to Android logcat
switch (logLevel) {
case Log.VERBOSE:
Log.v(tag, msg, throwable);
break;
case Log.DEBUG:
Log.d(tag, msg, throwable);
break;
case Log.INFO:
Log.i(tag, msg, throwable);
break;
case Log.WARN:
Log.w(tag, msg, throwable);
break;
case Log.ERROR:
Log.e(tag, msg, throwable);
break;
default:
break; // Do nothing
}
// Optionally: send to remote logging service
MyLoggingService.log(tag, logLevel, msg, throwable);
}
};
// Set the custom handler globally, before Airship.takeOff()
UALog.setLogHandler(logHandler);
```
# Locale
> Configure locale behavior and override the default locale that Airship uses.
Airship uses the [Locale](https://www.airship.com/docs/reference/glossary/#locale) for various SDK operations. By default, the SDK automatically uses the device's locale settings, but you can configure it to use the user's preferred language or override it programmatically.
## Overriding the locale
You can override the locale programmatically at runtime, which takes precedence over the device's locale settings.
#### Kotlin
```kotlin
Airship.localeManager.setLocaleOverride(Locale.GERMANY)
```
#### Java
```java
Airship.getLocaleManager().setLocaleOverride(new Locale("de"));
```
## Clearing the locale override
To remove a locale override and return to using the device's locale settings:
#### Kotlin
```kotlin
Airship.localeManager.setLocaleOverride(null)
```
#### Java
```java
Airship.getLocaleManager().setLocaleOverride(null);
```
## Getting the current locale
To retrieve the locale that Airship is currently using:
#### Kotlin
```kotlin
val airshipLocale = Airship.localeManager.locale
```
#### Java
```java
Locale airshipLocale = Airship.getLocaleManager().getLocale();
```