Why Ibotta Android Is Pivoting to Kotlin
Frequently in software engineering a new library/framework is introduced generating lot of buzz and excitement. These new technologies have benefits over their predecessors such as ease of use, cleanliness, robustness, etc. but normally aren’t worth an application refactor since they provide nothing new. For example, Room is a fantastic library that was released as an Android Architecture Component to help make writing and using SQL-backed databases more friendly. Although this is a great iteration on SQL libraries, if you’ve already implemented the rest of your databases using another technology, you won’t want to take the time to refactor for no real gain.
Ibotta’s engineering culture is highly pragmatic. On the Android team, we try to live by that every day. In the case of Room, we have used it for new features that require persistence but have not gone back and refactored older databases. Our main goal is to move fast and get features done quickly with the idea that not every pull request is going to be perfect. Engineering in general is a game of concessions and if we required perfection & constant refactoring we would never deliver features to our users. Of course, this doesn’t mean that we have the leniency to write unmaintainable code for the sake of speed. It simply means that making pragmatic decisions around patterns/frameworks have allowed us to move quickly without sacrificing code quality & maintainability.
Why is Kotlin different? 🤔
Our current codebase is 100% Java and it’s getting the job done nicely! The “legacy” code works great, and our crash rate normally sits at about 0.3%. We all love Java and enjoy working with it in our codebase, but we are always behind the curve of new releases. Java is tightly coupled with the Android SDK which means that even though Java 11 has been released we are still working with partial Java 8 support. We don’t even have access to some of the slickest features of Java 8 like java.util.stream
or java.util.function
since they require API 24+ (our min API is 21). We have added the Android Retrostreams library to get this functionality by way of desugaring, but this still doesn’t give us support for the newest language features.
Comparing this process to how Kotlin integrates with Android makes the Java way seem downright clumsy! Kotlin can be integrated into your application in a modular way where you have total control of the version being used. This means you can use the latest version with the newest language features without being concerned about Android supporting it.
Besides the Android integration, there are plenty of language features that make the Kotlin refactor palatable. I’m going to focus on the 3 most important from Ibotta Android’s point of view:
- Nullability
- Immutability
- Android Extensions
The idea is that all of these features from Kotlin help to replace some serious hinderances that exist in Java.
Nullability
This is the most commonly cited reason that Java developers love Kotlin. Nullability is built into the type system so that you explicitly know whether a variable can be null at point-of-use. In an ideal scenario, there would be no nullable variables in your codebase at all! As mentioned before, Ibotta engineering is a pragmatic team and that ideal will surely not be a reality. Still, the ability to be more expressive with your types is a huge win.
Our team decided to incorporate NullAway into our Java codebase to better communicate our nullability and protect ourselves from NPEs. This is a great library but it has a few drawbacks:
- Utilizes static code analysis which increases build time slightly (By less than 10% according to their docs)
- Unable to retroactively apply to your entire codebase if you’re working with legacy code
The second item has been the hardest to deal with. The fact that the majority of our codebase isn’t covered really limits the usefulness that we gain from the library. To get all of our packages covered, we would have to do a huge amount of refactoring. By switching to Kotlin, we end up doing a refactor anyway and can replace NullAway entirely with the language itself.
Immutability
This has been a big theme on the Ibotta Android team recently. Dealing with immutable variables can reduce confusion and make bugs easier to track down. We have been going down the path of a unidirectional architecture which leans a lot on the concept of immutability. The biggest challenge when it comes to immutability is with Collections
. For example, there is no easy way to discern when you’re dealing with an immutable List
or not. To do this, you need to track down where the List
is created and see if it’s by Collections.unmodifiableList
, List.of
, or any other source of immutable List
. If you happen to try and modify an immutableList
, you’ll get an UnsupportedOperationException
which is aRuntimeException
, meaning you won’t get an error from the compiler to catch it.
Kotlin fixes all of this by making mutability part of the type system (similar to nullability). Sticking with the List
example, instead of not knowing whether you’re dealing with a mutable List
or not, all Lists
in Kotlin are immutable. If you want a List
to be mutable, then you use the corresponding MutableList
type. This goes for all types of collections as well, so you can work with MutableSets
, MutableMaps
, etc.
If you know what variety of Collection
you’re dealing with at the point-of-use, this can dramatically increase code readability and developer effort. As opposed to locating the variable instantiation, which may be several method calls away, Kotlin allows you to focus on the task at hand without taking a detour.
Android Extensions
The other items discussed in this article deal with differences between the Kotlin & Java type systems. On the other hand, Android Extensions are specific to Android development and will dramatically affect UI-related work. As many other companies do, Ibotta relies on Butter Knife to handle view binding for us. Butter Knife is a great library that removes some of the boilerplate around binding a view to a variable in code. However in doing that, it creates its own boilerplate code (although less intrusive). This is where Android Extensions come in!
The main feature is synthetic view binding which completely eliminates boilerplate code. This is a big win not only for cleaner code, but also for removing a lot of Butter Knife annotations from your codebase. Using less annotations is vital if you’re interested in speeding up your build with incremental compilation.
There is also a built-in cache for your views which attempts to minimize the amount of calls to findViewById
. This method is generally slow and repeated calls can cause a strain on your system. Having this behind-the-scenes feature can help make your view layer snappier and more efficient without you adding any extra code in your codebase.
Lastly, there is a built-in @Parcelize
annotation that is able to generate Parcelable
implementations of your classes with no work on your behalf. We currently implement Parcelable
in two ways; the old fashioned way of implementing writeToParcel
and using a library to help us with generating these classes for us. The fact that this feature is built-in means that we’ll eventually be able to drop that library dependency and remove old boilerplate.
Conclusion
These are only a few of the reasons that Kotlin is so exciting for our team. The main themes to take away are:
- Modular integration with Android granting total control over language version & features.
- Type system enhancements including nullability and immutability which makes for a less buggy, easier-to-understand and more expressive codebase.
- Removing dependencies and boilerplate code in favor of built-in language features.
In coming blog posts, we’ll be outlining exactly how we’re going about our Kotlin conversion along with the lessons we’ve learned along the way. We’ve found great enjoyment throughout our conversion process thus far and the excitement on the team is palpable. If you’re interested in joining Ibotta on this journey please reach out as we’re hiring!