Reviving the cogwheel of legacy dependencies

sudesh sakthivel
Tickertape
Published in
7 min readJun 5, 2020

--

A guide to publishing dependency to an artifact repository and prevent legacy dependencies disappear into obsolescence.

Our app release came to a halt when we noticed that the crash rate started to spike on memory-constrained devices. On a closer look into the crash logs, we noticed that the issue was from an abstracted file in a legacy dependency. Until now the dependency which proved to be a great resource turned out to be a quagmire and any attempt of a desperate fix dragged me deeper into errors.

The next step I took was to check the issue section of the library. If the issue didn’t exist, raise a new one and anticipate that someone from the community would help resolve it. But, for a project whose development plug was pulled out years back, what possibly could be improved?. Nothing much. In fact, If you have such dependencies in your application, it could end up putting you in a perplexed situation as your app evolves.

So, I thought we should discuss how we can minimize the effect of a dependencies lifecycle on our application. But before that, we must understand what an artifact repository is and where it fits in the big picture.

What is an artifact repository

An “artifact” is an output of a sequential process applied to a codebase. You would have noticed aar or jar files generated after you included a dependency, these are binary files generated from dependencies source code and facilitated through a repository by a distribution channel.

But why use an artifact repository? can’t we use shared storage or directly build it on users machine?

The problem with shared storage is that it doesn’t provide any versioning solution. Moreover, the artifact management tools(Ex: Artifactory) are linked with the distribution channel(Ex: Bintray). This makes the build automation and delivery process much easier. It’s not recommended to build artifact locally on a users machine because they may not provide the exact environment required for creating a successful build. Also, the build process can be time-consuming and resource expensive.

Why and when do you need it

  1. Contribute to the community

Undoubtedly, today the most popular use of artifact repository is to share dependencies in the open-source community. Both individuals and organization contribute tools and extensions as dependencies and make it available through distribution channels like jCenter, nexus and mavenCentral.

2. Sharing project modules as artifacts

For apps having intricate modules, it is developed and maintained as a separate project. To use them in your application it’s recommended to include them as a dependency.

In the above diagram, you can see that the payment gateway and service integration module are dependent on the features of authentication module, this is known as transitive dependency. The transitive structure is supported by both Maven and Gradle. They can fetch artifacts, handle version conflicts, manage cache expiry and support dynamic versions. Modules are required to be included as dependencies to utilize these features.

💡Besides providing better versioning and artifact management. A module-based project can improve your build speed substantially.

3. Customized dependencies

If the enhancement or change is specific to your business requirement. You should host your dependency on a private artifact repository. Here at Tickertape, we use our customized version of a charting library built over open-source libraries.

4. Cutting down on waiting period for getting a merged update

The dependencies included in your projects are either jar or aar which is read-only, Say you have found some bugs and resolved it. Now, to get the updated changes, you have to wait until your pull request gets merged. Sometimes this process could take longer than expected and waiting for it isn’t practical, In such cases, you should use the forked version of the source code to build artifacts. By linking your project to an artifact repository you can obtain the dependency with all your changes included.

5. Adopt a legacy project

In Android open-source community you would see very few popular libraries and tools to be under long-term support, Rest of them suffer from the “tragedy of the commons”, a disproportionate imbalance between those using the project and those actively participating in its contribution. If you’re using one such project you might be the person who could get its clockwork ticking.

Workflow of an open-source project

When project owner or collaborators withdraw their support. The project becomes inactive and no other changes can be included from outside. This causes the artifact repository to provide a stale dependency. However, when you provide a forked version you can modify the source code and get the updated changes included.

For demonstration purpose, I will show you the steps on a dependency which hasn’t been updated over a long time, you can follow along with any project.

  1. Fork and clone a project(dependency): I will be using the following dependency SocketIO. this library enables you to establish a socket connection.
  2. Make the required changes: We will keep the changes minimal so that we can focus on getting the library hosted. Moreover, the improvements you could do on a project is innumerable. So if you are following this walkthrough you can do the following changes in the socket library.

Edit IOParser.java

  • Replace StringBuilder with StringBuffer: StringBuilder is not a thread-safe operation, If one thread is modifying a collection while another thread is iterating over it(Race condition) the system would throw Concurrent modification exception. This leads to an application crash. StringBuffer provides thread-safety which makes it the preferred choice when performing a large number of operations on String datatype. The only downside to is that it takes slightly longer to complete.
  • Update POM(Project Object Model): It’s an XML file that contains project and configuration details required for building artifacts.
changelog of replaced URL

Update the version and change the repository URL in Software Configuration Management (denoted by <scm> tag) to your project URL.

Remove the mojo test coverage library from POM. The library would cause build failure because maven configuration requires the directory to be created at src/test/javabut the library creates it at an incorrect path src/test/resources. We can ignore the test coverage for now.

3. Commit and push your updates to the forked repository

git add *
git commit -m local "YOUR COMMIT MESSAGE"
git push origin "BRANCH_NAME"

4. Draft a new release

Steps to draft a new release

In your GitHub project, draft a new release and create a unique tag, this unique tag will identify your new commit. Ones created, click on publish.

5. Hosting to an artifact repository

Both artifactory and jitpack are popular tools to build your artifacts. While artifactory offers you a more sophisticated solution it’s recommended you use an on-demand build tool like jitpack for simple projects.

Visit Jitpack and perform a repository lookup with your GitHub URL. Select “Get it”. jitpack will start building your dependency, once the build is successful you can scroll down and copy the dependency link.

6. include the dependency in your project

In app-level build.gradle add jitpack to your repositories source

// App level build.gradle
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
...
}

Add the dependency to your project-level build.gradle

dependencies {
implementation 'com.github.sudeshim3:socket.io-client-java:1.2.0'
}

Snapshots (aka Nightly builds)

Snapshot helps you to test the incremental changes during the development phase, during the build process the latest commit gets picked up by the artifact repository.

If your modules have a transitive structure, any blocker from a dependency would result in your tasks piling up making the development process chaotic and burdensome. Remember the transitive dependency example I showed containing payment, auth and service integration module. It would be difficult to keep track of all the changes going on in these modules and it is not practical to wait for their individual releases. If you use the development branch of these modules you can ensure that all the latest changes are included in the dependency.

implementation 'com.github.sudeshim3:socket.io-client-java:gh-pages-SNAPSHOT'

In jitpack, you can append -SNAPSHOTto the development branch to get the recent changes.

Conclusion

OK, so, after reading this article I hope you can be less anxious if the included dependency turns out to be a Pandora’s box. Sometimes during the issue resolution process, you might tinker and create a mess out of the library🐣, but as long as you keep your curious appetite high and search for solutions you will figure a way out.

If the article helped you learn something new, throw some thunderous clap. Sure, I can hear it on the other side😁. If you have any doubts or suggestion please comment below.

--

--