I’ve been having quite a time playing around with dependency management on Android. I am following the typical Model-View-Controller design pattern, and I’ve got an internal library that I want to reference, which handles the Model and Controller aspects of my app. This library is then included by my “shell” app, which has all the Views. So, what is the best way to manage this library? Here are a few things I’ve tried so far.
Git Submodules
Git submodules are a way of referencing a specific git commit SHA of another repository within your own project. This is my personal favorite way to do dependency management at the moment, but most people dislike it. For one thing, it is easy to mess up if you don’t get your head around what exactly Git is doing.
Basically, you set up your main repo to to pull down another repo (at a specific commit) at checkout time. You have to remember to use the –recursive flag when checking out / cloning your main repo, otherwise you won’t get the submodule.
To set up submodules, you add a .gitmodules file to the top level directory, and specify the name of your repo:
[submodule "my-repo-name"]
path = my-repo-name
url = ssh://git@github.com/my-repo-name.git
Then, in the app level build.gradle file, you include the submodule project as a dependency:
compile project(':my-repo-name:MyCoolLib')
If you do everything correctly, you will end up with your library repo sitting inside your main project.
PROS: You can now step into the library code at any point when debugging. Versioning is very easy to track as well, because everything is done in conjunction with git. Anyone who checks out your main repo will immediately have the correct version of the library included.
CONS: A git submodule is really just a commit SHA reference floating in outer space. If you want to make any changes you have to create a branch for the library repo first! If you don’t, you will be lead down a path of sadness and despair, also known as “committing on a detached head.”
Also (and this REALLY bugs me), the commit SHA you reference for your git submodule doesn’t appear in any file anywhere that you are committing. You can check the current SHA reference by running:
git submodule status
When you change the library code by checking out a new branch or committing to your branch, the new commit SHA reference will be updated, and you can see this reflected when you run the above command. But I don’t like it. It adds a layer of mystery that I don’t think is necessary.
Artifactory
Artifactory is a private hosted repository. Basically, its like a private version of Maven Central. You include your .aar Artifactory references in your project very similar to a regular maven reference. You also include your personal authentication for your Artifactory repository in a gradle.properties file.
Setting up Artifactory is a 2-step process. First you need to be able to publish your library code to Artifactory. Next you need to be able to pull down your library aar from Artifactory via your shell project. The process is quite lengthy, and can be found here.
PROS: Artifactory lets you reference an aar, instead of lugging around your entire library project with you. Having your library as an aar also promotes separation of concerns; ideally you should not be working on library and shell code at the same time. In times where you would like to debug the library and shell code together, you can download a source jar and attach it to your project, allowing you to step into the library code.
CONS: The biggest con for me is the lack of accountability and versioning. Theoretically, a developer could publish code to Artifactory that had never been reviewed or even committed! There is no tie-in to git for tracking versions.
Since all versioning is manual, there is also the question of when you bump the version. Every commit seems way over the top. Every feature seems very frequent as well, but also not frequent enough. When other developers pull down your project, how to they know what they are really getting in your library? Every release seems logical for delivering content to other teams, but definitely not frequent enough for an internal development cycle.
Also, even though you can attach sources, it is a very elaborate process. And, there is no guarantee that the sources jar you download matches exactly with the aar you are referencing (again, no git tie-in, anyone can publish anything).
Finally, Artifactory versioning fails at the “time machine” test; depending on how you are doing your versioning, it is very unlikely you could roll back to a very specific (non-release) version of the shell app and get the actual library code that was used with that version of the shell.
Artifactory + Local Project Reference
Since we were using Artifactory, I figured we could leverage its strengths in terms of “feature releases,” but it would be nice to have another option for developers while we work on the specific feature.
I created a flag for building with Artifactory or locally:
def useArtifact = true
if ( useArtifact ) {
compile(group: 'com.kiodev', name: 'MyCoolLib', version: '1.0.0', ext: 'aar')
}
else {
compile project(':MyCoolLib')
}
The flag can be toggled by developers to build with either the Artifact or the local library. We put our local library outside the shell project, and use a Gradle loophole to compile it in via our project level settings.gradle file:
include ':app', ':MyCoolLib'
project(':MyCoolLib').projectDir = new File(settingsDir, '../my-repo-name/MyCoolLib')
PROS: You and others can develop for the library and push to a branch, and wait to publish on Artifactory until your feature is done. You also can easily step through your code, from shell to library, and really know that the code you are running matches the code you are stepping through.
CONS: Having a flag inside the gradle file is annoying. It would be better if you could build with a flag on command line. We are also breaking gradle’s rules by including a reference to code that lives outside our main project. Also, if another developer checks out your work, it is not clear if they should be referencing the artifact or checking out a branch and running against the local lib.
Finally, this creates a problem for CI tools. If Jenkins is referencing your artifact before the feature work has been published, your build will fail. But, if you try and reference your ‘local’ project with Jenkins, it will also fail because the ‘local’ project doesn’t exist on Jenkins.
This is a big problem if you need a passing Jenkins build before code gets into master. I’m working on some potential solutions for this one, and will definitely write a post on it when I find something that works.
I’d love to hear what schemes you all use for managing internal libraries in your projects! Have you tried any of the ones I talked about? What’s your favorite?
Pingback: Big Android BBQ 2015 | KioDev