Frankencode: Bringing an old Android library back to life with Kotlin

Look! It's moving. It's alive. It's alive... It's alive, it's moving,  it's alive, it's alive, it's alive, it's alive, IT'S ALIVE!

A few years ago, I created an Android library, written in Java, to display Guitar Chord Diagrams natively. Recently, I revisited the library and converted it to Kotlin, fixing some lingering issues and updating the API in the process. The end result is chords, an easily customizable and simple to use native Android View for displaying fretted stringed instrument chord diagrams. In this article, I provide some insights into the Java to Kotlin conversion process for the library.

TL;DR

Kotlin provided conciseness and flexibility to an old Java Android library. Checkout the code here.

The process

Updating the library ended up requiring a significant overhaul in the code structure and API. There were some lingering issues in the old code and separation of concerns wasn't followed. On top of that, there was too much of an attempt to be "smart" and handle incorrect input values by coercing them to work. All of these things contributed to making the code hard to read and follow. These issues had to be fixed in the new implementation.

The first thing that needed to be done was to setup the build files correctly. It seems when I first created this library, I didn't upload the all the Gradle files. Adding these was simple enough. I also knew that I would create a Kotlin multi-platform module that would handle the common code so that this library could be implemented for different environments, other than Android.

Next, was to convert all the Java classes to Kotlin. IntelliJ has a helpful tool for this: In the top menu of IntelliJ, choose code > Convert Java File to Kotlin File. The code will now be in Kotlin, but sometimes you still have to go through and make some corrections and better structure the code for readability.

Then, removing the unnecessary code. The original library attempted to handle work that seemingly ventured out of the scope of the project. Also, there was redundancy in the API. For instance, the GuitarChordView had constants that just delegated to other constants for convenience. So, any redundant or out of scope code was removed.

After that came updating the API. This is where Kotlin played an important role. For instance, the model for this library is fairly complex. There's a model for the chart, a view model for how the chart is displayed, and a model for the chord. The chord model can contain any number of unique markers. These markers, however, were each different and didn't share much in common with the other markers. But there was a finite set of marker options. This was a perfect use case for Kotlin's sealed classes.

Finally, the view implementation had to be updated. The original library had some lingering issues that had to be addressed. And there was performance problems that had to be optimized as well. For instance, the original library performed a lot of the layout calculations from within the onDraw function. These calculations were moved into the onMeasure function and only drawing was performed in the onDraw function.

The result

The new API for the library is simple to use and easy to understand. It still maintains the customizability of the original and even adds some extra options. Overall, I'm satisfied with the end result. The following are the highlights of updating the library to use Kotlin:

  • The library is more concise. For example, after converting the GuitarChordView from Java to Kotlin and removing some of the unnecessary code, the line count dropped from 1,812 to 422. Some lines were added to this class, now called ChordWidget, but it still only has 547 lines, far fewer than the original.
Converted GuitarChordView to Kotlin and Refactored the class · chRyNaN/chords@d33526b
An Android view library for displaying stringed instrument chord diagrams - chRyNaN/chords
  • The library is more flexible. There's a core module which is a Kotlin multi-platform module that contains all the models, views, and parsing code. This means that other environments, such as, client-side Javascript or iOS, could be supported in the future.
  • Use of Kotlin's sealed classes. As stated before, the Chord class contains markers that represent the different components of a chord to be displayed. These markers are defined by the ChordMarker sealed class. This allows for a definite set of types which can be handled conditionally in an exhaustive manner. Which in turn, provides a better API to the user of the library, exposing only what is needed.
  • Use of Kotlin's inline classes. Some of the ChordMarker classes contain multiple properties of the same type. To prevent any accidental errors from messing up the constructor parameter order, inline classes are used.
// Instead of this
data class Note(
            override val fret: Int,
            override val string: Int,
            ...
)

// Inline classes are used to prevent mistakes
data class Note(
            override val fret: FretNumber,
            override val string: StringNumber,
            ...
)

  • Use of Kotlin's suspend functions. The ChordParser interface takes in an input and outputs a ChordParseResult containing a Chord. Different implementations of this interface can be created to handle different formats. Most of the time these parsing actions will take some time to perform and should be handled off the UI Thread. Leveraging Kotlin's support for Coroutines, the parse function is a suspending function. This will assert that it is called appropriately, from within another suspending function or within a Coroutine. There's no dependency on the Kotlin Coroutine library, since the suspend modifier is built-in to the language.
  • Use of Kotlin extension functions. Extension functions play an important role in providing convenience utilities on existing Android component objects, and adding properties and functions to models without polluting the model class itself. Examples of these can be seen in the ChordUtils in the core module and the ParcelableUtils in the library module.

Using the library

The README file contains a more detailed description on using the library. The basic idea is to define the ChordWidget in your XML layout. And then assign a Chord to the ChordWidget.

Defining ChordWidget in an XML layout:

<!-- Specify an exact size (MATCH_PARENT, MATCH_CONSTRAINTS, DP value). -->
<com.chrynan.chords.widget.ChordWidget
    android:id="@+id/chordWidget"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Assigning a Chord to the ChordWidget:

chordWidget?.chord = chord("G") {
            +ChordMarker.Note(
                    fret = FretNumber(3),
                    finger = Finger.MIDDLE,
                    string = StringNumber(6)
            )
            +ChordMarker.Note(
                    fret = FretNumber(2),
                    finger = Finger.INDEX,
                    string = StringNumber(5)
            )
            +ChordMarker.Open(string = StringNumber(4))
            +ChordMarker.Open(string = StringNumber(3))
            +ChordMarker.Note(
                    fret = FretNumber(3),
                    finger = Finger.RING,
                    string = StringNumber(2)
            )
            +ChordMarker.Note(
                    fret = FretNumber(3),
                    finger = Finger.PINKY,
                    string = StringNumber(1)
            )
        }

Sample using the library

There is a sample module in the library which contains a simple Android application showcasing the use of the library. The following is a screenshot of the sample app:

Sample app using the chords library

Conclusion

With the help of the Kotlin programming language, I was able to instill life into an old Java Android library. It was an interesting experience and was enlightening to see how much I have progressed in my development capability in just a few short years. Overall, I am satisfied with the result. If I find more time to work on the project, it may be worth while to see if I can get an implementation working for iOS, making the library truly multi-platform. Another option would be to create an implementation using Jetpack Compose, since that may be the future of UI development on Android.