A.J. Kueterman
Posted on December 13, 2019
Recently, I worked on an Android project where we wished to abstract away some Android resource files from the root application. Instead, we wanted our app - and any subsequent app - to consume Android resources (colors, strings, dimensions) from a library. In addition, because we wanted to define these resources independently of the apps consuming them, we needed to build our library separate from our main application.
After some effort, and some trials working with Gradle and Android dependencies, we were able to build our Android library for distribution to consuming apps. Let's take a minute and talk about what was required to accomplish this, and point out some pitfalls to help you build your first external Android library.
JAR/AAR
A bundle of Java/Kotlin source code without any Android dependencies can be packaged and consumed as a .jar
file. This makes things simple and enables cross platform code that isn't dependent on Android.
However, we immediately realized we'd need access to Android stuff in our project. We need access to Android resources, which can only be accessed in an Android Library. Because of this, we know that we'll need to build an Android Archive (.aar
) file.
An AAR file can't be tar
or jar
'd up easily from the command line. Because it's an Android Library ('com.android.library'
) it needs to be built using Gradle.
Gradle Dependencies
In our case, we were using a Node.js project to output our Android Library resources. This meant that we had a separate, non-Android app, building our eventual AAR output.
The first attempt at this used a packaged version of the Gradle wrapper from a dependency file on the machine, and attempted to package up a library from it's source code defined in the Node app. We quickly ran into issues here, because defining a Library build.gradle file without a parent build.gradle is something of an anti-pattern for an Android project structure.
Normally you have a 'project' build.gradle file at the root, then an application (usually 'app') module with it's own build.gradle file. Then, each module defined in the project would have it's own module build.gradle file. Trying to build a module without defining a parent 'project' build.gradle file isn't the norm - and was causing problems in our implementation.
Instead, what we ended up needing to do was build a shell Android app project with an included sub-module. The shell project defined the Android project structure at a high level, including our library sub-module structure, and was zipped up and packaged with our Node app. Then, as part of our Node app's build process, we would un-zip the Android project and update the sub-module directories to prepare our library. Finally, we would use the included Gradle wrapper in our Library container project to build our submodule.
Besides the clunky-ness of creating an entire Android application project just to build a module - this worked like a charm!
Android Dependencies
After fixing our project structure and defining the proper Gradle files, our app successfully built and packaged an AAR file. However, importing this file into an Android project didn't correctly link Android Resources.
What we soon learned was that including Android Resources is part of the Android AppCompat library. We updated our Android Library build.gradle file to include this dependency, and suddenly we had linked resources:
dependencies {
...
implementation 'androidx.appcompat:appcompat:1.1.0'
}
It turns out that when library modules are packaged up their Android res directory is combined into one values.xml file, including colors, dimensions, etc. In addition, they are linked with the R.txt file generated in the build so they can be referenced by consuming applications. This resource linking using R.txt is done by the Android AppCompat lib.
[Update] Continuous Integration
Part of our project goals included building this library on a build server using Jenkins. One of the hard-learned lessons of this endeavor was that in using our 'container' Android project to build our AAR files on a build server isn't the same as doing Android Studio builds or even local command line builds.
Biggest takeaway was a stupid-simple misstep: make sure your build machine sets the right ANDROID_HOME
directory!
Android for a Reason
In my research for solving this particular problem I came across a wide array of responses about sharing resources across multiple projects. Overall, the conclusions on Stack Overflow were pretty grim, with people mostly just explaining why shared Resources are hard in Android.
What I've learned is that packaging a bundle of resources to share across apps is very doable, you just have to respect the structure of a typical Android project and understand a bit about how AAR libraries are built. The more you know 🌟!
Posted on December 13, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.