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

Pragmatic Kotlin on Android

josh skeen
August 18, 2017

Pragmatic Kotlin on Android

presented at Android Summit: http://androidsummit.org/

In this talk Josh will share how Kotlin isn’t just syntactic sugar, it’s a better way to build Android apps. We will start with a modern java-based Android project and walk through a complete migration to Kotlin.In the process, you will see objective examples of how the Android concepts you're already familiar with are expressed in Kotlin more accurately, with less code, and with far fewer chances for bugs!

code example: http://github.com/mutexkid/stockwatcher

newIntent/newFragment pattern as extension:
https://github.com/mutexkid/kotlin-newintent-experiment/

josh skeen

August 18, 2017
Tweet

More Decks by josh skeen

Other Decks in Programming

Transcript

  1. The Story So Far… • Started with a Legacy Java/Android

    Application (side-project) Code and slides: https://goo.gl/gmFMqS Code and slides: https://goo.gl/gmFMqS
  2. The Story So Far… • Started with a Legacy Java/Android

    Application (side-project) • Run Automatic Conversion to Kotlin Code and slides: https://goo.gl/gmFMqS
  3. The Story So Far… • Started with a Legacy Java/Android

    Application (side-project) • Run Automatic Conversion to Kotlin • Learn from What Changed Code and slides: https://goo.gl/gmFMqS
  4. The Story So Far… • Started with a Legacy Java/Android

    Application (side-project) • Run Automatic Conversion to Kotlin • Learn from What Changed • Improve upon Design w/ Kotlin Features Code and slides: https://goo.gl/gmFMqS
  5. Running the Automatic Conversion RxFragment.java RxFragment.kt abstract class RxFragment :

    Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } public abstract class RxFragment extends Fragment { private static final String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"; private boolean requestInProgress; public boolean isRequestInProgress() { return requestInProgress; } public void setRequestInProgress(boolean requestInProgress) { this.requestInProgress = requestInProgress; } private CompositeDisposable compositeDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); compositeDisposable = new CompositeDisposable(); if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress); } @Override public void onResume() { super.onResume(); if (isRequestInProgress()) { loadRxData(); } } @Override public void onPause() { super.onPause(); compositeDisposable.clear(); } public abstract void loadRxData(); }
  6. public abstract class RxFragment extends Fragment { private static final

    String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"; private boolean requestInProgress; public boolean isRequestInProgress() { return requestInProgress; } public void setRequestInProgress(boolean requestInProgress) { this.requestInProgress = requestInProgress; } private CompositeDisposable compositeDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); compositeDisposable = new CompositeDisposable(); if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress); } @Override public void onResume() { super.onResume(); if (isRequestInProgress()) { loadRxData(); } } @Override public void onPause() { super.onPause(); compositeDisposable.clear(); } public abstract void loadRxData(); } Before and After.. abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } public abstract class RxFragment extends Fragment { private static final String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"; private boolean requestInProgress; public boolean isRequestInProgress() { return requestInProgress; } public void setRequestInProgress(boolean requestInProgress) { this.requestInProgress = requestInProgress; } private CompositeDisposable compositeDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); compositeDisposable = new CompositeDisposable(); if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress); } @Override public void onResume() { super.onResume(); if (isRequestInProgress()) { loadRxData(); } } @Override public void onPause() { abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } 1. Properties Aren’t Fields
  7. No Getters & Setters‽ public abstract class RxFragment extends Fragment

    { private static final String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROG private boolean requestInProgress; public boolean isRequestInProgress() { return requestInProgress; } public void setRequestInProgress(boolean requestInProgress) { this.requestInProgress = requestInProgress; } private CompositeDisposable compositeDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); compositeDisposable = new CompositeDisposable(); if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRE } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress); } abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = Comp override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getB 1. Properties Aren’t Fields
  8. Direct Access‽ What About Encapsulation‽ 1. Properties Aren’t Fields private

    fun <T> Observable<T>.applyRequestStatus(rxFragment: RxFragment): Observable<T> = doOnSubscribe { rxFragment.requestInProgress = true } .doOnTerminate { rxFragment.requestInProgress = false } RxUtil.kt:
  9. Looking under the Hood… private boolean requestInProgress; public final boolean

    getRequestInProgress() { return this.requestInProgress; } public final void setRequestInProgress(boolean var1) { this.requestInProgress = var1; } var requestInProgress = false generates this… 1. Properties Aren’t Fields
  10. var/val syntax: var var requestInProgress = false private boolean requestInProgress;

    public final boolean getRequestInProgress() { return this.requestInProgress; } public final void setRequestInProgress(boolean var1) { this.requestInProgress = var1; } private boolean requestInProgress; public final boolean getRequestInProgress() { return this.requestInProgress; } public final void setRequestInProgress(boolean var1) { this.requestInProgress = var1; } var 2. Var and Val mean “writable” and “read-only”
  11. var/val syntax: val val requestInProgress = false private final boolean

    requestInProgress; public final boolean getRequestInProgress() { return this.requestInProgress; } public final void setRequestInProgress(boolean var1) { this.requestInProgress = var1; } Read-only, not immutable! val 2. Var and Val mean “writable” and “read-only”
  12. Proof: val != immutable! private val notImmutable: Int get() {

    return (Math.random() * 10).toInt() } 2. Var and Val mean “writable” and “read-only”
  13. Proof: val != immutable! private val notImmutable: Int get() {

    return (Math.random() * 10).toInt() } public final int getNotImmutable() { return (int)(Math.random() * (double)10); } 2. Var and Val mean “writable” and “read-only”
  14. Related: No Primitives? 2. Var and Val mean “writable” and

    “read-only” private boolean requestInProgress;
  15. public abstract class RxFragment extends Fragment { private static final

    String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"; private boolean requestInProgress; public boolean isRequestInProgress() { return requestInProgress; } public void setRequestInProgress(boolean requestInProgress) { this.requestInProgress = requestInProgress; } private CompositeDisposable compositeDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); compositeDisposable = new CompositeDisposable(); if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress); } @Override public void onResume() { super.onResume(); if (isRequestInProgress()) { loadRxData(); } } @Override public void onPause() { super.onPause(); compositeDisposable.clear(); } public abstract void loadRxData(); } Before and After.. abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } public abstract class RxFragment extends Fragment { private static final String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS"; private boolean requestInProgress; public boolean isRequestInProgress() { return requestInProgress; } public void setRequestInProgress(boolean requestInProgress) { this.requestInProgress = requestInProgress; } private CompositeDisposable compositeDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); compositeDisposable = new CompositeDisposable(); if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress); } @Override public void onResume() { super.onResume(); if (isRequestInProgress()) { loadRxData(); } } @Override public void onPause() { super.onPause(); compositeDisposable.clear(); } public abstract void loadRxData(); } abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } 3. No Static Keyword
  16. What Changed: Where’d static go?! private static final String EXTRA_RX_REQUEST_IN_PROGRESS

    = "EXTRA_RX_REQUEST_IN_PROGRESS"; companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } 3. No Static Keyword
  17. Object’s not java.lang.Object! private static final String EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS";

    companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } 3. No Static Keyword
  18. What Changed: Where’s static‽ private static final String EXTRA_RX_REQUEST_IN_PROGRESS =

    "EXTRA_RX_REQUEST_IN_PROGRESS"; companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } 3. No Static Keyword
  19. What Changed: Where’s static‽ private static final String EXTRA_RX_REQUEST_IN_PROGRESS =

    "EXTRA_RX_REQUEST_IN_PROGRESS"; companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } 3. No Static Keyword
  20. companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } override

    fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) compositeDisposable = CompositeDisposable() if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } What Changed: Where’s static‽ 3. No Static Keyword
  21. Looking under the Hood… companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS

    = "EXTRA_RX_REQUEST_IN_PROGRESS" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) compositeDisposable = CompositeDisposable() if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } 3. No Static Keyword
  22. Looking under the Hood… private static final String EXTRA_RX_REQUEST_IN_PROGRESS =

    "EXTRA_RX_REQUEST_IN_PROGRESS"; public static final class Companion { private final String getEXTRA_RX_REQUEST_IN_PROGRESS() { return RxFragment.EXTRA_RX_REQUEST_IN_PROGRESS; } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } 3. No Static Keyword
  23. Related: Java Interop class Foo { companion object { val

    QAZ = "STATIC CLING" } } public static void main(String[] args) { Foo.Companion.getQAZ(); } 3. No Static Keyword
  24. Related: Java Interop class Foo { @JvmField companion object {

    val QAZ = "STATIC CLING" } } public static void main(String[] args) { println(Foo.Companion.getQAZ()); println(Foo.QAZ); } 3. No Static Keyword (don’t cling to static)
  25. Automatic Migration Mistakes private const val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" abstract

    class RxFragment : Fragment() { … companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } 4. Automatic Migration Makes Mistakes! - companion object often unneeded
  26. Automatic Migration Mistakes private const val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" abstract

    class RxFragment : Fragment() { … File level definition - totally normal in Kotlin! 4. Automatic Migration Makes Mistakes! - companion object often unneeded
  27. Clean Up: Use File Level Constants private const val EXTRA_RX_REQUEST_IN_PROGRESS

    = "EXTRA_RX_REQUEST_IN_PROGRESS" abstract class RxFragment : Fragment() { … 4. Automatic Migration Makes Mistakes! - Use File Level Constant if possible
  28. Cleaning up Automatic Migration: Nullability override fun onSaveInstanceState(outState: Bundle?) {

    super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } 4. Automatic Migration Makes Mistakes! - nullability is often mistranslated
  29. Cleaning up Auto Migration: Nullability public void onSaveInstanceState(Bundle outState) {

    } 4. Automatic Migration Makes Mistakes! “Nullability?” - nullability is often mistranslated Fragment.java
  30. Cleaning up Auto Migration: Nullability public void onSaveInstanceState(Bundle outState) {

    } Don’t see a @NonNullable annotation either… Assume it’s nullable. 4. Automatic Migration Makes Mistakes! - nullability is often mistranslated Fragment.java
  31. Cleaning up Auto Migration: Nullability override fun onSaveInstanceState(outState: Bundle?) {

    super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } 4. Automatic Migration Makes Mistakes! - nullability is often mistranslated
  32. 1. Properties Aren’t Fields 1. Manages field, a getter and

    a setter as needed 2. No direct access to creating Fields in Kotlin. 3. Even more powerful when used with Extensions! 4. No primitives, but translated to primitives in byte code 5. Override getter/setter operators for the property, don’t adds functions Takeaways
  33. Takeaways 1. Properties Aren’t Fields 2. Var and Val mean

    “readable/writable” and “read-only” 1. val != immutable! 2. For vals and vars, can’t specify primitives for type (but they are correctly “unboxed” in bytecode)
  34. 1. Properties Aren’t Fields 2. Var and Val mean “writable”

    and “read-only” 3. No Static Keyword 1. ‘object’ (singleton instance) or file level values & functions now 2. Favor file-level values/functions unless ne 3. Properties and functions at the file level are 100% normal in Kotlin 4. Java Interop annotations support generating static fields that match Java style Takeaways
  35. 1. Properties Aren’t Fields 2. Var and Val mean “writable”

    and “read-only” 3. No Static Keyword 4. Automatic Migration Makes Mistakes! 1. Often gets nullability wrong when generating Kotlin from Java, since anything goes in Java. Relies on @Null/@NonNull annotations to generate 2. Companion objects are often not needed 3. not quite idiomatic Kotlin…but, a good start! Takeaways
  36. abstract class RxFragment : Fragment() { var requestInProgress = false

    val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" } } Code Smells: != null Checks 1. Let/Apply to Drop Null Checks & Config Objects RxFragment.kt
  37. First thought: Make sure it actually needs to be nullable!

    abstract class RxFragment : Fragment() { var requestInProgress = false val compositeDisposable: CompositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } override fun onResume() { super.onResume() if (requestInProgress) { loadRxData() } } override fun onPause() { super.onPause() compositeDisposable.clear() } abstract fun loadRxData() companion object { private val EXTRA_RX_REQUEST_IN_PROGRESS = "EXTRA_RX_REQUEST_IN_PROGRESS" 1. Let/Apply to Drop Null Checks & Config Objects RxFragment.kt
  38. Fix: Use Let with Safe Call Operator override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) savedInstanceState?.let { requestInProgress = it.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } 1. Let/Apply to Drop Null Checks & Config Objects RxFragment.kt
  39. Code Smells: == null abstract class SingleFragmentActivity : AppCompatActivity() {

    protected abstract fun createFragment(): Fragment public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .add(R.id.fragment_container, createFragment()) .commit() } } } 1. Let/Apply to Drop Null Checks & Config Objects SingleFragmentActivity.kt
  40. Fix: Use Let with Null Coalesce Operator if (savedInstanceState ==

    null) { supportFragmentManager.beginTransaction() .add(R.id.fragment_container, createFragment()) .commit() } savedInstanceState ?: let { supportFragmentManager.beginTransaction() .add(R.id.fragment_container, createFragment()) .commit() } 1. Let/Apply to Drop Null Checks & Config Objects SingleFragmentActivity.kt
  41. Smell: Init a Class and Configure lots of Methods on

    it ProgressDialog.kt (legacy) 1. Let/Apply to Drop Null Checks & Config Objects override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val progressDialog = ProgressDialog(context) progressDialog.setMessage(arguments.getString(ARG_MESSAGE)) progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER) return progressDialog }
  42. Fix: Use an Apply Block ProgressDialog.kt (legacy) 1. Let/Apply to

    Drop Null Checks & Config Objects override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val progressDialog = ProgressDialog(context) progressDialog.setMessage(arguments.getString(ARG_MESSAGE)) progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER) return progressDialog } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return ProgressDialog(context).apply { setMessage(arguments.getString(ARG_MESSAGE)) setProgressStyle(ProgressDialog.STYLE_SPINNER) } }
  43. Cleaning up: Drop Unneeded Object object DialogUtils { private const

    val TAG_DIALOG_PROGRESS = "dialog_progress" internal fun showProgressDialog(fragmentManager: FragmentManager, message: String) { if (fragmentManager.findFragmentByTag(DialogUtils.TAG_DIALOG_PROGRESS) != null) { hideProgressDialog(fragmentManager) } val dialog = ProgressDialogFragment.newInstance(message) dialog.isCancelable = false dialog.show(fragmentManager, TAG_DIALOG_PROGRESS) } internal fun hideProgressDialog(fragmentManager: FragmentManager) { val fragment = fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS) (fragment as? ProgressDialogFragment)?.dismissAllowingStateLoss() } } 1. Let/Apply to Drop Null Checks & Config Objects
  44. Extensions: Quick Example fun String.addEnthusiasm() = this + "!!!!" fun

    main(args: Array<String>) { "Android Summit is Awesome".addEnthusiasm() } 2. Refactor to Extensions
  45. More Involved Example: Building Lambda Elvis val mightBeNull: String? =

    null val definitelyNotNull: String = mightBeNull ?: "default" “If the thing on the left is null, do the thing on the right” 2. Refactor to Extensions
  46. infix fun <T> T?.orDefault(default: T?.(T?) -> T) = 
 this

    ?: default() More Involved Example: Building Lambda Elvis 2. Refactor to Extensions “For all Types? Add a function called orDefault that accepts a lambda. The lambda must return a non-nullable version of the type. If the instance is null, call the lambda”
  47. More Involved Example: Building Lambda Elvis val displayName = cachedDisplayName

    orDefault { val formatted = formatName(user.first, user.last) cache.displayName = formatted formatted } 2. Refactor to Extensions
  48. Question: Where Will An Extension Help? 2. Refactor to Extensions

    var requestInProgress = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) } RxFragment.kt
  49. Refactoring RxFragment with Extensions private var Bundle.requestInProgress: Boolean get() =

    getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) set(inProgress) = putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, inProgress) 2. Refactor to Extensions
  50. Refactoring RxFragment with Extensions private var Bundle.requestInProgress: Boolean get() =

    getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) set(inProgress) = putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, inProgress) 2. Refactor to Extensions
  51. Carrying the Refactor Forward: Let no longer Needed savedInstanceState?.let {

    requestInProgress = savedInstanceState.getBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, false) } requestInProgress = savedInstanceState?.requestInProgress ?: false onCreate: 2. Refactor to Extensions RxFragment.kt
  52. Carrying the Refactor Forward override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState)

    outState.putBoolean(EXTRA_RX_REQUEST_IN_PROGRESS, requestInProgress) outState.requestInProgress = requestInProgress } onSaveInstanceState: 2. Refactor to Extensions RxFragment.kt
  53. private val TAG_DIALOG_PROGRESS = "dialog_progress" object DialogUtils { internal fun

    showProgressDialog(fragmentManager: FragmentManager, message: String) { fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS)?.let { hideProgressDialog(fragmentManager) } ProgressDialogFragment.newInstance(message).apply { isCancelable = true show(fragmentManager, TAG_DIALOG_PROGRESS) } } internal fun hideProgressDialog(fragmentManager: FragmentManager) { val fragment = fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS) if (fragment is ProgressDialogFragment) { fragment.dismissAllowingStateLoss() } } } 2. Refactor to Extensions DialogUtils.Kt Another Candidate for Refactoring To Extensions
  54. Another Candidate for Refactoring To Extensions private const val TAG_DIALOG_PROGRESS

    = “dialog_progress" private val FragmentManager.progressDialog: ProgressDialogFragment? get() = findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment internal fun showProgressDialog(fragmentManager: FragmentManager, message: String) { if (fragmentManager.findFragmentByTag(DialogUtils.TAG_DIALOG_PROGRESS) != null) { hideProgressDialog(fragmentManager) } val dialog = ProgressDialogFragment.newInstance(message) dialog.isCancelable = false dialog.show(fragmentManager, TAG_DIALOG_PROGRESS) } internal fun hideProgressDialog(fragmentManager: FragmentManager) { val fragment = fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS) (fragment as? ProgressDialogFragment)?.dismissAllowingStateLoss() } 2. Refactor to Extensions DialogUtils.kt
  55. Refactoring to an Extension Property private val FragmentManager.progressDialog: ProgressDialogFragment? get()

    = findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun showProgressDialog(fragmentManager: FragmentManager, message: String) { if (fragmentManager.findFragmentByTag(DialogUtils.TAG_DIALOG_PROGRESS) != null) { hideProgressDialog(fragmentManager) } fragmentManager.progressDialog?.hideProgressDialog(fragmentManager) val dialog = ProgressDialogFragment.newInstance(message) dialog.isCancelable = false dialog.show(fragmentManager, TAG_DIALOG_PROGRESS) } 2. Refactor to Extensions DialogUtils.kt
  56. private val FragmentManager.progressDialog: ProgressDialogFragment? get() = findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun

    showProgressDialog(fragmentManager: FragmentManager, message: String) { if (fragmentManager.findFragmentByTag(DialogUtils.TAG_DIALOG_PROGRESS) != null) { hideProgressDialog(fragmentManager) } fragmentManager.progressDialog?.hideProgressDialog(fragmentManager) val dialog = ProgressDialogFragment.newInstance(message) dialog.isCancelable = false dialog.show(fragmentManager, TAG_DIALOG_PROGRESS) ProgressDialogFragment.newInstance(message).apply { isCancelable = false show(fragmentManager, TAG_DIALOG_PROGRESS) } } 2. Refactor to Extensions Refactoring to an Extension Property DialogUtils.kt
  57. private val FragmentManager.progressDialog: ProgressDialogFragment? get() = findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun

    hideProgressDialog(fragmentManager: FragmentManager) { val fragment = fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS) (fragment as? ProgressDialogFragment)?.dismissAllowingStateLoss() fragmentManager.progressDialog?.dismissAllowingStateLoss() } 2. Refactor to Extensions Carrying the Refactor Forward DialogUtils.kt
  58. Carrying the Refactor Forward private val FragmentManager.progressDialog: ProgressDialogFragment? get() =

    findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun hideProgressDialog(fragmentManager: FragmentManager) = fragmentManager.progressDialog?.dismissAllowingStateLoss() 2. Refactor to Extensions DialogUtils.kt
  59. Pushing the Extension Even Further… private val FragmentManager.progressDialog: ProgressDialogFragment? get()

    = findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun hideProgressDialog(fragmentManager: FragmentManager) = fragmentManager.progressDialog?.dismissAllowingStateLoss() fun showProgressDialog(fragmentManager: FragmentManager, message: String) { fragmentManager.progressDialog?.let { hideProgressDialog(fragmentManager) } ProgressDialogFragment.newInstance(message).apply { isCancelable = false show(fragmentManager, TAG_DIALOG_PROGRESS) } } 2. Refactor to Extensions DialogUtils.kt
  60. Pushing the Extension Further… private val FragmentManager.progressDialog: ProgressDialogFragment? get() =

    findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun hideProgressDialog(fragmentManager: FragmentManager) = fragmentManager.progressDialog?.dismissAllowingStateLoss() fun FragmentManager.hideProgressDialog() = progressDialog?.dismissAllowingStateLoss() DialogUtils.kt 2. Refactor to Extensions
  61. private val FragmentManager.progressDialog: ProgressDialogFragment? get() = findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun

    FragmentManager.hideProgressDialog() = progressDialog?.dismissAllowingStateLoss() fun FragmentManager.showProgressDialog(message: String) { hideProgressDialog() ProgressDialogFragment.newInstance(message).apply { isCancelable = false }.show(this, TAG_DIALOG_PROGRESS) } Pushing the Extension Further… DialogUtils.kt 2. Refactor to Extensions
  62. Before and After DialogUtils.kt private val FragmentManager.progressDialog: ProgressDialogFragment? get() =

    findFragmentByTag(TAG_DIALOG_PROGRESS) as? ProgressDialogFragment fun FragmentManager.hideProgressDialog() = progressDialog?.dismissAllowingStateLoss() fun FragmentManager.showProgressDialog(message: String) { hideProgressDialog() ProgressDialogFragment.newInstance(message).apply { isCancelable = false }.show(this, TAG_DIALOG_PROGRESS) } object DialogUtils { internal fun showProgressDialog(fragmentManager: FragmentManager, message: String) { fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS)?.let { hideProgressDialog(fragmentManager) } ProgressDialogFragment.newInstance(message).apply { isCancelable = true show(fragmentManager, TAG_DIALOG_PROGRESS) } } internal fun hideProgressDialog(fragmentManager: FragmentManager) { val fragment = fragmentManager.findFragmentByTag(TAG_DIALOG_PROGRESS) if (fragment is ProgressDialogFragment) { fragment.dismissAllowingStateLoss() } } } 2. Refactor to Extensions
  63. Before and After: Call Site RxUtils.kt private fun <T> Observable<T>.showLoadingDialog(rxFragment:

    RxFragment): Observable<T> = doOnSubscribe { DialogUtils.showProgressDialog(rxFragment.fragmentManager, LOADING_MESSAGE) } .doOnTerminate { DialogUtils.hideProgressDialog(rxFragment.fragmentManager) } private fun <T> Observable<T>.showLoadingDialog(fragmentManager: FragmentManager): Observable<T> = doOnSubscribe { fragmentManager.showProgressDialog(LOADING_MESSAGE) } .doOnTerminate { fragmentManager.hideProgressDialog() } 2. Refactor to Extensions
  64. 1. Let/Apply to Drop Null Checks & Config Objects 1.

    Let with safe call operator (.?) is good for dropping a != null comparison 2. Let with null coalesce operator (?:) is good for dropping a == null comparison 3. Apply is good for configuring objects with many methods to call Takeaways
  65. 1. Let/Apply to Drop Null Checks & Config Objects 2.

    Refactor to Extensions 1. Extensions give you “this” for free, allows you to drop an argument to a function commonly 2. Let with null coalesce operator is good for dropping a == null comparison 3. Apply is good for configuring objects with many methods to call Takeaways
  66. Resources •Kotlin track on Exercism: http://exercism.io/languages/kotlin/about •Kotlin Language Design https://discuss.kotlinlang.org/c/language-design

    •Kotlin koans: https://kotlinlang.org/docs/tutorials/koans.html •Big Nerd Ranch Kotlin course coming november