Slide 1

Slide 1 text

@skentphd iOS & Android & Swift & Kotlin

Slide 2

Slide 2 text

@skentphd Who? Stuart Kent stkent.com @skentphd http://bit.ly/cocoaheads-july-2017

Slide 3

Slide 3 text

@skentphd Who? Stuart Kent stkent.com @skentphd http://bit.ly/cocoaheads-july-2017

Slide 4

Slide 4 text

@skentphd ! What?

Slide 5

Slide 5 text

@skentphd $ What Not?

Slide 6

Slide 6 text

@skentphd Why?

Slide 7

Slide 7 text

@skentphd For common topics: • Illustrate similarities by comparing code. • Discuss differences and mitigation strategies. Recommend resources & tactics. Q&As. How?

Slide 8

Slide 8 text

@skentphd • Languages • Architecture • Layouts Topics

Slide 9

Slide 9 text

@skentphd • Application lifecycle • Networking • Continuous Integration • Distribution • Soft Skills ❤ Not Topics

Slide 10

Slide 10 text

@skentphd • IDEs (mostly) • Frameworks Also Not Topics

Slide 11

Slide 11 text

@skentphd Level Check

Slide 12

Slide 12 text

@skentphd Me Android Java Kotlin iOS Objective-C Swift

Slide 13

Slide 13 text

@skentphd You? iOS Objective-C Swift Android Java Kotlin

Slide 14

Slide 14 text

@skentphd Languages

Slide 15

Slide 15 text

@skentphd Principles • Safe • Expressive • Concise • Multi-paradigm • Fast • Interoperable

Slide 16

Slide 16 text

@skentphd Principles • Safe • Expressive • Concise • Multi-paradigm • Fast • Interoperable

Slide 17

Slide 17 text

@skentphd Syntax & Capabilities

Slide 18

Slide 18 text

@skentphd Swift 4 class User { let first: String let last: String? init( first: String, last: String? = nil) { self.first = first self.last = last } } class User { val first: String val last: String? constructor( first: String, last: String? = null) { this.first = first this.last = last } } Kotlin 1.1.3

Slide 19

Slide 19 text

@skentphd Swift class User { let first: String let last: String? init( first: String, last: String? = nil) { self.first = first self.last = last } } class User { val first: String val last: String? constructor( first: String, last: String? = null) { this.first = first this.last = last } } Kotlin

Slide 20

Slide 20 text

@skentphd Swift class User { let first: String let last: String? init( first: String, last: String? = nil) { self.first = first self.last = last } } class User { val first: String val last: String? constructor( first: String, last: String? = null) { this.first = first this.last = last } } Kotlin

Slide 21

Slide 21 text

@skentphd Swift class User { let first: String let last: String? init( first: String, last: String? = nil) { self.first = first self.last = last } } class User { val first: String val last: String? constructor( first: String, last: String? = null) { this.first = first this.last = last } } Kotlin

Slide 22

Slide 22 text

@skentphd Swift class User { let first: String let last: String? init( first: String, last: String? = nil) { self.first = first self.last = last } } class User { val first: String val last: String? constructor( first: String, last: String? = null) { this.first = first this.last = last } } Kotlin

Slide 23

Slide 23 text

@skentphd Swift let stuart = User(first: "stuart", last: "kent") Kotlin val stuart = User(first = "stuart", last = "kent")

Slide 24

Slide 24 text

@skentphd Swift let stuart = User(first: "stuart", last: "kent") stuart.first // "stuart" Kotlin val stuart = User(first = "stuart", last = "kent") stuart.first // "stuart"

Slide 25

Slide 25 text

@skentphd Swift let stuart = User(first: "stuart", last: "kent") stuart.first // "stuart" stuart.last // "kent" Kotlin val stuart = User(first = "stuart", last = "kent") stuart.first // "stuart" stuart.last // "kent"

Slide 26

Slide 26 text

@skentphd Swift let stuart = User(first: "stuart", last: "kent") stuart.first // "stuart" stuart.last // "kent" "\(stuart.first) \(stuart.last)" // "stuart Optional("kent")" Kotlin val stuart = User(first = "stuart", last = "kent") stuart.first // "stuart" stuart.last // "kent" "${stuart.first} ${stuart.last}" // "stuart kent"

