Skip to content

Commit

Permalink
Final Code Updated and Docs Generated, Ready to Merge After Review; F…
Browse files Browse the repository at this point in the history
  • Loading branch information
himanshumahajan138 committed Oct 21, 2024
1 parent d9d78b8 commit c81b905
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 50 deletions.
8 changes: 8 additions & 0 deletions docs/modules/ROOT/pages/kotlinlib/android-examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ This example demonstrates how to create a basic "Hello World" Android applicatio
using the Mill build tool. It outlines the minimum setup required to compile Kotlin code,
package it into an APK, and run the app on an Android device.

== Kotlin Android Jetpack Compose Application

include::partial$example/kotlinlib/android/2-jetpack-compose-hello-world.adoc[]

This example demonstrates how to create a basic "Hello World" Jetpack Compose Android application
using the Mill build tool. It outlines the minimum setup required to compile Kotlin code,
Compiling Jetpack Compose Libraries, package it into an APK, and run the app on an Android device.

== Understanding `AndroidSdkModule` and `AndroidAppKotlinModule`

The two main modules you need to understand when building Android apps with Mill
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.helloworld.app

import android.app.Activity
import android.os.Bundle
import android.widget.TextView
import android.view.Gravity
import android.view.ViewGroup.LayoutParams

class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Create a new TextView
val textView = TextView(this)

// Set the text to "Hello, World!"
textView.text = "Hello, World Kotlin!"

// Set text size
textView.textSize = 32f

// Center the text within the view
textView.gravity = Gravity.CENTER

// Set layout parameters (width and height)
textView.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)

// Set the content view to display the TextView
setContentView(textView)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.helloworld.app

import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val linearLayout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER
setBackgroundColor(Color.parseColor("#FFFFFF"))
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
}

val textView = TextView(this).apply {
text = "Hello, World!\nJetpack Compose!"
textSize = 32f
setTextColor(Color.parseColor("#34A853"))
gravity = Gravity.CENTER
setPadding(16, 16, 16, 16)
}

linearLayout.addView(textView)

setContentView(linearLayout)
}
}

This file was deleted.

46 changes: 46 additions & 0 deletions example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
// This build configuration file defines an Android application using Mill,
// Kotlin, and Jetpack Compose. It manages dependencies through Maven repositories
// and specifies the necessary configurations for the Android SDK and Kotlin versions.

// The main components include:
// - Maven repositories for dependency resolution
// - Android SDK configuration, including the build tools version
// - Android application module with required dependencies for Jetpack Compose

//// SNIPPET:BUILD
package build

