$30 off During Our Annual Pro Sale. View Details »

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. Pragmatic Kotlin on Android
    Josh Skeen | [email protected] | @mutexkid

    View Slide

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

    View Slide

  3. 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

    View Slide

  4. 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

    View Slide

  5. 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

    View Slide

  6. 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

    View Slide

  7. Running the Automatic Conversion
    RxFragment.java

    View Slide

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

    View Slide

  9. RxFragment.java
    1. Properties Aren’t Fields

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

  12. 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:

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. 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

    View Slide

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

    View Slide

  18. 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”

    View Slide

  19. 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”

    View Slide

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

    View Slide

  21. 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”

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. 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

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

  30. 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

    View Slide

  31. 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

    View Slide

  32. 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

    View Slide

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

    View Slide

  34. 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)

    View Slide

  35. 4. Automatic Migration Makes Mistakes!

    View Slide

  36. 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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

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

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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)

    View Slide

  45. 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

    View Slide

  46. 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

    View Slide

  47. Improve Upon Design w/ Kotlin Features

    View Slide

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

    View Slide

  49. 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

    View Slide

  50. 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

    View Slide

  51. 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

    View Slide

  52. 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

    View Slide

  53. 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

    View Slide

  54. 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
    }

    View Slide

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

    View Slide

  56. 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

    View Slide

  57. 2. Refactor to Extensions

    View Slide

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

    View Slide

  59. 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

    View Slide

  60. 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”

    View Slide

  61. 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

    View Slide

  62. 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

    View Slide

  63. 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

    View Slide

  64. 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

    View Slide

  65. 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

    View Slide

  66. 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

    View Slide

  67. 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

    View Slide

  68. 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

    View Slide

  69. 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

    View Slide

  70. 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

    View Slide

  71. 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

    View Slide

  72. 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

    View Slide

  73. 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

    View Slide

  74. 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

    View Slide

  75. 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

    View Slide

  76. 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

    View Slide

  77. 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

    View Slide

  78. 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

    View Slide

  79. 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

    View Slide

  80. 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

    View Slide

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

    View Slide