Slide 27

Slide 27 text

@skentphd Swift let eminem = User(first: "eminem") Kotlin val eminem = User(first = "eminem")

Slide 28

Slide 28 text

@skentphd Swift let eminem = User(first: "eminem") eminem.first // "eminem" Kotlin val eminem = User(first = "eminem") eminem.first // "eminem"

Slide 29

Slide 29 text

@skentphd Swift let eminem = User(first: "eminem") eminem.first // "eminem" eminem.last // nil Kotlin val eminem = User(first = "eminem") eminem.first // "eminem" eminem.last // null

Slide 30

Slide 30 text

@skentphd Swift let eminem = User(first: "eminem") eminem.first // "eminem" eminem.last // nil "\(eminem.first) \(eminem.last)" // "eminem nil" Kotlin val eminem = User(first = "eminem") eminem.first // "eminem" eminem.last // null "${eminem.first} ${eminem.last}" // "eminem null"

Slide 31

Slide 31 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 32

Slide 32 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 33

Slide 33 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 34

Slide 34 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 35

Slide 35 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 36

Slide 36 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } stuart.fullName() // "stuart kent" eminem.fullName() // "eminem" class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } stuart.fullName() // "stuart kent" eminem.fullName() // "eminem" Kotlin

Slide 37

Slide 37 text

@skentphd Swift extension String { func cap() -> String { guard !isEmpty else { return "" } return String(uppercased().first!) + dropFirst() } } fun String.cap(): String { if (isEmpty()) return "" return toUpperCase().first() + drop(1) } Kotlin

Slide 38

Slide 38 text

@skentphd Swift extension String { func cap() -> String { guard !isEmpty else { return "" } return String(uppercased().first!) + dropFirst() } } fun String.cap(): String { if (isEmpty()) return "" return toUpperCase().first() + drop(1) } Kotlin

Slide 39

Slide 39 text

@skentphd Swift extension String { func cap() -> String { guard !isEmpty else { return "" } return String(uppercased().first!) + dropFirst() } } fun String.cap(): String { if (isEmpty()) return "" return toUpperCase().first() + drop(1) } Kotlin

Slide 40

Slide 40 text

@skentphd Swift extension String { func cap() -> String { guard !isEmpty else { return "" } return String(uppercased().first!) + dropFirst() } } fun String.cap(): String { if (isEmpty()) return "" return toUpperCase().first() + drop(1) } Kotlin

Slide 41

Slide 41 text

@skentphd Swift extension String { func cap() -> String { guard !isEmpty else { return "" } return String(uppercased().first!) + dropFirst() } } fun String.cap(): String { if (isEmpty()) return "" return toUpperCase().first() + drop(1) } Kotlin

Slide 42

Slide 42 text

@skentphd Swift extension String { func cap() -> String { guard !isEmpty else { return "" } return String(uppercased().first!) + dropFirst() } } "stuart".cap() // "Stuart" fun String.cap(): String { if (isEmpty()) return "" return toUpperCase().first() + drop(1) } "stuart".cap() // "Stuart" Kotlin

Slide 43

Slide 43 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 44

Slide 44 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .joinToString(" ") } } Kotlin

Slide 45

Slide 45 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .map { $0!.cap() } .joined(separator: " ") } } class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .map { it.cap() } .joinToString(" ") } } Kotlin

Slide 46

Slide 46 text

