Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Journey From Legacy Code to Idiomatic Kotlin

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?

Seetha

November 25, 2019
Tweet

More Decks by Seetha

Other Decks in Technology

Transcript

  1. I

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

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

    should be left better than you found it. Assumptions
  4. ◂ How much time do I have? ◂ What are

    the priority items? ◂ Are there components that need fixes? Ask yourself:
  5. ◂ Rewrite the file in Kotlin from scratch. ◂ Works

    well for large files or files with many dependencies. Option 1: Rewrite
  6. ◂ Null Safety ◂ Immutability ◂ Being expressive and concise

    ◂ Easy Interoperability What is idiomatic Kotlin?
  7. 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
  8. 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 !! !! !! !! !! !! !!
  9. Can you use by lazy? private MediaLoader mediaLoader; public void

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

    lazy { MediaLoader(context) } private MediaLoader mediaLoader; public void initialize(){ mediaLoader = new MediaLoader(getContext()); ... }
  11. 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
  12. 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
  13. 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() } }
  14. 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() } }
  15. 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 }
  16. 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
  17. 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 }
  18. Transforming Collections val studentsViewItems = studentsList .mapIndexed { index, student

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

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

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

    -> Pair(student.major, StudentViewItem(student.name, student.imgURL)) } .filter { (studentMajor, _) -> studentMajor == COMPUTER_SCIENCE }.map { (_, studentViewItem) -> studentViewItem }
  22. 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
  23. 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?)
  24. Extensions for utility classes public class ExtraStringUtils { public ExtraStringUtils(){}

    public static String toOppositeCase(String str) { // ... } }
  25. ◂ Commit #1: Convert file to Kotlin ◂ Commit #2:

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

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

    Fix compiler errors ◂ Commit #3: Fix warnings ◂ Commit #4: Idiomatic changes Refactoring to Kotlin
  28. ◂ 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