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

Kotlin MVVM: Making iOS and Android apps as sim...

QuickBird
February 09, 2018

Kotlin MVVM: Making iOS and Android apps as similar as possible

In this presentation from 2018, we present our functional approach to MVVM on Android and iOS. In summary, we try to reduce overhead by making the ViewModels on iOS and Android as similar as possible. In order to achieve that, we remove all SDK dependencies from the ViewModels until it's pure Kotlin/Swift Code.

QuickBird

February 09, 2018
Tweet

More Decks by QuickBird

Other Decks in Programming

Transcript

  1. 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
  2. Malte Bucksch 5 TEAM Mixing business logic and view logic

    Testable app architecture Beautiful native UI Awesome app Sharing logic between Android and iOS
  3. “Model View Controller (MVC) is one of the most quoted

    (and most misquoted) patterns around” – Martin Fowler (martinfowler.com) Malte Bucksch 7
  4. MVVM: Model-View-ViewModel Malte Bucksch 10 View View Model Model Presentation

    User Input Business Logic Data User action Update Update Notify
  5. 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
  6. 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 } }
  7. 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() }
  8. Shared Kotlin/Swift Features • Optionals • String Interpolation • Extension

    functions • Functional operations for lists/arrays • Type inference • Lambdas • Type alias • … Malte Bucksch 29
  9. 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
  10. 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."
  11. 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"
  12. 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 } }
  13. 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>() …
  14. 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 }
  15. ViewModel Tests Malte Bucksch 39 @Test fun testTranslation() { val

    viewModel: TranslatorViewModel = TranslatorViewModelImpl() viewModel.input.englishText.onNext("Dog") viewModel.output.germanText.test().assertValue("Hund") }
  16. Local unit tests for UI testing Malte Bucksch 40 Fast

    local unit tests Avoid UI testing frameworks
  17. 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
  18. 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
  19. Wrap up Improvement points • Optimize project workflow to make

    people reuse code • Write ViewModels in exact same programming language Malte Bucksch 51