Skip to content

Android application

Use the android/app product type in a module to build an Android application.

Using IntelliJ IDEA?

Make sure to install the Android plugin to get proper support for Android-specific features.

Module layout

Here is an overview of the module layout for an Android application:

my-android-app/
├─ assets/ # (1)!
├─ jniLibs/ # (2)!
│  ╰─ arm64-v8a/
│     ╰─ libfoo.so
├─ res/ # (3)!
│  ├─ drawable/
│  │  ╰─ graphic.png
│  ├─ layout/
│  │  ├─ main.xml
│  │  ╰─ info.xml
│  ╰─ ...
├─ resources/
├─ src/
│  ├─ AndroidManifest.xml # (4)!
│  ╰─ MainActivity.kt # (5)!
├─ test/
│  ╰─ MainTest.kt
├─ module.yaml
╰─ proguard-rules.pro # (6)!
  1. assets and res are standard Android resource directories. See the official Android docs.
  2. Pre-compiled native libraries (.so files) organized by ABI (e.g. arm64-v8a, x86_64). See the official Android docs.
  3. assets and res are standard Android resource directories. See the official Android docs.
  4. The manifest file of your application.
  5. An activity (screen) of your application.
  6. Optional configuration for R8 code shrinking and obfuscation. See code shrinking.

Entry point

The application's entry point is specified in the AndroidManifest.xml file according to the official Android documentation:

src/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application>
    <activity android:name="com.example.myapp.MainActivity" android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

Running your application

You can run your application using the kotlin run command.

It installs and starts the application on a connected device or emulator, starting a new emulator if necessary.

There are no prerequisites for this. All the required tools, including the Android SDK, will be provisioned if not present (you will need to accept licenses).

Run in IntelliJ IDEA

IntelliJ IDEA with the Kotlin Toolchain plugin automatically detects the android/app product type and provides a run configuration for it:

Packaging

You can use the build command to create an APK, or the package command to create an Android Application Bundle (AAB).

The package command will not only build the AAB, but also minify/obfuscate it with R8, and sign it when signing is enabled. See the dedicated signing and code shrinking sections below to learn how to configure this.

For example, build an AAB for the android-app module with:

kotlin package -m android-app

For an android/app module, the Android platform, AAB format, and release variant are selected automatically. The command prints the path to the generated AAB. For a module named android-app, the default path, relative to the project root, is:

build/tasks/_android-app_bundleAndroid/gradle-project-release.aab

Note

This path is temporary: the build artifact layout will be revised in a future release. Use the path printed by the command to locate your bundle.

Resolving duplicate Java resources

Dependencies can package Java resources under the same path. If Android packaging fails in MergeJavaResWorkAction with an error such as 2 files found with path ..., use settings.android.resourcePackaging to tell the Android packager how to handle the conflict.

For example, the following configuration excludes a duplicated resource from the APK:

settings:
  android:
    resourcePackaging:
      excludes:
        - META-INF/versions/9/OSGI-INF/MANIFEST.MF

Choose the rule that matches the resource's semantics:

  • excludes omits matching resources from the APK.
  • pickFirsts packages only the first matching resource.
  • merges concatenates all matching resources into a single APK entry.

The values are glob patterns accepted by Android's Packaging.Resources API. See the resourcePackaging reference for all available options.

Filtering native library ABIs

An Android package may carry pre-compiled native libraries (.so files) grouped by ABIs, and by default it carries every ABI it can find.

Native libraries come from two sources: the module's own jniLibs directory, and those dependencies of the module that contain native libraries, each bringing the ABIs it was built for. Most dependencies contain none at all, but the ones that do could have different ABIs coverage.

This matters because Android picks a single ABI per installation: it takes the first entry of the device's supported ABI list that is present in the package, and then only that one lib/<abi>/ directory is used. If an ABI doesn't carry every mandatory native library that the other ABIs carry, the app might fail at runtime with UnsatisfiedLinkError on every device that selects it.

The Kotlin Toolchain warns when it packages ABIs with inconsistent native libraries, reporting which ABI is missing which library.

Use settings.android.abiFilters to package only the ABIs that all of your native libraries support:

settings:
  android:
    abiFilters: [ arm64-v8a, x86_64 ]

Only the listed ABIs are packaged; any other lib/<abi>/ directory is dropped. Narrowing the list to ABIs whose native libraries are all present makes the package consistent, which silences the warning.

Sometimes an ABI is incomplete on purpose because the missing library is optional: your code guards the call to System.loadLibrary and degrades gracefully when it isn't there. Only you can know that, which is why this is reported as a warning rather than an error.

The check runs whether or not you selected the ABIs yourself, since selecting them says nothing about the consistency of the libraries behind them: a dependency you add later can make a previously fine selection incomplete.

Code shrinking

