The Journey From Legacy Code to Idiomatic Kotlin

F554ae9d4c03a3c14cc2f77fc83105cd?s=47 Seetha
November 25, 2019

The Journey From Legacy Code to Idiomatic Kotlin

We are often faced with large legacy codebases in Java that need to be converted to Kotlin.

Sure, you can let Android Studio do the conversion, but then you end up with a bunch of !!s and inefficient Kotlin code. So where do you begin?

This talk walks you through key steps in converting your Java codebase to efficient, idiomatic Kotlin. After working with multiple clients on legacy projects, I have learned lessons on doing this conversion efficiently, and ensuring that you are taking full advantage of Kotlin. We will walk through the process, along with tips on major patterns that generally require attention. After all, shouldn't every developer's goal be to leave the codebase better than you found it?

F554ae9d4c03a3c14cc2f77fc83105cd?s=128

Seetha

November 25, 2019
Tweet

Transcript

  1. The Journey From Legacy Code to Idiomatic Kotlin Seetha Annamraju

    @seetha_a
  2. Every Android project written in Java is now a legacy

    project.
  3. I

  4. ◂ What is idiomatic Kotlin? ◂ Convert common patterns into

    idiomatic Kotlin ◂ Ideas and suggestions Outline
  5. ◂ You know basic Kotlin syntax. ◂ You believe code

    should be left better than you found it. Assumptions
  6. Legacy

  7. Step 1: Take a step back and look at your

    project.
  8. ◂ How much time do I have? ◂ What are

    the priority items? ◂ Are there components that need fixes? Ask yourself:
  9. Step 2: Start converting.

  10. ◂ Rewrite the file in Kotlin from scratch. Option 1:

    Rewrite
  11. ◂ Rewrite the file in Kotlin from scratch. ◂ Works

    well for large files or files with many dependencies. Option 1: Rewrite
  12. ◂ "Convert Java file to Kotlin file” Option 2: Pull

    off the bandaid
  13. ◂ 1. Compiler errors

  14. ◂ 1. Compiler errors ◂ 2. Warnings and suggestions

  15. ◂ 1. Compiler errors ◂ 2. Warnings and suggestions ◂

    3. Idiomatic Kotlin stuff
  16. Step 3: Ensure Idiomatic code.

  17. ◂ Null Safety ◂ Immutability ◂ Being expressive and concise

    ◂ Easy Interoperability What is idiomatic Kotlin?
  18. Item #1: Null Safety Getting rid of !!s

  19. ◂ ? —“maybe it’s null?” ◂ !! —“this is definitely

    NOT null!!” Null Safety
  20. fun glDrawFrame(viewProjectionMatrix: FloatArray, eyeType: Int) { // ... if (frameAvailable.compareAndSet(true,

    false)) { displayTexture!!.updateTexImage() checkGlError() } displayMesh!!.glDraw(viewProjectionMatrix, eyeType) if (videoUiView != null) { canvasQuad!!.glDraw(viewProjectionMatrix, videoUiView.alpha) } reticle.glDraw(viewProjectionMatrix, controllerOrientationMatrix) } fun glShutdown() { if (displayMesh != null) { displayMesh!!.glShutdown() } // ... } @BinderThread @Synchronized fun setControllerOrientation(currentOrientation: Orientation) { this.controllerOrientation = currentOrientation controllerOrientation!!.toRotationMatrix(controllerOrientationMatrix) } @MainThread fun handleClick() { // ... val clickTarget = CanvasQuad.translateClick(controllerOrientation!!) // ... // The actual processing of the synthetic event needs to happen in the UI thread. uiHandler!!.post { // ... } } Code Sample
  21. fun glDrawFrame(viewProjectionMatrix: FloatArray, eyeType: Int) { // ... if (frameAvailable.compareAndSet(true,

    false)) { displayTexture!!.updateTexImage() checkGlError() } displayMesh!!.glDraw(viewProjectionMatrix, eyeType) if (videoUiView != null) { canvasQuad!!.glDraw(viewProjectionMatrix, videoUiView.alpha) } reticle.glDraw(viewProjectionMatrix, controllerOrientationMatrix) } fun glShutdown() { if (displayMesh != null) { displayMesh!!.glShutdown() } // ... } @BinderThread @Synchronized fun setControllerOrientation(currentOrientation: Orientation) { this.controllerOrientation = currentOrientation controllerOrientation!!.toRotationMatrix(controllerOrientationMatrix) } @MainThread fun handleClick() { // ... val clickTarget = CanvasQuad.translateClick(controllerOrientation!!) // ... // The actual processing of the synthetic event needs to happen in the UI thread. uiHandler!!.post { // ... } } Code Sample !! !! !! !! !! !! !!
  22. Safe-call operator (?) fun destroy() { mediaPlayer?.stop() mediaPlayer?.release() mediaPlayer =

    null isDestroyed = true }
  23. Safe-call operator (?) fun destroy() { mediaPlayer?.stop() mediaPlayer?.release() mediaPlayer =

    null isDestroyed = true }
  24. Use Elvis operator val result = name.toString ?: "Unknown"

  25. Can you use lateinit var? @Inject lateinit var viewModelFactory:ViewModelProvider.Factory

  26. Item #2: Immutability By lazy, local captures

  27. Can you use by lazy? private MediaLoader mediaLoader; public void

    initialize(){ mediaLoader = new MediaLoader(getContext()); ... }
  28. Can you use by lazy? private val mediaLoader: MediaLoader by

    lazy { MediaLoader(context) } private MediaLoader mediaLoader; public void initialize(){ mediaLoader = new MediaLoader(getContext()); ... }
  29. Local captures of values

  30. private var mediaPlayer: MediaPlayer? = null if (mediaPlayer != null

    && sceneRenderer != null) { displaySurface = sceneRenderer?.createDisplay( mediaPlayer!!.videoWidth, mediaPlayer!!.videoHeight, mesh) mediaPlayer!!.apply { setSurface(displaySurface) isLooping = true start() } } Local captures of values
  31. private var mediaPlayer: MediaPlayer? = null if (mediaPlayer != null

    && sceneRenderer != null) { displaySurface = sceneRenderer?.createDisplay( mediaPlayer!!.videoWidth, mediaPlayer!!.videoHeight, mesh) mediaPlayer!!.apply { setSurface(displaySurface) isLooping = true start() } } Local captures of values
  32. Local captures of values val player = mediaPlayer val renderer

    = sceneRenderer if (player != null && renderer != null) { displaySurface = renderer.createDisplay( player.videoWidth, player.videoHeight, mesh ) with(player) { setSurface(displaySurface) isLooping = true start() } }
  33. Local captures of values val player = mediaPlayer val renderer

    = sceneRenderer if (player != null && renderer != null) { displaySurface = renderer.createDisplay( player.videoWidth, player.videoHeight, mesh ) with(player) { setSurface(displaySurface) isLooping = true start() } }
  34. Item #3: Being more concise and expressive Using Kotlin’s Standard

    Library
  35. Over 70% of your time is spent reading code, rather

    than writing code. -Unknown
  36. Java if-statements if (detailResponse != null && detailResponse.getStudent() != null

    && !Helper.isListEmpty(detailResponse.getStudent() .getRegisteredClasses()) && detailResponse.getStudent() .getRegisteredClasses().get(0) != null && !Helper.isListEmpty(detailResponse.getStudent() .getRegisteredClasses() .get(0).getSubjectAssignments()) && detailResponse.getStudent().getRegisteredClasses().get(0) .getSubjectAssignments().get(0) != null){ //do something }
  37. try{ detailResponse.getStudent().getRegisteredClasses() .get(0).getSubjectAssignments().get(0) } catch (NullPointerException e){} Java shorter statement

  38. Kotlin’s Let detailResponse.getStudent?.getRegisteredClasses()? .get(0)?.getSubjectAssignments()?.get(0)?.let { //do something }

  39. if (detailResponse != null && detailResponse.getStudent() != null && !

    Helper.isListEmpty(detailResponse.getStudent() .getRegisteredClasses()) && detailResponse.getStudent() .getRegisteredClasses().get(0) != null && ! Helper.isListEmpty(detailResponse.getStudent() .getRegisteredClasses() .get(0).getSubjectAssignments()) && detailResponse.getStudent().getRegisteredClasses() .get(0) .getSubjectAssignments().get(0) != null){ //do something } Kotlin’s Let
  40. if (detailResponse != null && detailResponse.getStudent() != null && !

    Helper.isListEmpty(detailResponse.getStudent() .getRegisteredClasses()) && detailResponse.getStudent() .getRegisteredClasses().get(0) != null && ! Helper.isListEmpty(detailResponse.getStudent() .getRegisteredClasses() .get(0).getSubjectAssignments()) && detailResponse.getStudent().getRegisteredClasses() .get(0) .getSubjectAssignments().get(0) != null){ //do something } Kotlin’s Let detailResponse.getStudent?.getRegisteredClasses()? .get(0)?.getSubjectAssignments()?.get(0)?.let { //do something }
  41. Apply mediaPlayer = (MediaPlayer()).apply { setDisplay(surfaceHolder) setDataSource(context, uri) prepare() setOnPreparedListener(preparedListener)

    }
  42. Apply mediaPlayer = (MediaPlayer()).apply { setDisplay(surfaceHolder) setDataSource(context, uri) prepare() setOnPreparedListener(preparedListener)

    }
  43. With fun getCourse(syllabus: Syllabus): Course { return with(syllabus) { Course(title,

    professorName, syllabus) } }
  44. With fun getCourse(syllabus: Syllabus): Course { return with(syllabus) { Course(title,

    professorName, syllabus) } }
  45. Transforming Collections

  46. Transforming Collections Students -> StudentViewItems

  47. Transforming Collections val studentsViewItems = studentsList .mapIndexed { index, student

    -> Pair(student.major, StudentViewItem(student.name, student.imgURL)) } .filter { (studentMajor, _) -> studentMajor == COMPUTER_SCIENCE }.map { (_, studentViewItem) -> studentViewItem }
  48. Transforming Collections val studentsViewItems = studentsList .mapIndexed { index, student

    -> Pair(student.major, StudentViewItem(student.name, student.imgURL)) } .filter { (studentMajor, _) -> studentMajor == COMPUTER_SCIENCE }.map { (_, studentViewItem) -> studentViewItem }
  49. Transforming Collections val studentsViewItems = studentsList .mapIndexed { index, student

    -> Pair(student.major, StudentViewItem(student.name, student.imgURL)) } .filter { (studentMajor, _) -> studentMajor == COMPUTER_SCIENCE }.map { (_, studentViewItem) -> studentViewItem }
  50. Transforming Collections val studentsViewItems = studentsList .mapIndexed { index, student

    -> Pair(student.major, StudentViewItem(student.name, student.imgURL)) } .filter { (studentMajor, _) -> studentMajor == COMPUTER_SCIENCE }.map { (_, studentViewItem) -> studentViewItem }
  51. Item #4: Easy interop Models and Utility Classes

  52. Annotate your Java APIs @NonNull @Nullable

  53. None
  54. public class User { private String firstName; private String lastName;

    public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } Convert your Java Objects
  55. public class User { private String firstName; private String lastName;

    public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } Convert your Java Objects data class User(var firstName: String?, var lastName: String?)
  56. Extensions for utility classes public class ExtraStringUtils { public ExtraStringUtils(){}

    public static String toOppositeCase(String str) { // ... } }
  57. StringExt.kt @JvmMultifileClass @file:JvmName("ExtraStringUtils") package com.example.androidtest // ... fun String.toOppositeCase(): String

    { // ... }
  58. FAQ Common Questions

  59. How do I do a large refactoring into Kotlin?

  60. ◂ Commit #1: Convert file to Kotlin Refactoring to Kotlin

  61. ◂ Commit #1: Convert file to Kotlin ◂ Commit #2:

    Fix compiler errors Refactoring to Kotlin
  62. ◂ Commit #1: Convert file to Kotlin ◂ Commit #2:

    Fix compiler errors ◂ Commit #3: Fix warnings Refactoring to Kotlin
  63. ◂ Commit #1: Convert file to Kotlin ◂ Commit #2:

    Fix compiler errors ◂ Commit #3: Fix warnings ◂ Commit #4: Idiomatic changes Refactoring to Kotlin
  64. How do I ensure idiomatic code?

  65. ◂ Code reviews ◂ Learn from each other ◂ https://kotlinlang.org/docs/reference/

    idioms.html Ensuring Idiomatic Kotlin
  66. What’s next?

  67. ◂ Lambdas ◂ Sealed classes ◂ Standard library functions What’s

    next?
  68. ◂ Step back and take a look at the project.

    ◂ Look for areas to apply null safety, immutability, readability, better interop ◂ Keep your team on the same page. Recap
  69. What challenges do you face? @seetha_a / seetha.io