@skentphd Swift class User { // ... func fullName() -> String { return [first, last] .flatMap { $0 } .map { $0!.cap() } .joined(separator: " ") } } stuart.fullName() // "Stuart Kent" eminem.fullName() // "Eminem" class User { // ... fun fullName(): String { return arrayOf(first, last) .filterNotNull() .map { it.cap() } .joinToString(" ") } } stuart.fullName() // "Stuart Kent" eminem.fullName() // "Eminem" Kotlin

Slide 47

Slide 47 text

@skentphd Swift class User { // ... var reversedName: String { return fullName() .split(separator: " ") .map { String($0) } .reversed() .joined(separator: ", ") } } class User { // ... val reversedName: String get() = fullName() .split(Regex(" ")) // No explicit cast needed. .asReversed() .joinToString(", ") } Kotlin

Slide 48

Slide 48 text

@skentphd Swift class User { // ... var reversedName: String { return fullName() .split(separator: " ") .map { String($0) } .reversed() .joined(separator: ", ") } } class User { // ... val reversedName: String get() = fullName() .split(Regex(" ")) // No explicit cast needed. .asReversed() .joinToString(", ") } Kotlin

Slide 49

Slide 49 text

@skentphd Swift class User { // ... var reversedName: String { return fullName() .split(separator: " ") .map { String($0) } .reversed() .joined(separator: ", ") } } class User { // ... val reversedName: String get() = fullName() .split(Regex(" ")) // No explicit cast needed. .asReversed() .joinToString(", ") } Kotlin

Slide 50

Slide 50 text

@skentphd Swift class User { // ... var reversedName: String { return fullName() .split(separator: " ") .map { String($0) } .reversed() .joined(separator: ", ") } } stuart.reversedName // "Kent, Stuart" eminem.reversedName // "Eminem" class User { // ... val reversedName: String get() = fullName() .split(Regex(" ")) // No explicit cast needed. .asReversed() .joinToString(", ") } stuart.reversedName // "Kent, Stuart" eminem.reversedName // "Eminem" Kotlin

Slide 51

Slide 51 text

@skentphd Swift enum Type { case visa case masterCard case discover } enum class Type { VISA, MASTERCARD, DISCOVER } Kotlin

Slide 52

Slide 52 text

@skentphd Swift enum Type { case visa case masterCard case discover } func getUrl(t: Type) -> String { switch t { case .visa: return "/visa" case .masterCard: return "/mc" case .discover: return "/disc" // No default case required! } } enum class Type { VISA, MASTERCARD, DISCOVER } fun getUrl(t: Type): String { return when (t) { VISA -> "/visa" MASTERCARD -> "/mc" DISCOVER -> "/disc" // No default case required! } } Kotlin

Slide 53

Slide 53 text

@skentphd Swift enum Type { case visa case masterCard case discover } func getUrl(t: Type) -> String { switch t { case .visa: return "/visa" case .masterCard: return "/mc" case .discover: return "/disc" // No default case required! } } enum class Type { VISA, MASTERCARD, DISCOVER } fun getUrl(t: Type): String { return when (t) { VISA -> "/visa" MASTERCARD -> "/mc" DISCOVER -> "/disc" // No default case required! } } Kotlin

Slide 54

Slide 54 text

@skentphd Swift enum Type { case visa case masterCard case discover } func getUrl(t: Type) -> String { switch t { case .visa: return "/visa" case .masterCard: return "/mc" case .discover: return "/disc" // No default case required! } } enum class Type { VISA, MASTERCARD, DISCOVER } fun getUrl(t: Type): String { return when (t) { VISA -> "/visa" MASTERCARD -> "/mc" DISCOVER -> "/disc" // No default case required! } } Kotlin

Slide 55

Slide 55 text

@skentphd Swift enum Type { case visa case masterCard case discover } func getUrl(t: Type) -> String { switch t { case .visa: return "/visa" case .masterCard: return "/mc" case .discover: return "/disc" // No default case required! } } enum class Type { VISA, MASTERCARD, DISCOVER } fun getUrl(t: Type): String { return when (t) { VISA -> "/visa" MASTERCARD -> "/mc" DISCOVER -> "/disc" // No default case required! } } Kotlin

Slide 56

Slide 56 text

@skentphd Swift enum Type { case visa case masterCard case discover } func getUrl(t: Type) -> String { switch t { case .visa: return "/visa" case .masterCard: return "/mc" case .discover: return "/disc" // No default case required! } } enum class Type { VISA, MASTERCARD, DISCOVER } fun getUrl(t: Type): String { return when (t) { VISA -> "/visa" MASTERCARD -> "/mc" DISCOVER -> "/disc" // No default case required! } } Kotlin

Slide 57

Slide 57 text

@skentphd Swift enum Type { case visa case masterCard case discover } func getUrl(t: Type) -> String { switch t { case .visa: return "/visa" case .masterCard: return "/mc" case .discover: return "/disc" // No default case required! } } getUrl(t: .discover) // "/disc" enum class Type { VISA, MASTERCARD, DISCOVER } fun getUrl(t: Type): String { return when (t) { VISA -> "/visa" MASTERCARD -> "/mc" DISCOVER -> "/disc" // No default case required! } } getUrl(DISCOVER) // "/disc" Kotlin

Slide 58

Slide 58 text

@skentphd Swift enum Method { case cash case card(String) } sealed class Method { class Cash : Method() class Card(val num: String):Method() } Kotlin

Slide 59

Slide 59 text

@skentphd Swift enum Method { case cash case card(String) } func getUrl(m: Method) -> String { switch method { case .cash: return "/cash" case .card(let num): return "/card/\(num)" // No default case required! } } sealed class Method { class Cash : Method() class Card(val num: String):Method() } fun getUrl(m: Method): String { return when (method) { is Cash -> "/cash" is Card -> "/card/${method.num}" // No default case required! } } Kotlin

Slide 60

Slide 60 text

@skentphd Swift enum Method { case cash case card(String) } func getUrl(m: Method) -> String { switch method { case .cash: return "/cash" case .card(let num): return "/card/\(num)" // No default case required! } } sealed class Method { class Cash : Method() class Card(val num: String):Method() } fun getUrl(m: Method): String { return when (method) { is Cash -> "/cash" is Card -> "/card/${method.num}" // No default case required! } } Kotlin

Slide 61

Slide 61 text

@skentphd Swift enum Method { case cash case card(String) } func getUrl(m: Method) -> String { switch method { case .cash: return "/cash" case .card(let num): return "/card/\(num)" // No default case required! } } sealed class Method { class Cash : Method() class Card(val num: String):Method() } fun getUrl(m: Method): String { return when (method) { is Cash -> "/cash" is Card -> "/card/${method.num}" // No default case required! } } Kotlin

Slide 62

Slide 62 text

@skentphd Swift enum Method { case cash case card(String) } func getUrl(m: Method) -> String { switch method { case .cash: return "/cash" case .card(let num): return "/card/\(num)" // No default case required! } } sealed class Method { class Cash : Method() class Card(val num: String):Method() } fun getUrl(m: Method): String { return when (method) { is Cash -> "/cash" is Card -> "/card/${method.num}" // No default case required! } } Kotlin

Slide 63

Slide 63 text

@skentphd Swift enum Method { case cash case card(String) } func getUrl(m: Method) -> String { switch method { case .cash: return "/cash" case .card(let num): return "/card/\(num)" // No default case required! } } // "/card/1234" getUrl(m: .card("1234")) sealed class Method { class Cash : Method() class Card(val num: String):Method() } fun getUrl(m: Method): String { return when (method) { is Cash -> "/cash" is Card -> "/card/${method.num}" // No default case required! } } // "/card/1234" getUrl(Card("1234")) Kotlin

Slide 64

Slide 64 text

@skentphd So far, soooo good...

Slide 65

Slide 65 text

@skentphd Swift struct Name { let first: String let last: String? } data class Name( val first: String, val last: String? = null) Kotlin

Slide 66

Slide 66 text

@skentphd Swift struct Name { let first: String let last: String? } let stuartName = Name(first: "Stuart", last: "Kent") data class Name( val first: String, val last: String? = null) val stuartName = Name(first="Stuart", last="Kent") Kotlin

Slide 67

Slide 67 text

@skentphd Swift struct Name { let first: String let last: String? } let stuartName = Name(first: "Stuart", last: "Kent") let eminemName = Name(first: "Eminem", last: null) data class Name( val first: String, val last: String? = null) val stuartName = Name(first="Stuart", last="Kent") val eminemName = Name(first="Eminem") Kotlin

Slide 68

Slide 68 text

@skentphd Swift // Value type struct Name { let first: String let last: String? } let stuartName = Name(first: "Stuart", last: "Kent") let eminemName = Name(first: "Eminem", last: null) // Reference type data class Name( val first: String, val last: String? = null) val stuartName = Name(first="Stuart", last="Kent") val eminemName = Name(first="Eminem") Kotlin

Slide 69

Slide 69 text

@skentphd Swift // Value type struct Name { let first: String let last: String? } let stuartName = Name(first: "Stuart", last: "Kent") let eminemName = Name(first: "Eminem", last: null) // Reference type data class Name( val first: String, val last: String? = null) val stuartName = Name(first="Stuart", last="Kent") val eminemName = Name(first="Eminem") val littleStuartName = stuartName.copy(last = "Little") Kotlin

Slide 70

Slide 70 text

@skentphd Swift // Value type struct Name { let first: String let last: String? } let stuartName = Name(first: "Stuart", last: "Kent") let eminemName = Name(first: "Eminem", last: null) // Reference type data class Name( val first: String, val last: String? = null) val stuartName = Name(first="Stuart", last="Kent") val eminemName = Name(first="Eminem") val littleStuartName = stuartName.copy(last = "Little") Kotlin

Slide 71

Slide 71 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } interface InitialsProvider { fun initials(): String } Kotlin

Slide 72

Slide 72 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User: InitialsProvider { // ... func initials() -> String { return fullName() .split(separator: " ") .map { String($0.first!) } .joined(separator: "") } } interface InitialsProvider { fun initials(): String } class User : InitialsProvider { // ... override fun initials(): String { return fullName() .split(Regex(" ")) .map { it.first() } .joinToString("") } } Kotlin

Slide 73

Slide 73 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User: InitialsProvider { // ... func initials() -> String { return fullName() .split(separator: " ") .map { String($0.first!) } .joined(separator: "") } } interface InitialsProvider { fun initials(): String } class User : InitialsProvider { // ... override fun initials(): String { return fullName() .split(Regex(" ")) .map { it.first() } .joinToString("") } } Kotlin

Slide 74

Slide 74 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User: InitialsProvider { // ... func initials() -> String { return fullName() .split(separator: " ") .map { String($0.first!) } .joined(separator: "") } } interface InitialsProvider { fun initials(): String } class User : InitialsProvider { // ... override fun initials(): String { return fullName() .split(Regex(" ")) .map { it.first() } .joinToString("") } } Kotlin

Slide 75

Slide 75 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User: InitialsProvider { // ... func initials() -> String { return fullName() .split(separator: " ") .map { String($0.first!) } .joined(separator: "") } } interface InitialsProvider { fun initials(): String } class User : InitialsProvider { // ... override fun initials(): String { return fullName() .split(Regex(" ")) .map { it.first() } .joinToString("") } } Kotlin

Slide 76

Slide 76 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User: InitialsProvider { // ... func initials() -> String { return fullName() .split(separator: " ") .map { String($0.first!) } .joined(separator: "") } } interface InitialsProvider { fun initials(): String } class User : InitialsProvider { // ... override fun initials(): String { return fullName() .split(Regex(" ")) .map { it.first() } .joinToString("") } } Kotlin

Slide 77

Slide 77 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User: InitialsProvider { // ... func initials() -> String { return fullName() .split(separator: " ") .map { String($0.first!) } .joined(separator: "") } } stuart.initials() // "SK" eminem.initials() // "E" interface InitialsProvider { fun initials(): String } class User : InitialsProvider { // ... override fun initials(): String { return fullName() .split(Regex(" ")) .map { it.first() } .joinToString("") } } stuart.initials() // "SK" eminem.initials() // "E" Kotlin

Slide 78

Slide 78 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } Kotlin

Slide 79

Slide 79 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User {/**/} Kotlin

Slide 80

Slide 80 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User {/**/} extension User: InitialsProvider { func initials() -> String {/**/} } Kotlin

Slide 81

Slide 81 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User {/**/} extension User: InitialsProvider { func initials() -> String {/**/} } Kotlin

Slide 82

Slide 82 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User {/**/} extension User: InitialsProvider { func initials() -> String {/**/} } Kotlin

Slide 83

Slide 83 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User {/**/} extension User: InitialsProvider { func initials() -> String {/**/} } stuart is InitialsProvider // true Kotlin

Slide 84

Slide 84 text

@skentphd Swift protocol InitialsProvider { func initials() -> String } class User {/**/} extension User: InitialsProvider { func initials() -> String {/**/} } stuart is InitialsProvider // true ⛔ Kotlin

Slide 85

Slide 85 text

@skentphd Swift extension ProgressIndicatable where Self: UIViewController {/**/} Kotlin

Slide 86

Slide 86 text

@skentphd Swift extension ProgressIndicatable where Self: UIViewController {/**/} extension Stack where Element: Equatable {/**/} Kotlin

Slide 87

Slide 87 text

@skentphd Swift extension ProgressIndicatable where Self: UIViewController {/**/} extension Stack where Element: Equatable {/**/} ⛔ Kotlin

Slide 88

Slide 88 text

@skentphd Architecture

Slide 89

Slide 89 text

@skentphd Shared Architectures • ⛔ Protocol-oriented • ✅ Platform-agnostic: MVP, MVVM, etc.

Slide 90

Slide 90 text

@skentphd iOS // Mockable protocol UserView: class { // View updating methods } // Mockable interface UserView { // View updating methods } Android

Slide 91

Slide 91 text

@skentphd iOS // Mockable protocol UserView: class { // View updating methods } // (Unit) Testable class UserPresenter { weak var view: UserView init(_ view: UserView) {/**/} func start() {} func stop() {} // Other view update callbacks } // Mockable interface UserView { // View updating methods } // (Unit) Testable class UserPresenter { var view: UserView constructor(view: UserView) {/**/} fun start() {} fun stop() {} // Other view update callbacks } Android

Slide 92

Slide 92 text

@skentphd iOS // Mockable protocol UserView: class { // View updating methods } // (Unit) Testable class UserPresenter { weak var view: UserView init(_ view: UserView) {/**/} func start() {} func stop() {} // Other view update callbacks } // Mockable interface UserView { // View updating methods } // (Unit) Testable class UserPresenter { var view: UserView constructor(view: UserView) {/**/} fun start() {} fun stop() {} // Other view update callbacks } Android

Slide 93

Slide 93 text

@skentphd iOS // Mockable protocol UserView: class { // View updating methods } // (Unit) Testable class UserPresenter { weak var view: UserView init(_ view: UserView) {/**/} func start() {} func stop() {} // Other view update callbacks } // Mockable interface UserView { // View updating methods } // (Unit) Testable class UserPresenter { var view: UserView constructor(view: UserView) {/**/} fun start() {} fun stop() {} // Other view update callbacks } Android

Slide 94

Slide 94 text

@skentphd iOS // Mockable protocol UserView: class { // View updating methods } // (Unit) Testable class UserPresenter { weak var view: UserView init(_ view: UserView) {/**/} func start() {} func stop() {} // Other view update callbacks } // Mockable interface UserView { // View updating methods } // (Unit) Testable class UserPresenter { var view: UserView constructor(view: UserView) {/**/} fun start() {} fun stop() {} // Other view update callbacks } Android

Slide 95

Slide 95 text

@skentphd iOS // Mockable protocol UserView: class { // View updating methods } // (Unit) Testable class UserPresenter { weak var view: UserView init(_ view: UserView) {/**/} func start() {} func stop() {} // Other view update callbacks } // Mockable interface UserView { // View updating methods } // (Unit) Testable class UserPresenter { var view: UserView constructor(view: UserView) {/**/} fun start() {} fun stop() {} // Other view update callbacks } Android

Slide 96

Slide 96 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 97

Slide 97 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 98

Slide 98 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 99

Slide 99 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 100

Slide 100 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 101

Slide 101 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 102

Slide 102 text

@skentphd iOS class UserVC: UIViewController, UserView { var userPresenter: UserPresenter? override func viewDidLoad() { super.viewDidLoad() userPresenter = UserPresenter(self) } override func viewWillAppear(/**/) { super.viewWillAppear(/**/) userPresenter?.start() } override func viewWillDisappear(/**/) { userPresenter?.stop() super.viewWillDisappear(/**/) } } class UserActivity: Activity(), UserView { var userPresenter: UserPresenter? = null override fun onCreate(/**/) { super.onCreate(/**/) userPresenter = UserPresenter(this) } override fun onStart() { super.onStart() userPresenter?.start() } override fun onStop() { userPresenter?.stop() super.onStop() } } Android

Slide 103

Slide 103 text

@skentphd Layouts

Slide 104

Slide 104 text

@skentphd 2008 iPhone iOS

Slide 105

Slide 105 text

@skentphd 2008 iPhone 2010 iPad iOS

Slide 106

Slide 106 text

@skentphd 2008 iPhone 2010 iPad 2012 iPhone 5; iPad mini; AutoLayout iOS

Slide 107

Slide 107 text

@skentphd 2008 iPhone 2010 iPad 2012 iPhone 5; iPad mini; AutoLayout 2014 iPhone 6; iPhone 6 Plus iOS

Slide 108

Slide 108 text

@skentphd 2008 iPhone 2010 iPad 2012 iPhone 5; iPad mini; AutoLayout 2014 iPhone 6; iPhone 6 Plus 2015 UIStackView iOS

Slide 109

Slide 109 text

@skentphd 2009 Android 1.0 (internal) Android

Slide 110

Slide 110 text

@skentphd 2009 Android 1.0 (internal) 2009 AbsoluteLayout replaced by LinearLayout, RelativeLayout Android

Slide 111

Slide 111 text

@skentphd 2009 Android 1.0 (internal) 2009 AbsoluteLayout replaced by LinearLayout, RelativeLayout 2009 Android 1.6 (public) Android

Slide 112

Slide 112 text

@skentphd 2009 Android 1.0 (internal) 2009 AbsoluteLayout replaced by LinearLayout, RelativeLayout 2009 Android 1.6 (public) 2017 ConstraintLayout released Android

Slide 113

Slide 113 text

@skentphd iOS vs Android Similarities: • Precise → adaptive • General, performant option • Specialized, maintainable option(s)

Slide 114

Slide 114 text

@skentphd AutoLayout vs ConstraintLayout Similarities: • Powerful • Design via GUI • Based on Cassowary algorithm • Simple slick animations

Slide 115

Slide 115 text

@skentphd AutoLayout vs ConstraintLayout Differences: • Default behaviors • Fallback handling

Slide 116

Slide 116 text

@skentphd UIStackView vs LinearLayout Similarities: • Maintainable • Orientation control • Graceful dynamic behaviors

Slide 117

Slide 117 text

@skentphd UIStackView vs LinearLayout Differences: • Weighting

Slide 118

Slide 118 text

@skentphd Use AutoLayout* or ConstraintLayout when #PerfMatters. Use UIStackView or LinearLayout when #SanityMatters.

Slide 119

Slide 119 text

@skentphd

Slide 120

Slide 120 text

@skentphd

Slide 121

Slide 121 text

@skentphd

Slide 122

Slide 122 text

@skentphd

Slide 123

Slide 123 text

@skentphd Resources & Tactics

Slide 124

Slide 124 text

@skentphd Resources • http://nilhcem.com/swift-is-like-kotlin/

Slide 125

Slide 125 text

@skentphd Resources • http://nilhcem.com/swift-is-like-kotlin/ • https://kotlinlang.org/docs/tutorials/getting-started.html

Slide 126

Slide 126 text

@skentphd Resources • http://nilhcem.com/swift-is-like-kotlin/ • https://kotlinlang.org/docs/tutorials/getting-started.html • https://kotlinlang.org/docs/tutorials/koans.html

Slide 127

Slide 127 text

@skentphd Resources • http://nilhcem.com/swift-is-like-kotlin/ • https://kotlinlang.org/docs/tutorials/getting-started.html • https://kotlinlang.org/docs/tutorials/koans.html • https://kotlinlang.org/docs/tutorials/kotlin-android.html

Slide 128

Slide 128 text

@skentphd Tactics • Ride the wave

Slide 129

Slide 129 text

@skentphd Tactics • Ride the wave • Read Android PRs; ask questions

Slide 130

Slide 130 text

@skentphd Tactics • Ride the wave • Read Android PRs; ask questions • Build a small Android app

Slide 131

Slide 131 text

@skentphd Tactics • Ride the wave • Read Android PRs; ask questions • Build a small Android app • Use IntelliJ's auto-conversion to turn Java/ Android code samples into Kotlin.

Slide 132

Slide 132 text

@skentphd Q&A