When creating a release build with the Kotlin Toolchain, R8 will be used automatically, with minification and shrinking enabled. This is equivalent to the following Gradle configuration:

// Gradle equivalent of the Kotlin Toolchain's defaults
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))

You can create a proguard-rules.pro file in the module folder to add custom rules for R8.

├─ src/
├─ test/
├─ proguard-rules.pro
╰─ module.yaml

It is automatically used by the Kotlin Toolchain if present.

An example of how to add custom R8 rules can be found in the android-app module of the compose-multiplatform example project.

Signing

Enable signing in android-app/module.yaml to sign the release AAB during kotlin package -m android-app:

android-app/module.yaml
settings:
  android:
    signing: enabled

Create android-app/keystore.properties next to android-app/module.yaml:

android-app/
├─ module.yaml
╰─ keystore.properties  # create this file

Add the signing details to that file. Set storeFile to the name of the keystore to create in the same module directory:

android-app/keystore.properties
storeFile=release.keystore
storePassword=REPLACE_WITH_STRONG_STORE_PASSWORD
keyAlias=alias
keyPassword=REPLACE_WITH_STRONG_KEY_PASSWORD

Replace both password placeholders with your own strong passwords before generating the keystore.

From the android-app directory, generate the keystore using the values in keystore.properties:

kotlin tool generate-keystore --properties-file keystore.properties

The tool creates android-app/release.keystore; do not create it beforehand. When you build the AAB, the signing configuration reads android-app/keystore.properties and uses android-app/release.keystore to sign the bundle. A relative storeFile path is resolved from the module directory, so run generate-keystore from there as shown above.

Keep the signing files secure

Add release.keystore and keystore.properties to your Git ignore rules. Never commit either file to version control. Back up both files in a secure location. Losing the upload key requires an upload-key reset in Google Play Console.

Note

You can also pass in these details to generate-keystore as command line arguments. Invoke the tool with --help to learn more.

Publishing

Publish an android/app module to Google Play as a signed Android App Bundle (AAB). You need a Google Play Console developer account.

Configure the application

Set a unique application ID and an initial version in android-app/module.yaml:

android-app/module.yaml
product: android/app

settings:
  android:
    applicationId: com.example.myapp
    versionCode: 1
    versionName: "1.0"

The applicationId uniquely identifies the application in Google Play and cannot be changed after you upload the first artifact.

Before uploading the application:

  1. Prepare signing: create android-app/keystore.properties and generate the upload key in android-app/release.keystore.
  2. Package the application with kotlin package -m android-app. This produces a signed release AAB and prints its path. Use that AAB for the upload.

Upload the bundle

If you haven't created the application yet, open Google Play Console and select Create app to set it up before uploading your first bundle.

Open the application in Google Play Console, go to Test and release, and select the appropriate testing or production track. Create a release and upload the generated .aab file.

Google Play validates and processes the bundle before making the release available to testers or users.

For future uploads

Increase versionCode in android-app/module.yaml before every new upload: Google Play rejects bundles with a previously used version code. Update the user-facing versionName when publishing a new application version.

Build from IntelliJ IDEA or Android Studio

Generating a signed bundle from Build | Generate Signed App Bundle or APK is not supported for Kotlin Toolchain projects yet. Use the Kotlin CLI to create the AAB.

Parcelize

If you want to automatically generate your Parcelable implementations, you can enable Parcelize as follows:

settings:
  android:
    parcelize: enabled

With this simple toggle, the following class gets its Parcelable implementation automatically without spelling it out in the code, just thanks to the @Parcelize annotation:

import kotlinx.parcelize.Parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable

While this is only relevant on Android, sometimes you need to share your data model between multiple platforms. However, the Parcelable interface and @Parcelize annotation are only present on Android. But fear not, there is a solution described in the official documentation. In short:

  • For android.os.Parcelable, you can use the expect/actual mechanism to define your own interface as typealias of android.os.Parcelable (for Android), and as an empty interface for other platforms.
  • For @Parcelize, you can simply define your own annotation instead, and then tell Parcelize about it (see below).

For example, in common code:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class MyParcelize

expect interface MyParcelable
Then in Android code:
actual typealias MyParcelable = android.os.Parcelable
And in other platforms:
// empty because nothing is generated on non-Android platforms
actual interface MyParcelable

You can then make Parcelize recognize this custom annotation using the additionalAnnotations option:

settings:
  kotlin:
    # for the expect/actual MyParcelable interface
    freeCompilerArgs: [ -Xexpect-actual-classes ]
  android:
    parcelize:
      enabled: true
      additionalAnnotations: [ com.example.MyParcelize ]

Google Services and Firebase

To enable the google-services plugin, place your google-services.json file in the module containing an android/app product, next to module.yaml.

╰─ androidApp/
   ├─ src/
   ├─ google-services.json
   ╰─ module.yaml

This file will be found and consumed automatically.