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

Lessons Learned From Porting To Swift And Kotlin - Mobile Era Oslo, October 2017

Lessons Learned From Porting To Swift And Kotlin - Mobile Era Oslo, October 2017

Video:
https://vimeo.com/237875498

Abstract:
SpotHero has been moving its entire iOS application to Swift and started moving its Android app to Kotlin. Here's some lessons learned in the process in terms of selling management on using a new languages, how to get started, and some tips on things you should do along the way.

(Big thanks to my old crew at SpotHero!)

Ellen Shapiro
PRO

October 05, 2017
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. Lessons learned from porting to
    Swift and Kotlin
    by Ellen Shapiro
    Mobile Era | Oslo, Norway | October 2017
    @designatednerd | justhum.com | designatednerd.com

    View Slide

  2. View Slide

  3. (this is awkward)

    View Slide

  4. !

    View Slide

  5. !

    View Slide

  6. !""

    View Slide

  7. !"#

    View Slide

  8. !"#

    View Slide

  9. !

    View Slide

  10. !

    View Slide

  11. View Slide

  12. View Slide

  13. ANYWAY

    View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. Android

    View Slide

  22. iOS

    View Slide

  23. iOS (excluding dependencies)

    View Slide

  24. View Slide

  25. Step 1
    Selling it to The Bosses

    View Slide

  26. Identify specific problems
    addressed by the new language

    View Slide

  27. Objective-C
    if (![paymentNetworks containsObject:PKPaymentNetworkDiscover]) {
    [paymentNetworks addObject:PKPaymentNetworkDiscover];
    }

    View Slide

  28. View Slide

  29. Objective-C
    if (![paymentNetworks containsObject:PKPaymentNetworkDiscover]) {
    [paymentNetworks addObject:PKPaymentNetworkDiscover];
    }
    iOS 9 = !

    View Slide

  30. Objective-C
    if (![paymentNetworks containsObject:PKPaymentNetworkDiscover]) {
    [paymentNetworks addObject:PKPaymentNetworkDiscover];
    }
    iOS 8 = !

    View Slide

  31. Swift
    if !paymentNetworks.contains(.discover) {
    paymentNetworks.append(.discover)
    }

    View Slide

  32. Swift
    if !paymentNetworks.contains(.discover) {
    paymentNetworks.append(.discover)
    }
    !" Does Not Compile

    View Slide

  33. Swift
    if @available(iOS 9.0, *) {
    if !paymentNetworks.contains(.discover) {
    paymentNetworks.append(.discover)
    }
    }

    View Slide

  34. Swift
    if @available(iOS 9.0, *) {
    if !paymentNetworks.contains(.discover) {
    paymentNetworks.append(.discover)
    }
    }
    Safe at compile time and run time

    View Slide

  35. Be honest
    about potential drawbacks

    View Slide

  36. Be honest
    (with yourself)
    about potential drawbacks

    View Slide

  37. View Slide

  38. View Slide

  39. View Slide

  40. View Slide

  41. Step 2
    Start small

    View Slide

  42. Step 2
    Start small

    View Slide

  43. Learn the
    new language

    View Slide

  44. Fight New System APIs
    or A New Language
    not both at once

    View Slide

  45. Stop writing old language patterns
    in a new language

    View Slide

  46. Objective-C
    NSMutableArray *capitalizedStrings = [NSMutableArray array];
    for (NSString *string in arrayOfStrings) {
    NSString *thing = [string capitalized];
    [capitalizedStrings addObject: thing];
    }

    View Slide

  47. Objective-C, but written in Swift
    var capitalizedStrings = [String]()
    for string in arrayOfStrings {
    let thing = string.capitalized
    capitalizedStrings.append(thing)
    }

    View Slide

  48. Swiftier
    let capitalizedStrings = arrayOfStrings.map {
    string in
    let thing = string.capitalized
    return thing
    }

    View Slide

  49. Swiftier
    let capitalizedStrings = arrayOfStrings.map {
    string in
    let thing = string.capitalized
    return thing
    }
    Swiftiest
    let capitalizedStrings = arrayOfStrings.map { $0.capitalized }

    View Slide

  50. Limit your risk

    View Slide

  51. A parable of going
    too far, too fast

    View Slide

  52. View Slide

  53. View Slide

  54. View Slide

  55. View Slide

  56. View Slide

  57. View Slide

  58. View Slide

  59. Be willing to pause
    and re-evaluate
    (before everything goes wrong)

    View Slide

  60. Be willing to pause
    and re-evaluate
    (before everything goes wrong)

    View Slide

  61. Step 3
    Biggest ! For Your "

    View Slide

  62. Step 3
    Biggest ! For Your "

    View Slide

  63. Look at your
    bugs and crashes

    View Slide

  64. NPE

    View Slide

  65. NPE + GoogleMaps

    View Slide

  66. Look at your most
    error-prone code

    View Slide

  67. Objective-C JSON Parsing
    - (instancetype)fromJSONData:(NSData *)data
    {
    NSDictionary* jsonObject = [NSJSONSerialization jsonObjectWithData:data error:nil];
    if (jsonObject == nil]) {
    return nil;
    }
    user.firstName = dictionary[@"first_name"];
    user.lastName = dictionary[@"last_name"];
    user.age = [dictionary[@"age"] integerValue];
    return user;
    }

    View Slide

  68. Objective-C JSON Parsing
    - (instancetype)fromJSONData:(NSData *)data
    {
    NSDictionary* jsonObject = [NSJSONSerialization jsonObjectWithData:data error:nil];
    if (jsonObject == nil || ![jsonObject isKindOfClass:[NSDictionary class]]) {
    return nil;
    }
    if ([dictionary[@"first_name"] isKindOfClass: [NSString class]]) {
    user.firstName = dictionary[@"first_name"];
    } else {
    return nil;
    }
    if ([dictionary[@"last_name"] isKindOfClass: [NSString class]]) {
    user.lastName = dictionary[@"last_name"];
    } else {
    return nil;
    }
    if ([dictionary[@"age"] isKindOfClass: [NSNumber class]]) {
    user.age = [dictionary[@"age"] integerValue];
    } else {
    return nil;
    }
    return user;
    }

    View Slide

  69. Swift 4 JSON Parsing
    static func from(jsonData: Data) -> User? {
    let decoder = JSONDecoder()
    return try? decoder.decode(User.self, for: jsonData)
    }

    View Slide

  70. (This does require some setup)
    struct User: Codable {
    let firstName: String
    let lastName: String
    let age: Int
    enum CodingKeys: String, CodingKey {
    case
    firstName = "first_name",
    lastName = "last_name",
    age
    }
    }

    View Slide

  71. Step 4
    Advanced Features
    for Fun and Profit

    View Slide

  72. Step 4
    Advanced Features
    for Fun, Maintainability, and Profit

    View Slide

  73. Make your code more
    reusable

    View Slide

  74. Swift: Protocols!

    View Slide

  75. Kotlin: Extensions!

    View Slide

  76. Make your code
    safer

    View Slide

  77. Swift: Version handling!

    View Slide

  78. Kotlin: XML ids
    Become automatic variables

    View Slide

  79. activity_profile.xml
    android:id="@+id/textview_username"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
    ProfileActivity.java
    TextView mUsernameTextView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.profile_activity);
    mUsernameTextView = (TextView) findViewById(R.id.textview_username);
    mUsernameTextView.setText("Hello, User");
    }

    View Slide

  80. activity_profile.xml
    android:id="@+id/textview_username"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
    ProfileActivity.java
    @BindView(R.id.textview_username)
    TextView mUsernameTextView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.profile_activity);
    mUsernameTextView.setText("Hello, User");
    }

    View Slide

  81. activity_profile.xml
    android:id="@+id/textview_username"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
    ProfileActivity.kt
    import kotlinx.android.synthetic.main.activity_profile.*
    public override fun onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_profile)
    textview_username.text = "Hello, User"
    }

    View Slide

  82. activity_profile.xml
    android:id="@+id/usernameTextView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
    ProfileActivity.kt
    import kotlinx.android.synthetic.main.activity_profile.*
    public override fun onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_profile)
    usernameTextView = "Hello, User"
    }

    View Slide

  83. Things You Should Do
    at Every Step

    View Slide

  84. Test

    View Slide

  85. View Slide

  86. Seriously though,
    Test

    View Slide

  87. What was I even
    trying
    to do here?

    View Slide

  88. Share

    View Slide

  89. Share strategies

    View Slide

  90. Share benefits

    View Slide

  91. Share problems

    View Slide

  92. Review

    View Slide

  93. Learn by reading

    View Slide

  94. Keep code clear

    View Slide

  95. Swiftiest
    let capitalizedStrings = arrayOfStrings.map { $0.capitalized }

    View Slide

  96. Unnecessarily One-Liniest
    let studentNames = students.reduce("") { $0 + "\n" + ($1.firstName ?? "") + " " + ($1.lastName ?? "") }

    View Slide

  97. ! Borderline Incomprehensible !
    array.flatMap { $0 }
    .map { $0.doSomething() }
    .flatMap { $0 }
    .filter { $0.isSomething || $0.isSomethingElse }
    .reduce("") { $0 + $1.description }

    View Slide

  98. "I don't understand this code"
    !=
    "I don't understand this language"

    View Slide

  99. Reducing confusion

    View Slide

  100. Listen
    to the creators

    View Slide

  101. View Slide

  102. View Slide

  103. View Slide

  104. View Slide

  105. View Slide

  106. View Slide

  107. View Slide

  108. Listen
    skeptically
    to the creators

    View Slide

  109. View Slide

  110. View Slide

  111. View Slide

  112. View Slide

  113. Watch out for
    Yaks

    View Slide

  114. View Slide

  115. Never let the perfect
    be the enemy of the shipping

    View Slide

  116. Obligatory Summary Slide
    → Honestly assess the pros and cons
    → Start small, then go bonkers once it works
    → Test the crap out of stuff in the new language
    → Share your code, your joy, and your pain
    → Resist desire to rewrite your whole app at once
    → Ship it!

    View Slide

  117. Links
    → Concurrency In Swift: One Approach
    https://gist.github.com/lattner/
    31ed37682ef1576b16bca1432ea9f782
    → Apple's Swift Blog
    https://developer.apple.com/swift/blog/
    → Jetbrains' Kotlin Blog
    https://blog.jetbrains.com/kotlin/

    View Slide

  118. Links
    → Kotlin Android Extensions
    https://kotlinlang.org/docs/tutorials/android-
    plugin.html

    View Slide

  119. Videos
    → Anything You Can Do, I Can Do Better
    (Kickstarter team on FP in Kotlin vs. Swift)
    https://www.youtube.com/watch?
    v=_DuGaAkQSnM
    → iOS & Android & Swift & Kotlin
    (Stuart Kent on Kotlin for iOS Devs)
    http://www.stkent.com/2017/07/27/ios-and-
    android-and-swift-and-kotlin.html

    View Slide

  120. Photo Credits
    → Paperwork, by Keith Williamson
    https://www.flickr.com/photos/elwillo/4729801304
    → Simpsons screenshots + memes, Frinkiac
    https://frinkiac.com/

    View Slide