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

MVVM as a good (anti)pattern in iOS - Mobilizat...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

MVVM as a good (anti)pattern in iOS - Mobilization IX 2019

Nowadays there are a lot of materials, presentations & talks about how great MVVM architecture is. The hype about the view models looks like a never-ending story. There are a lot of developers who like this approach and use it in their own projects. I tried to go against the grain and show a different perspective of MVVM.

Avatar for Mateusz Szklarek

Mateusz Szklarek

October 26, 2019

More Decks by Mateusz Szklarek

Other Decks in Programming

Transcript

  1. Agenda 1. Short explanation WHY? 2. MVVM - where it

    comes from? 3. MVVM in i OS World 4. Alternative solutions 5. Conclusions 6. Q & A @SzklarekMateusz Mobilization IX 2019 3
  2. How many of you have unit tests for view models?

    @SzklarekMateusz Mobilization IX 2019 6
  3. How many of you write tests before real implementation (TDD)?

    @SzklarekMateusz Mobilization IX 2019 8
  4. The thing which motivates me to do this talk was...

    @SzklarekMateusz Mobilization IX 2019 13
  5. Swift code 4 including mine 3 years ago, 4 in

    other companies, @SzklarekMateusz Mobilization IX 2019 15
  6. Swift code 4 including mine 3 years ago, 4 in

    other companies, 4 open source projects available on github, @SzklarekMateusz Mobilization IX 2019 15
  7. Swift code 4 including mine 3 years ago, 4 in

    other companies, 4 open source projects available on github, 4 tons of articles which promote MVVM. @SzklarekMateusz Mobilization IX 2019 15
  8. Invented by Microsoft architects Ken Cooper & Ted Peters to

    simplify event-driven programming of user interfaces. @SzklarekMateusz Mobilization IX 2019 20
  9. Introduced for the first time in 2005 by John Gossman

    - one of Microsoft's WPF1 and Silverlight architects. 1 Windows Presentation Foundation @SzklarekMateusz Mobilization IX 2019 21
  10. Windows Presentation Foundation is a graphical subsystem created by Microsoft

    for rendering user interfaces in Windows-based applications. Released as part of .NET Framework 3.0 in 2006. @SzklarekMateusz Mobilization IX 2019 22
  11. public class CustomerViewModel { private Customer obj = new Customer();

    public string TxtCustomerName { get { return obj.CustomerName; } set { obj.CustomerName = value; } } public string TxtAmount { get { return Convert.ToString(obj.Amount) ; } set { obj.Amount = Convert.ToDouble(value); } } ... } @SzklarekMateusz Mobilization IX 2019 24
  12. public class CustomerViewModel { ... public string LblAmountColor { get

    { if (obj.Amount > 2000) { return "Blue"; } else if (obj.Amount > 1500) { return "Red"; } return "Yellow"; } } public bool IsMarried { get { if (obj.Married == "Married") { return true; } else { return false; } } } } @SzklarekMateusz Mobilization IX 2019 25
  13. private void DisplayUi(CustomerViewModel customer) { lblName.Content = customer.TxtCustomerName; lblAmount.Content =

    customer.TxtAmount; BrushConverter brushconv = new BrushConverter(); lblBuyingHabits.Background = brushconv.ConvertFromString(customer.LblAmountColor) as SolidColorBrush; chkMarried.IsChecked = customer.IsMarried; } @SzklarekMateusz Mobilization IX 2019 26
  14. Gossman introduced MVVM as a standardized way to leverage core

    features of WPF to simplify the creation of user interfaces. In that sense, I consider MVVM to be a tailor-made for the WPF and Silverlight platforms. -- Josh Smith (Microsoft), February 2009 @SzklarekMateusz Mobilization IX 2019 31
  15. View model is an abstract object between a model object

    and a view controller, which performs any data transformation necssary for presentation, as well as sometimes networking and database access, might do a little form validation and any other tasks you feel like throwing in there, and acts as a jack-of-all-trades object originally intended to transfer weight away from your controllers but ultimately creates a new kitchen sink for you to dump responsibilities into. Soroush Khanlou @SzklarekMateusz Mobilization IX 2019 36
  16. class SavedListViewModel { var title: String // MARK: Child view

    models var itemViewModels: [SavedListItemViewModel] {} var loadMoreViewModel: FetchMoreViewModel? {} // MARK: Loading var isLoading: Bool {} var isPerformingAction: Bool {} // MARK: Actions func present(details: PropertyDTO) {...} func itemLeftButtonAction(itemViewModel: SavedListItemViewModel) {...} func itemRightButtonAction(itemViewModel: SavedListItemViewModel) {...} // MARK: Networking func reloadItems() {} func loadMoreItems() {} } @SzklarekMateusz Mobilization IX 2019 37
  17. class SavedListViewModel { var title: String // MARK: Child view

    models var itemViewModels: [SavedListItemViewModel] {} var loadMoreViewModel: FetchMoreViewModel? {} // MARK: Loading var isLoading: Bool {} var isPerformingAction: Bool {} // MARK: Actions func present(details: PropertyDTO) {...} func itemLeftButtonAction(itemViewModel: SavedListItemViewModel) {...} func itemRightButtonAction(itemViewModel: SavedListItemViewModel) {...} // MARK: Networking func reloadItems() {} func loadMoreItems() {} } @SzklarekMateusz Mobilization IX 2019 37
  18. class EventsViewModel { var lastEvent: Observable<Event?> var buttonsState: Observable<ButtonsState> var

    tableViewState: Observable<AppContentState> var isReminderAllowed: Bool var isEventOutdated: Bool var attendanceState: Observable<AttendanceState> var notificationState: Observable<NotificationState> var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var eventsListRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var previousEvents: Observable<[Event?]?> var eventDetailsRefresh: Observable<Void>() var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var notificationPermissionsNotGranted: Observable<Void>() weak var delegate: EventsControllerDelegate? } @SzklarekMateusz Mobilization IX 2019 38
  19. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  20. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  21. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  22. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  23. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  24. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  25. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  26. class EventsViewModel { var buttonsState: Observable<ButtonsState> var tableViewState: Observable<AppContentState> var

    attendanceState: Observable<AttendanceState> var isReminderAllowed: Bool var isEventOutdated: Bool var facebookAlert: Observable<String?> var loginScreen: Observable<Void> var summaryCellDidTap: Observable<Void> var locationCellDidTap: Observable<Void> var lectureCellDidTap: Observable<Int> var speakerCellDidTap: Observable<Int> var eventsListRefresh: Observable<Void> var eventDetailsRefresh: Observable<Void> var previousEventsCellDidSet: Observable<Void> var previousEvents: Observable<[Event?]?> var previousEventsViewModel: Observable<PreviousEventsListTableViewCellViewModel?> var carouselEventPhotosViewModel: Observable<CarouselEventPhotosTableViewCellViewModel?> var lastEvent: Observable<Event?> weak var delegate: EventsControllerDelegate? var notificationState: Observable<NotificationState> var notificationPermissionsNotGranted: Observable<Void> } @SzklarekMateusz Mobilization IX 2019 40
  27. class SavedListViewModel { var title: String // MARK: Child view

    models var itemViewModels: [SavedListItemViewModel] {} var loadMoreViewModel: FetchMoreViewModel? {} // MARK: Loading var isLoading: Bool {} var isPerformingAction: Bool {} // MARK: Actions func present(details: PropertyDTO) {...} func itemLeftButtonAction(itemViewModel: SavedListItemViewModel) {...} func itemRightButtonAction(itemViewModel: SavedListItemViewModel) {...} // MARK: Networking func reloadItems() {} func loadMoreItems() {} } @SzklarekMateusz Mobilization IX 2019 44
  28. Implementation (numbers) 4 ViewController - 115 lines 4 bindings 190

    lines @SzklarekMateusz Mobilization IX 2019 45
  29. Implementation (numbers) 4 ViewController - 115 lines 4 bindings 190

    lines 4 ViewModel 280 lines @SzklarekMateusz Mobilization IX 2019 45
  30. "It's NOT a mistake to make a mistake but it

    is a mistake to repeat a mistake." @SzklarekMateusz Mobilization IX 2019 46
  31. View model is a model for a view, isn't it?

    @SzklarekMateusz Mobilization IX 2019 48
  32. View model is a “model for the view”. This is

    a dumb object (usually a struct in Swift) that is passed to a view to populate its subviews. Soroush Khanlou @SzklarekMateusz Mobilization IX 2019 50
  33. struct SavedListViewModel { let title: String let itemViewModels: [SavedListItemViewModel] let

    loadMoreViewModel: FetchMoreViewModel? let isLoading: Bool {} let isPerformingAction: Bool {} } @SzklarekMateusz Mobilization IX 2019 51
  34. struct ActivityViewModel { let id: Int let time: String let

    title: NSAttributedString let icon: UIImage let details: [ActivityDetailViewModel] let isSeparatorVisible: Bool } @SzklarekMateusz Mobilization IX 2019 52
  35. struct SavedListViewModel { let title: String let itemViewModels: [SavedListItemViewModel] let

    loadMoreViewModel: FetchMoreViewModel? let isLoading: Bool {} let isPerformingAction: Bool {} } @SzklarekMateusz Mobilization IX 2019 53
  36. // MARK: Actions func present(details: PropertyDTO) {...} func itemLeftButtonAction(itemViewModel: SavedListItemViewModel)

    {...} func itemRightButtonAction(itemViewModel: SavedListItemViewModel) {...} func reloadItems() {} func loadMoreItems() {} @SzklarekMateusz Mobilization IX 2019 55
  37. Real life example app 4 235k lines of code (85k

    production code & 150k tests) @SzklarekMateusz Mobilization IX 2019 62
  38. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files @SzklarekMateusz Mobilization IX 2019 62
  39. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files 4 8200 unit tests @SzklarekMateusz Mobilization IX 2019 62
  40. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files 4 8200 unit tests 4 204 UIViewControllers @SzklarekMateusz Mobilization IX 2019 62
  41. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files 4 8200 unit tests 4 204 UIViewControllers 4 average file length 37.23 lines @SzklarekMateusz Mobilization IX 2019 62
  42. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files 4 8200 unit tests 4 204 UIViewControllers 4 average file length 37.23 lines 4 code coverage 99.2% @SzklarekMateusz Mobilization IX 2019 62
  43. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files 4 8200 unit tests 4 204 UIViewControllers 4 average file length 37.23 lines 4 code coverage 99.2% 4 file > 150 lines does not compile (only 88 files > 100 lines) @SzklarekMateusz Mobilization IX 2019 62
  44. Real life example app 4 235k lines of code (85k

    production code & 150k tests) 4 6092 swift files 4 8200 unit tests 4 204 UIViewControllers 4 average file length 37.23 lines 4 code coverage 99.2% 4 file > 150 lines does not compile (only 88 files > 100 lines) 4 8 frameworks @SzklarekMateusz Mobilization IX 2019 62
  45. Split a view model 4 Services for network calls 4

    Data providers @SzklarekMateusz Mobilization IX 2019 65
  46. Split a view model 4 Services for network calls 4

    Data providers 4 Notification handlers @SzklarekMateusz Mobilization IX 2019 65
  47. Split a view model 4 Services for network calls 4

    Data providers 4 Notification handlers 4 Database repositories @SzklarekMateusz Mobilization IX 2019 65
  48. Split a view model 4 Services for network calls 4

    Data providers 4 Notification handlers 4 Database repositories 4 and many child controllers @SzklarekMateusz Mobilization IX 2019 65
  49. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let

    item = items[indexPath.row] let controller = DetailViewController(item: item) navigationController?.pushViewController(controller, animated: true) } @SzklarekMateusz Mobilization IX 2019 68
  50. public class ItemViewModel { let id: Int let title: String

    var buttonTapped: (() -> Void)? func presentAlert(on controller: UIViewController) { let alert = UIAlertController(title: "Confirmation", message: "Do you want to buy \(title)?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: "YES", style: .default, handler: { [weak self] in self?.buyProduct(id: id) })) controller.present(alert, animated: true, completion: nil) } func buyProduct(id: Int) { // TODO: Network request } } @SzklarekMateusz Mobilization IX 2019 69
  51. public class DashboardController: UIViewController { public init(childrenFactory: DashboardChildrenFactoryProtocol) { self.childrenFactory

    = childrenFactory super.init(nibName: nil, bundle: nil) } ... // MARK: - Child controllers private lazy var headerController = childrenFactory.makeHeaderController() private lazy var detailsController = childrenFactory.makedetailsController() private lazy var availableItemsController = childrenFactory.makeAvailableItemsController() private lazy var orderController = childrenFactory.makeOrderController() private lazy var showMoreButtonController = childrenFactory.makeReferralButtonController() ... } @SzklarekMateusz Mobilization IX 2019 76
  52. public class DashboardController: UIViewController { ... private var childControllers: [DashboardChildControllerType]

    { return [headerController, detailsController, availableItemsController, orderController, showMoreButtonController] } // MARK: - Lifecycle public override func viewDidLoad() { super.viewDidLoad() embedChildControllers() } // MARK: - Private private let childrenFactory: DashboardChildrenFactoryProtocol private func embedChildControllers() { childControllers.forEach { controller in embed(childViewController: controller, using: dashboardView.stackView.addArrangedSubview) } } } @SzklarekMateusz Mobilization IX 2019 77
  53. Many View Controllers? Why not put it inside a view?

    @SzklarekMateusz Mobilization IX 2019 78
  54. class DetailsView: UIView { let container: UIStackView = Subviews.container let

    verticalContainer: UIStackView = Subviews.verticalContainer let titleView: TitleView = TitleView() let separator: UIView = Subviews.separator let infoView: InfoView = InfoView() let icon: UIImageView = Views.imageView(contentMode: .scaleAspectFit) init() { super.init(frame: .zero) addSubviews() setUpLayout() } // MARK: - Subviews private func addSubviews() { // Add subviews } // MARK: - Layout private func setUpLayout() { // Set up constraints } // MARK: - Required initializer required init?(coder _: NSCoder) { nil } } @SzklarekMateusz Mobilization IX 2019 80
  55. class DetailsView: UIView { let container: UIStackView = Subviews.container let

    verticalContainer: UIStackView = Subviews.verticalContainer let titleView: TitleView = TitleView() let separator: UIView = Subviews.separator let infoView: InfoView = InfoView() let icon: UIImageView = Views.imageView(contentMode: .scaleAspectFit) init() { super.init(frame: .zero) addSubviews() setUpLayout() apiService.getDetails { details in ... // present details } } ... @SzklarekMateusz Mobilization IX 2019 81
  56. class DetailsView: UIView { let container: UIStackView = Subviews.container let

    verticalContainer: UIStackView = Subviews.verticalContainer let titleView: TitleView = TitleView() let separator: UIView = Subviews.separator let infoView: InfoView = InfoView() let icon: UIImageView = Views.imageView(contentMode: .scaleAspectFit) init() { super.init(frame: .zero) addSubviews() setUpLayout() apiService.getDetails { details in ... // present details } } ... @SzklarekMateusz Mobilization IX 2019 83
  57. "Extracting logic from VC to VM is nothing else than

    moving the Junk to a Different Drawer" @SzklarekMateusz Mobilization IX 2019 88
  58. class ListController: UIViewController, UITableViewDataSource { ... // MARK: UITableViewDataSource override

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let model = viewModel[indexPath.row] let cell = tableView.dequeueReusableCell(SomeCell.self, forIndexPath: indexPath) cell.model = model return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = viewModel[indexPath.row] openDetails(model: model) } ... } @SzklarekMateusz Mobilization IX 2019 89
  59. extension ListController: UITableViewDataSource { override func tableView(_ tableView: UITableView, cellForRowAt

    indexPath: IndexPath) -> UITableViewCell { let model = viewModel[indexPath.row] let cell = tableView.dequeueReusableCell(SomeCell.self, forIndexPath: indexPath) cell.model = model return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = viewModel[indexPath.row] openDetails(model: model) } } @SzklarekMateusz Mobilization IX 2019 90
  60. 4 John: "We do MVVM because it's really easy to

    test our view models" @SzklarekMateusz Mobilization IX 2019 92
  61. 4 John: "We do MVVM because it's really easy to

    test our view models" 4 Me: Ok, so please show me the tests @SzklarekMateusz Mobilization IX 2019 92
  62. 4 John: "We do MVVM because it's really easy to

    test our view models" 4 Me: Ok, so please show me the tests 4 John: We don't have them :D @SzklarekMateusz Mobilization IX 2019 92
  63. 4 John: "We do MVVM because it's really easy to

    test our view models" 4 Me: Ok, so please show me the tests 4 John: We don't have them :D 4 Me: Why? @SzklarekMateusz Mobilization IX 2019 92
  64. 4 John: "We do MVVM because it's really easy to

    test our view models" 4 Me: Ok, so please show me the tests 4 John: We don't have them :D 4 Me: Why? 4 John: Because logic is quite simple @SzklarekMateusz Mobilization IX 2019 92
  65. 4 John: "We do MVVM because it's really easy to

    test our view models" 4 Me: Ok, so please show me the tests 4 John: We don't have them :D 4 Me: Why? 4 John: Because logic is quite simple 4 Me: But you said that you made view models to extract business logic from your view controllers because it was too complex, am I right? @SzklarekMateusz Mobilization IX 2019 92
  66. 4 John: "We do MVVM because it's really easy to

    test our view models" 4 Me: Ok, so please show me the tests 4 John: We don't have them :D 4 Me: Why? 4 John: Because logic is quite simple 4 Me: But you said that you made view models to extract business logic from your view controllers because it was too complex, am I right? 4 John: Yes, it's true and it is easy to test @SzklarekMateusz Mobilization IX 2019 92
  67. 3. "Because it's so popular and it's a perfect solution

    with RxSwift" @SzklarekMateusz Mobilization IX 2019 95
  68. 4 nice way to keep the state of the view

    (simple struct) @SzklarekMateusz Mobilization IX 2019 98
  69. struct BookViewModel { let id: String let name: String let

    author: String let coverImage: UIImage } @SzklarekMateusz Mobilization IX 2019 99
  70. Break SRP 4 Loading data from multiple APIs / endpoints

    @SzklarekMateusz Mobilization IX 2019 101
  71. Break SRP 4 Loading data from multiple APIs / endpoints

    4 Filtering data @SzklarekMateusz Mobilization IX 2019 101
  72. Break SRP 4 Loading data from multiple APIs / endpoints

    4 Filtering data 4 logic for formatting strings (composed from several other values) @SzklarekMateusz Mobilization IX 2019 101
  73. Break SRP 4 Loading data from multiple APIs / endpoints

    4 Filtering data 4 logic for formatting strings (composed from several other values) 4 Being data sources and delegates for UITableViews / UICollectionViews @SzklarekMateusz Mobilization IX 2019 101
  74. Break SRP 4 Loading data from multiple APIs / endpoints

    4 Filtering data 4 logic for formatting strings (composed from several other values) 4 Being data sources and delegates for UITableViews / UICollectionViews 4 Saving or persisting data @SzklarekMateusz Mobilization IX 2019 101
  75. 4 Break SRP 4 Poor reusability 4 Poor testability 4

    without bindings it's still more code to write @SzklarekMateusz Mobilization IX 2019 104
  76. 3 things to remember 4 architecture & design patterns are

    to solve problem not to create them @SzklarekMateusz Mobilization IX 2019 105
  77. 3 things to remember 4 architecture & design patterns are

    to solve problem not to create them 4 don't put network calls, logic and different actions to your view model - use your view model as a simple struct @SzklarekMateusz Mobilization IX 2019 105
  78. 3 things to remember 4 architecture & design patterns are

    to solve problem not to create them 4 don't put network calls, logic and different actions to your view model - use your view model as a simple struct 4 don't repeat mistakes from your previous projects @SzklarekMateusz Mobilization IX 2019 105