Recently Quipper Android migrated from java to Kotlin, let me share some of the issues that we got during migration process:
Kotlin gradle configuration/dependencies
For the first step of the migration we followed the Kotlin Gradle guideline
Existing library problem
The first problem we encountered was a conflict with the Lombok library during compilation, it always shows error for any property annotated with @Getter
. For that reason we need to manually remove the annotation and generate the required getter and setters which consequentially affected a lot of code. The process took almost 2-3 days, and during migration we still maintain the unused getter and setters.
Existing java source code
Kotlin visibility access is different with java and some implementation classses from existing code are designed to be injected, so we have to violate/detour from the existing architecture.
With regards to Java Nullability: the existing code is using a lot of Callback
mechanism and since Java param/property is nullable by default, any property/function converted to Kotlin is nullable unless it proven otherwise.
Sample Code
Consider this interface that is required to be implemented by a parent Activity of a Fragment:
interface Callback {
public MyObject getMyObject();
}
The Activity and Fragment are coupled by the Callback, but the fragment must always check for nullables.
class MyActivity extends Activity implements Callback {
private MyObject myObject;
@Override
protected void onCreate() {
if ((this instanceof Callback) == false) {
throw new Exception("Implement Callback!");
}
// Misc ops
myObject = new MyObject(miscArgs);
}
@Override
public MyObject getMyObject() {
return myObject;
}
}
class MyFragment extends Fragment {
private Callback callback;
@Override
protected void onCreateView(...) {
if ((getActvity() instanceof Callback) == false) {
throw new Exception("Implement Callback!");
}
callback = (Callback) getActvity();
}
@Override
protected void onResume() {
// Some task
MyObject myObject = callback.getMyObject();
if (myObject != null)
myObject.someOperation();
}
}
Converted to Kotlin, it carry overs Java’s nullable parameters. A Kotlin IDE will then warn of potential NPEs and also suggest some refactors.
class MyActivity : Activity(), Callback {
private var myObject: MyObject
override fun onCreate() {
if (this !is Callback) {
throw Exception("Implement Callback!")
}
// Misc ops
myObject = MyObject(miscArgs)
}
override fun getMyObject(): MyObject {
return myObject
}
}
class MyFragment : Fragment() {
private var callback: Callback
override fun onCreateView(...) {
if (getActvity() !is Callback) {
throw Exception("Implement Callback!")
}
callback = getActvity() as Callback
}
override fun onResume() {
// Some task
val myObject = callback.getMyObject()
if (myObject != null) {
myObject.someOperation()
}
}
}
What Kotlin would look like after more modifications for null safety, notice the ?
operators on objects.
class MyActivity : Activity(), Callback {
private lateinit var myObject: MyObject
override fun onCreate() {
if (this !is Callback) throw Exception("Implement Callback!")
// Misc ops
myObject = MyObject(miscArgs)
}
override fun getMyObject(): MyObject {
return myObject
}
}
class MyFragment : Fragment() {
private lateinit var callback: Callback
override fun onCreateView(...) {
if (getActvity() !is Callback) throw Exception("Implement Callback!")
callback = getActvity() as Callback
}
override fun onResume() {
// Some task
callback?.let { cb ->
val myObject = cb.getMyObject()
myObject?.someOperation()
}
}
}
So until proven otherwise, you must always check for nulls and use safety operators in Kotlin, else refactor the code to explicitly state late initializations and Nullable parameters.