Slide 1

Slide 1 text

Pragmatic Kotlin on Android Josh Skeen | [email protected] | @mutexkid

Slide 2

Slide 2 text

Who am I? Android Developer & Instructor at Big Nerd Ranch Josh Skeen | [email protected] | @mutexkid

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Running the Automatic Conversion RxFragment.java

Slide 8

Slide 8 text

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(); }

Slide 9

Slide 9 text

RxFragment.java 1. Properties Aren’t Fields

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Direct Access‽ What About Encapsulation‽ 1. Properties Aren’t Fields private fun Observable.applyRequestStatus(rxFragment: RxFragment): Observable = doOnSubscribe { rxFragment.requestInProgress = true } .doOnTerminate { rxFragment.requestInProgress = false } RxUtil.kt:

Slide 13

Slide 13 text

Looking under the Hood… 1. Properties Aren’t Fields var requestInProgress = false

Slide 14

Slide 14 text

Looking under the Hood… 1. Properties Aren’t Fields

Slide 15

Slide 15 text

Looking under the Hood… var requestInProgress = false generates this… 1. Properties Aren’t Fields

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

2. Var and Val mean “writable” and “read-only”

Slide 18

Slide 18 text

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”

Slide 19

Slide 19 text

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”

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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”

Slide 22

Slide 22 text

Related: No Primitives? 2. Var and Val mean “writable” and “read-only”

Slide 23

Slide 23 text

Related: No Primitives? 2. Var and Val mean “writable” and “read-only” private boolean requestInProgress;

Slide 24

Slide 24 text

3. No Static Keyword, Use File-Level or Object Instead

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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)

Slide 35

Slide 35 text

4. Automatic Migration Makes Mistakes!

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Improve Upon Design w/ Kotlin Features

Slide 48

Slide 48 text

1. Let/Apply to Drop Null Checks & Config Objects

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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 }

Slide 55

Slide 55 text

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) } }

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

2. Refactor to Extensions

Slide 58

Slide 58 text

Extensions: Quick Example fun String.addEnthusiasm() = this + "!!!!" fun main(args: Array) { "Android Summit is Awesome".addEnthusiasm() } 2. Refactor to Extensions

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

infix fun 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”

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Before and After: Call Site RxUtils.kt private fun Observable.showLoadingDialog(rxFragment: RxFragment): Observable = doOnSubscribe { DialogUtils.showProgressDialog(rxFragment.fragmentManager, LOADING_MESSAGE) } .doOnTerminate { DialogUtils.hideProgressDialog(rxFragment.fragmentManager) } private fun Observable.showLoadingDialog(fragmentManager: FragmentManager): Observable = doOnSubscribe { fragmentManager.showProgressDialog(LOADING_MESSAGE) } .doOnTerminate { fragmentManager.hideProgressDialog() } 2. Refactor to Extensions

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

Thanks! Josh Skeen | [email protected] | @mutexkid