We use the architectural pattern MVVM to align our Android and iOS as much as possible to share logic between them. We use platform-independent ViewModels that can be used on both iOS and Android. Therefore the only thing that differs between the platforms is the view.
4. Agenda
I. The problem we’re solving
II. Comparison between architecture patterns
III. Our approach with MVVM
IV. How Kotlin helps us
V. Testing with MVVM
VI. Reusability of ViewModels
Malte Bucksch 4
5. Malte Bucksch 5
TEAM
Mixing business logic
and view logic
Testable
app architecture
Beautiful native UI
Awesome app Sharing logic between
Android and iOS
24. ViewModel Interface
24
interface TranslatorViewModelInput {
val englishText: BehaviorSubject<String>
val saveTrigger: PublishSubject<Unit>
}
interface TranslatorViewModelOutput {
val germanText: Observable<String>
val isSavingAllowed: Observable<Boolean>
val savedGermanTranslation: Observable<String>
}
interface TranslatorViewModel {
val input: TranslatorViewModelInput
val output: TranslatorViewModelOutput
}
class TranslatorViewModelImpl :
TranslatorViewModel,
TranslatorViewModelInput,
TranslatorViewModelOutput {
override val input = this
override val output = this
…
Usage
val viewModel: TranslatorViewModel =
createTranslatorViewModel()
viewModel.germanText
viewModel.englishText
viewModel.output.germanText
viewModel.input.englishText
25. ViewModel Implementation
Malte Bucksch 25
class TranslatorViewModelImpl : TranslatorViewModel, TranslatorViewModelInput, TranslatorViewModelOutput {
override val input = this
override val output = this
// *** inputs ***
override val englishText = BehaviorSubject.createDefault("")
override val saveTrigger = PublishSubject.create<Unit>()
// *** outputs ***
override val germanText = input.englishText
.map { TranslatorEngine.translateToGerman(it) }
override val isSavingAllowed = input.englishText
.map { !it.isEmpty() }
override val savedGermanTranslation =
input.saveTrigger.withLatestFrom(germanText)
.map { (_, german) -> german }
}
26. Binding to ViewModels via Kotlin Fragment
Malte Bucksch 26
// *** supply inputs ***
viewModel.input.englishText.receiveTextChangesFrom(englishInputEditText)
viewModel.input.saveTrigger.receiveClicksFrom(saveTextButton)
// *** subscribe to outputs ***
viewModel.output.germanText
.subscribe { germanOutputTextView.text = it }
viewModel.output.isSavingAllowed
.subscribe { saveTextButton.isEnabled = it }
viewModel.output.savedGermanTranslation
.subscribe { germanText ->
showMessage("Saved to clipboard")
germanText.saveToClipboard()
}
29. Shared Kotlin/Swift Features
• Optionals
• String Interpolation
• Extension functions
• Functional operations for lists/arrays
• Type inference
• Lambdas
• Type alias
• …
Malte Bucksch 29
30. Kotlin vs. Swift: Variables and constants
Malte Bucksch 30
var myVariable = 42
myVariable = 50
let myConstant = 42
var myVariable = 42
myVariable = 50
val myConstant = 42
31. Kotlin vs. Swift: String interpolation
Malte Bucksch 31
let apples = 3
let oranges = 5
let fruitSummary = "I have (apples + oranges) " + "pieces of fruit."
val apples = 3
val oranges = 5
val fruitSummary = "I have ${apples + oranges} " + "pieces of fruit."
32. Kotlin vs. Swift: Maps
Malte Bucksch 32
val occupations = mutableMapOf( "Malcolm" to "Captain",
"Kaylee" to "Mechanic")
occupations["Jayne"] = "Public Relations"
var occupations = [ "Malcolm": "Captain",
"Kaylee": "Mechanic"]
occupations["Jayne"] = "Public Relations"
33. Kotlin vs Swift ViewModel
Malte Bucksch 33
interface TranslatorViewModelInput {
val englishText: BehaviorSubject<String>
val saveTrigger: PublishSubject<Unit>
}
interface TranslatorViewModelOutput {
val germanText: Observable<String>
val isSavingAllowed: Observable<Boolean>
val savedGermanTranslation: Observable<String>
}
interface TranslatorViewModel {
val input: TranslatorViewModelInput
val output: TranslatorViewModelOutput
}
protocol TranslatorViewModelInput {
var englishText: BehaviorSubject<String> { get }
var saveTrigger: PublishSubject<Void> { get }
}
protocol TranslatorViewModelOutput {
var germanText: Observable<String> { get }
var isSavingAllowed: Observable<Bool> { get }
var saveGermanTranslation: Observable<String> { get }
}
protocol TranslatorViewModel {
var input: TranslatorViewModelInput { get }
var output: TranslatorViewModelOutput { get }
}
34. Kotlin vs Swift ViewModel
Malte Bucksch 34
class TranslatorViewModelImpl:
TranslatorViewModel,
TranslatorViewModelInput,
TranslatorViewModelOutput {
var input: TranslatorViewModelInput { return self }
var output: TranslatorViewModelOutput { return self }
// MARK: - Inputs
var englishText = BehaviourSubject<String>(value: "")
var saveTrigger = PublishSubject<Void>()
…
class TranslatorViewModelImpl:
TranslatorViewModel,
TranslatorViewModelInput,
TranslatorViewModelOutput {
override val input = this
override val output = this
// *** inputs ***
override val englishText =
BehaviorSubject.createDefault("")
override val saveTrigger =
PublishSubject.create<Unit>()
…
35. Kotlin vs Swift ViewModel
Malte Bucksch 35
…
// MARK: - Outputs
lazy var germanText: Observable<String> = {
input.englishText
.map { TranslatorEngine.translateToGerman($0) }
}()
lazy var isSavingAllowed: Observable<Bool> = {
input.englishText
.map { $0.isEmpty }
}()
…
// *** outputs ***
override val germanText = input.englishText
.map { TranslatorEngine.translateToGerman(it) }
override val isSavingAllowed = input.englishText
.map { !it.isEmpty() }
override val savedGermanTranslation =
input.saveTrigger.withLatestFrom(germanText)
.map { (_, german) -> german }
49. Wrap up
Advantages
• Logic can be reused within iOS and Android
• Functional way of writing UI with isolated side effects
• Great testability
Malte Bucksch 49
50. Wrap up
Advantages
• Logic can be reused within iOS and Android
• Functional way of writing UI with isolated side effects
• Great testability
Disadvantages
• Boiler plate for interfaces
• Steep learning curve
• Less intuitive if programmer is not used to functional-style programming
Malte Bucksch 50
51. Wrap up
Improvement points
• Optimize project workflow to make people reuse code
• Write ViewModels in exact same programming language
Malte Bucksch 51
52. Questions?
More resources on MVVM
https://github.com/quickbirdstudios
https://github.com/kickstarter/android-oss
https://github.com/kickstarter/ios-oss
https://bit.ly/2qSFPqO (Talk at UI Konf about Swift/Kotlin similarity)
malte@quickbirdstudios.com
Malte Bucksch 52