How to miserably fail migration from Java to Kotlin in Android application
Since Google announced official support for Kotlin on Android, more and more developers want to use it in their new and existing projects. Since I am also a big fan of Kotlin, I could not wait to be able to use Kotlin in my work project. In the end, Kotlin is fully compatible with Java, and all developers absolutely love it. So what can go wrong?
Well, actually a lot can go wrong. It scares me that on the Android Getting Started with Kotlin official documentation page , if you want to transfer an existing application to Kotlin, you should just start writing unit tests, and then, after some experience with this language, you should writing new code in Kotlin, and simply converting existing Java code.
In this article I will tell you what would happen if I decided to immediately follow this strategy on the sale, and not try it on my small project.
Get to know my little project.
I had a project that I created over a year ago in Java, and I needed to change it a bit and add one new screen. I thought it was a great opportunity to check whether the migration of an existing project to Kotlin is really very simple, as everyone says. Based on the recommendations of the official documentation, I began by writing unit tests on Kotlin. I also wrote new classes on Kotlin and converted some existing ones. Everything seemed fine, but after a while I found one unpleasant problem.
Kotlin + Lombok = :(
In my application, I used the Lombok library to generate getters and setters. Lombok is an annotation handler for javac. Unfortunately, the kotlin compiler uses javac without annotation processing [source] . This means that methods created using Lombok will not be available in Kotlin:
But you can say: “Well, it will not be very cool, but in principle you can manually create getters and setters for those fields that will be needed in the Kotlin code. In the end, you do not need to do this in the whole project at once. ”
I thought the same. But I ran into a real problem when I wanted to add a new screen to the application.
Kotlin + Lombok + Dagger 2 =: (((
In my application, Dagger 2 is used for dependency injection. When creating a new screen, I usually create an MVP structure: Activity, Presenter, Component, Module and Contract. All dependencies for the presenter are implemented using Dagger. Activity calls
DaggerSomeComponent.builder().(...).build().inject(this)to inject a presenter with the dependencies necessary for it.
Using Dagger 2 with Kotlin is no problem. Just before that, you need to apply the kapt-plugin , which creates the necessary self-generated classes for Dagger.
And here everything starts to fall apart.
Without the kapt plugin, I could not use the generated Dagger classes in the Kotlin files. But after I added this plugin, all the methods created by Lombok disappeared!
Before using the kapt plugin :
After applying the kapt plugin :
And, unfortunately, there is no solution to this problem. You can use either only the kapt-plugin, or only Lombok . Fortunately, since it was just my little project, I simply deleted Lombok and wrote the getters and setters myself. But in this project there were only about 50 generated methods. In the project that we support at work, we have about a thousand of them . Uninstalling Lombok from this application is simply impossible.
Also, it is obvious that the rejection of the kapt-plugin is not the way out. Without it, you will not be able to use Kotlin in classes that use Dagger. In my case, I would have to implement Activity, Component and Module in Java, and only Contract and Presenter could be written in Kotlin. Mixing Java and Kotlin files is definitely not great. Instead of a smooth transition from Java to Kotlin, you would simply create a big mess.
This would be what this terrible polyglot MVP structure would look like without a kapt plugin:
But I still want to go to Kotlin. What should I do?
One way is to use different modules . Kotlin will not see the methods that Lombok will generate in the source code, but will see them in bytecode.
Personally, it seems to me that this is the most preferable way. If you bring Kotlin into separate dependent modules for each feature, you reduce the risk of such compatibility problems I encountered, or more complex ones listed in the official guide .
And that is not all. There are many other shortcomings in mixing Kotlin and Java files without a clear separation. This makes all developers working with you on the project know both languages. It also reduces the readability of the code and may lead to an increase in build time [source] .
- methods generated by Lombok are not visible in Kotlin;
- using a kapt plugin breaks Lombok;
- without a kapt plugin, you cannot use self-generated classes for Dagger in Kotlin, which means that you still have to write new Java code;
- the way to solve this problem is to bring Kotlin into separate modules;
- Mixing Kotlin and Java files in large projects without a clear separation can lead to unexpected compatibility issues.