Expand Down Expand Up @@ -43,3 +52,40 @@ object app extends AndroidAppKotlinModule {
".../out/app/androidApk.dest/app.apk"

*/

// To build the Android APK, you can use this command
// This command will output the path to the generated APK file
// This path indicates where the built APK can be found after executing the build process.
//
//
// Detailed Code Explanation
// 1. **Maven Repositories**:
// - `val maven_google`: This sequence defines the Maven repositories from which dependencies will be resolved.
// - Google Maven repository is essential for Android libraries.
// - Maven Central repository provides additional libraries.
//
// 2. **Android SDK Module**:
// - `object androidSdkModule0`: Defines an Android SDK module.
// - `def buildToolsVersion`: Specifies the version of the Android build tools to be used during the build process.
//
// 3. **Android Application Module**:
// - `object app`: Defines the main Android application module.
// - `def kotlinVersion`: Specifies the version of Kotlin used in the application.
// - `def androidSdkModule`: Links to the previously defined Android SDK module.
// - `override def mandatoryIvyDeps`: Overrides the default dependencies to include necessary Jetpack Compose libraries.
// - `def repositoriesTask`: Combines the default repositories with custom Maven repositories for dependency resolution.
//
// Directory Structure:
// The project structure is as follows:
//
// example/kotlinlib/android/2-jetpack-compose-hello-world
// ├── app
// │ ├── AndroidManifest.xml // Manifest file for the application
// │ └── src
// │ └── main
// │ └── java
// │ └── com
// │ └── helloworld
// │ └── app
// │ └── MainActivity.kt // Main activity of the application
// └── build.mill // Mill build configuration file
113 changes: 84 additions & 29 deletions scalalib/src/mill/javalib/android/AndroidAppModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,48 @@ trait AndroidAppModule extends JavaModule {
def artifactTypes: T[Set[coursier.Type]] = Task { super.artifactTypes() + coursier.Type("aar") }

/**
* Task to extract `classes.jar` files from AAR files in the classpath.
* Task to extract files from AAR files in the classpath.
*
* @return A sequence of `PathRef` pointing to the extracted JAR files.
*/
def androidUnpackArchives: T[Seq[PathRef]] = Task {
def androidUnpackArchives: T[(Seq[PathRef], Seq[PathRef])] = Task {
// Get all AAR files from the compile classpath
val aarFiles = super.compileClasspath().map(_.path).filter(_.ext == "aar").toSeq

aarFiles.map { aarFile =>
// Initialize sequences for jar files and resource folders
var jarFiles: Seq[PathRef] = Seq()
var resFolders: Seq[PathRef] = Seq()

// Process each AAR file
aarFiles.foreach { aarFile =>
val extractDir = T.dest / aarFile.baseName
os.unzip(aarFile, extractDir)
PathRef(extractDir / "classes.jar")

// Collect all .jar files in the AAR directory
jarFiles ++= os.walk(extractDir).filter(_.ext == "jar").map(PathRef(_))

// If the res folder exists, add it to the resource folders
val resFolder = extractDir / "res"
if (os.exists(resFolder)) {
resFolders :+= PathRef(resFolder)
}
}

// Return both jar files and resource folders
(jarFiles, resFolders)
}

/**
* Overrides the `resources` task to include resources from unpacked AAR files
*
* @return Combined sequence of original and filtered AAR resources.
*/
override def resources: T[Seq[PathRef]] = Task {
// Call the function to unpack AARs and get the jar and resource paths
val (_, resFolders) = androidUnpackArchives()

// Combine and return all resources
super.resources() ++ resFolders
}

/**
Expand All @@ -64,38 +94,63 @@ trait AndroidAppModule extends JavaModule {
* @return The updated classpath with `.jar` files only.
*/
override def compileClasspath: T[Agg[PathRef]] = Task {
super.compileClasspath().filter(_.path.ext == "jar") ++ Agg.from(androidUnpackArchives())
// Call the function to get jar files and resource paths
val (jarFiles, _) = androidUnpackArchives()
super.compileClasspath().filter(_.path.ext == "jar") ++ jarFiles
}

/**
* Generates the Android resources (such as layouts, strings, and other assets) needed
* for the application.
* Compiles and links Android resources using `aapt2`, generating the `R.java` file.
*
* This method uses the Android `aapt` tool to compile resources specified in the
* project's `AndroidManifest.xml` and any additional resource directories. It creates
* the necessary R.java files and other compiled resources for Android. These generated
* resources are crucial for the app to function correctly on Android devices.
* @return A `PathRef` to the directory containing the generated `R.java`.
*
* For more details on the aapt tool, refer to:
* For more details on the aapt2 tool, refer to:
* [[https://developer.android.com/tools/aapt2 aapt Documentation]]
*/
def androidResources: T[PathRef] = Task {
val genDir: os.Path = T.dest // Directory to store generated resources.

os.call(Seq(
androidSdkModule().aaptPath().path.toString, // Call aapt tool
"package",
"-f",
"-m",
"-J",
genDir.toString, // Generate R.java files
"-M",
androidManifest().path.toString, // Use AndroidManifest.xml
"-I",
androidSdkModule().androidJarPath().path.toString // Include Android SDK JAR
))

PathRef(genDir)
def androidResources: T[PathRef] = T.task {
val genDir: os.Path = T.dest // Directory to store generated R.java
val compiledResDir: os.Path = T.dest / "compiled" // Directory for compiled resources
val resourceDirs = resources().map(_.path).filter(os.exists) // Merge all resource directories
os.makeDir.all(compiledResDir)

try {
// Step 1: Compile resources using `aapt2 compile`
resourceDirs.foreach { resDir =>
os.call(Seq(
androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool
"compile",
"-o",
compiledResDir.toString, // Output directory for compiled resources
"--dir",
resDir.toString // Compile each resource directory
))
}

// Collect all .flat files explicitly
val flatFiles = os.walk(compiledResDir).filter(_.ext == "flat").map(_.toString)

// Step 2: Link resources using `aapt2 link`
os.call(Seq(
androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool
"link",
"-o",
(genDir / "resources.apk").toString, // Output APK or intermediate result
"-I",
androidSdkModule().androidJarPath().path.toString, // Include Android SDK JAR
"--manifest",
androidManifest().path.toString, // Use AndroidManifest.xml
"--auto-add-overlay", // Automatically add resources from overlays
"--java",
genDir.toString // Generate R.java in the genDir
) ++ flatFiles.flatMap(flatFile => Seq("-R", flatFile)))

PathRef(genDir) // Return the generated directory if successful

} catch {
case e: Throwable =>
T.log.info(s"Using Only Correct Resources")
PathRef(T.dest) // Return PathRef for only correct resources
}
}

/**
Expand Down
7 changes: 7 additions & 0 deletions scalalib/src/mill/javalib/android/AndroidSdkModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ trait AndroidSdkModule extends Module {
PathRef(buildToolsPath().path / "aapt")
}

/**
* Provides the path to AAPT2, used for resource handling and APK packaging.
*/
def aapt2Path: T[PathRef] = Task {
PathRef(buildToolsPath().path / "aapt2")
}

/**
* Provides the path to the Zipalign tool, which optimizes APK files by aligning their data.
*/
Expand Down

0 comments on commit c81b905

Please sign in to comment.