Android Scala Projects
Mill can build Android applications written in Scala.
Currently, Scala code is compiled to JVM bytecode and packaged into an Android APK/AAB, running on the Android Runtime (ART) just like Java or Kotlin sources.
Support for native-image-based Scala applications is still a work in progress. The JVM-based workflow is functional but still experimental, following the same structure as the Java and Kotlin workflows. The main difference is that you use the AndroidScalaModule and AndroidAppScalaModule traits instead of the Java or Kotlin equivalents.
Simple Android Hello World Application
This example demonstrates how to configure a simple "Hello World" Android application written in Scala targeting the JVM.
package build
import mill.*, androidlib.*, scalalib.*
import mill.api.{ModuleRef, Task}
object androidSdkModule0 extends AndroidSdkModule {
def buildToolsVersion = "35.0.0"
}
object app extends AndroidAppScalaModule {
def scalaVersion = "3.3.4"
def androidSdkModule = mill.api.ModuleRef(androidSdkModule0)
def androidMinSdk = 26
def androidCompileSdk = 34
def androidApplicationId = "com.helloworld.app"
def androidApplicationNamespace = "com.helloworld.app"
override def mvnDeps = Seq(
mvn"com.google.code.gson:gson:2.11.0"
)
/**
* Configuration for ReleaseKey
* WARNING: Replace these default values with secure and private credentials before using in production.
* Never use these defaults in a production environment as they are not secure.
* This is just for testing purposes.
*/
def androidReleaseKeyName: Option[String] = Some("releaseKey.jks")
def androidReleaseKeyAlias: T[Option[String]] = Task { Some("releaseKey") }
def androidReleaseKeyPass: T[Option[String]] = Task { Some("MillBuildTool") }
def androidReleaseKeyStorePass: T[Option[String]] = Task { Some("MillBuildTool") }
override def androidVirtualDevice = super.androidVirtualDevice().withName("scala-test")
override def androidEmulatorPort: String = System.getenv.getOrDefault("EMULATOR_PORT", "5558")
override def androidIsDebug: T[Boolean] = Task { false }
object test extends AndroidAppScalaTests, TestModule.Junit4 {
def junit4Version = "4.13.2"
}
object it extends AndroidAppScalaInstrumentedTests {
override def androidIsDebug: T[Boolean] = Task {
false
}
def mvnDeps = Seq(
mvn"androidx.test.ext:junit:1.2.1",
mvn"androidx.test:runner:1.6.2",
mvn"androidx.test:rules:1.6.1",
mvn"androidx.test:core:1.6.1",
mvn"androidx.test.espresso:espresso-core:3.5.1",
mvn"junit:junit:4.13.2"
)
}
}
> ./mill show app.androidApk
".../out/app/androidApk.dest/app.apk"
> ./mill show app.it.androidTestApk
".../out/app/it/androidApk.dest/app.apk"
> ./mill show app.test
...
{
"msg": "",
"results": [
{
"fullyQualifiedName": "com.helloworld.ExampleUnitTest.testParseMessage",
"selector": "com.helloworld.ExampleUnitTest.testParseMessage",
"duration": ...,
"status": "Success"
}
]
}
...
> ./mill show app.createAndroidVirtualDevice
...Name: scala-test, DeviceId: medium_phone...
> ./mill show app.startAndroidEmulator
> ./mill show app.androidInstall
...All files should be loaded. Notifying the device...
> ./mill show app.it
...
{
"msg": "",
"results": [
{
"fullyQualifiedName": "com.helloworld.app.ExampleInstrumentedTest.useAppContext",
"selector": "com.helloworld.app.ExampleInstrumentedTest.useAppContext",
"duration": ...,
"status": "Success"
},
{
"fullyQualifiedName": "com.helloworld.app.ExampleInstrumentedTest.testActivityContent",
"selector": "com.helloworld.app.ExampleInstrumentedTest.testActivityContent",
"duration": ...,
"status": "Success"
}
]
}
...
> ./mill show app.stopAndroidEmulator
> ./mill show app.deleteAndroidVirtualDevice