6. Assess state of a codebase
• Is your app green field app?
• How current codebase looks like?
• How big is the team?
7. Architecture requirements
• Easy to learn
• Highly testable - Supports refactoring
• Single responsibility of components
• Causes excitement
• Applicable to legacy system
8. MVP+?
• Variation of MVP
• Highly testable
• Dependency injection of components
• Applicable for both platforms
• Simple definition of your app structure
14. Presenter
class TODOListPresenter {
weak var view: TODOListView?
func didLoad() {
let todos = self.todoListRepository.getAllTODOs()
self.view?.showTODOs(todo: todos)
}
}
15. Presenter Test
class TODOListPresenterTest: XCTestCase {
var mockRepository: MockTodoRepo!
var presenter: TODOListPresenter!
override func setUp() {
self.mockRepository = MockTodoRepo()
self.presenter = TODOListPresenter(repository: mockRepository)
}
func testDidLoad() {
XCTAssertFalse(verify(MockTodoRepo.getAllTODOs))
presenter.didLoad()
XCTAssertTrue(verify(MockTodoRepo.getAllTODOs))
}
}
16. Presenter - View Test
class TODOListPresenterTest: XCTestCase {
var mockRepository: MockTodoRepo!
var presenter: TODOListPresenter!
var view: MockTodoView!
override func setUp() {
self.mockRepository = MockTodoRepo()
self.presenter = TODOListPresenter(repository: mockRepository)
self.view = MockTodoView()
self.presenter.view = self.view
}
func testDidLoad() {
presenter.didLoad()
XCTAssert(verify(MockTodoRepo.getAllTODOs))
XCTAssertTrue(verify(MockTodoView.showTODOs))
}
}
17. Assembly
• Wires together presenters, views, navigators, …
• Creates other assemblies
• Acts as a factory for view controllers
• Assembly tree defines your whole app
18. Assembly
class TODOAssembly {
var navigationController: UINavigationController
var todoRepository: TODORepository
init(navigationController: UINavigationController, todoRepository: TODORepository) {
self.navigationController = navigationController
self.todoRepository = todoRepository
}
var todoViewController: TODOListViewController {
let todoPresenter = TODOListPresenter(repository: todoRepository)
let vc = TODOListViewController(presenter: todoPresenter)
return vc
}
}
19.
20. Assembly tree
class LoginAssembly {
// ...
var loginViewController: LoginViewController {
return LoginViewController()
}
}
class TrafficAssembly {
// ...
var trafficViewController: TrafficViewController {
return UIViewController()
}
}
class CoreAssembly {
lazy var loginAssembly = LoginAssembly(self)
lazy var trafficAssembly = TrafficAssembly(self)
var rootViewController: UIViewController {
return trafficAssembly.trafficViewController
}
}
21. Assembly example #2
let coreAssembly = CoreAssembly()
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions: …) -> Bool
{
self.window?.rootViewController = coreAssembly.rootViewController
return true
}
22. Assembly Example #2
final class CoreAssembly {
var navigationController: UINavigationController
var appStateRepository: AppStateRepository
lazy var tourAssembly = TourAssembly(self.navigationController)
lazy var todoAssembly = TODOAssembly(self.navigationController)
init(navigationController: UINavigationController,
appStateRepository: AppStateRepository) {
self.navigationController = navigationController
self.appStateRepository = appStateRepository
}
var rootViewController: UIViewController {
if appStateRepository.isNewInstall {
return tourAssembly.tourViewController
} else {
return todoAssembly.todoViewController
}
}
}
23. Assembly Test
class RootAssemblyTest: XCTestCase {
func testRootViewControllerTour() {
let mockAppStateRepo = MockStateRepository(isNewInstall: true)
let assembly = RootAssembly(navigationController: UINavigationController(),
appStateRepository: mockAppStateRepo)
XCTAssert(type(of: assembly.rootViewController) == TourViewController.self)
}
func testRootViewControllerTODO() {
let mockAppStateRepo = MockStateRepository(isNewInstall: false)
let assembly = RootAssembly(navigationController: UINavigationController(),
appStateRepository: mockAppStateRepo)
XCTAssert(type(of: assembly.rootViewController) == TODOListViewController.self)
}
}
30. Problem with state
• Managing state can be hard
• State representation often leads to confusion
• Good state representation can improves
readability
31. View State
protocol TODODetailView: class {
func showTodo(todo: TODOItemDetail)
func showError(error: Error)
func showLoading()
}
class TODODetailViewController: UIViewController {
var isLoading: Bool = false
var error: Error? = nil
var data: TODOItemDetail?
// …
32. View State
enum ViewState<T> {
case initial
case loading
case data(T)
case error(Error)
}
protocol TODODetailsStateView: class {
var state: ViewState<TODOItemDetail> { get set }
}
class TODODetailViewController: UIViewController {
var state: ViewState<TODOItemDetail> = .initial {
didSet {
// ...
33. Simple view models
• Simple data model
• No logic
• Just holds values to be rendered
34. View Models
struct TodoTableViewCellViewModel {
var formattedDate: String
var content: String
}
class TodoTableViewCell: UITableViewCell {
var viewModel: TodoTableViewCellViewModel? {
didSet {
guard let viewModel = viewModel else { return }
date.text = viewModel.formattedDate
content.text = viewModel.content
}
}
@IBOutlet weak var date: UILabel!
@IBOutlet weak var content: UILabel!
}
35. Enable gradual refactoring
• Apply change gradually
• Isolate modules and surround them by tests
• Easy to add presenter to any view controller
• Obj-C rewrite
36. Code sharing among apps
• Start with an interface
• Assembly can be used in any architecture
37. Take aways
• There is no silver bullet
• Don’t build thing you don’t need today
• Uncertainty is only certain thing
• You write code for people, not computers.
• Good test is better than outdated documentation