I watched a lot of Krzysztof Zablocki’s videos on MVVM and realized there’s only one way to understand something new: Build a project!

After reading a lot about software architecture, I’ve been studying the MVVM protocol model for the last 6 months. To understand this protocol you need to refer to an article by Natasha The Robot that covers everything you need to know about programming protocols. If you can’t hear what I’m saying, I suggest you read Natasha The Robot.

I finished Steve “Scotty” Scott’s course on MVVM-C a month ago. In one of the best videos I’ve seen this year, it’s not the reduction in code that matters, but how the architecture improves our software. I have no problem with people calling a technology a “silver bullet”, but I prefer to pursue the extreme and find the best solution.

In recent weeks, I’ve been thinking a lot about how to improve my understanding of the MVVM architecture and create a maintainable development framework. So I watched Krzysztof Zabłocki’s video on software architecture, and it was amazing. If you want to see what’s going on you can watch the video here or you can watch the blog here.

After watching Krzysztof Zablocki’s video I decided to build a project to implement a better architecture. So I set clear goals for this architecture.

The total goal

Before choosing an architecture, I make a list of the goals that the architecture is focused on solving, which I have learned from my years of Java project development. This helped me define the strengths of our architecture. Here’s what prompted me to take the test.

The module

I wanted my architecture to create modules that had strong code usability. You can also create structures that can be reused throughout the project, and you can use a method to create a flexible interface so that the project is extensible.

All right, let’s test it

Unit testing and user interface testing, I don’t need to explain that. But I focused on the layering of the architecture, which allows QA analysts to come up with new testing mechanisms to ensure the (high) quality of the application in order to automate testing (for better deployment).

A/B test (in simple terms, it is to make two plans for the same goal, let some users use plan A, the other part of the users use plan B, record the use of users, to see which plan is more consistent with the design)

As applications in the app market become increasingly complex based on different interfaces and functions, there are many ways to define what is good and what is not. Here I focus on the ability of applications to customize and simulate user experiences.

MVVM and flow controller

With this concept in mind, I decided to use the MVVM write interface entirely to create a clear distinction. Add the necessary dependencies. Manage these dependencies and decide which interfaces to use will be flow controllers.

Flow controller

A flow controller is a collection of small classes and structures that control a user’s path. This enables us to create different data flows for A/B testing, for example, permission management. Streams communicate through a common object that can pass window references or navigation controllers, which allows you to create navigation for different streams.

Another important feature of this Model is that it is responsible for instantiating and injecting ViewModel + Model into the ViewController. This helps code reuse more in dependency injection. In this case, it’s worth taking a look at Swift’s generics, although it still has some problems.

MVVM

This architecture is very similar to the architecture of my previous projects, except that the VC (ViewController) must accept a compatible ViewModel (by established protocol). VC is therefore independent and fully packaged, and it is important to facilitate testing and improve code reuse.

This independence means that I can use this controller whenever I want to make the interface flexible. Another example is abstracting similar interfaces, such as grids and lists using the same ViewModel. The abstractions are certainly more complex, but as your application grows or changes over time, so does your revenue.

I’m talking about ways to keep an application going, and improving a finished product is just as important as building the first release. More details can be found in this article: medium.com/@digoreis/y…

After explaining the reasons for the architectural tests in the text below, I will illustrate the preliminary results.

Practical project

I decided to create a simple project, with a list and details. To understand and prove another important point I’m going to test, you can’t use dependencies without CocoaPods.

One of the things I’ve noticed over time is that we’ve all realized that the build times for developing applications are long because of compilation issues in the main steps of the project. You may only see partial details when you first evaluate, but the truth is that a lot of time is wasted waiting for Xcode to translate and organize projects.

digoreis/ExampleMVVMFlow _ExampleMVVMFlow – One Example of MVVM w/ Flow Controller_github.com

Storyboard

I don’t agree that what a Storyboard takes away in Xcode is bad. Instead, it is the consequences of not using it that are worth worrying about. I’ll consider not using it in the next project, which is just an XML representation of native code. In a mature team with increasing project complexity and build time, I think everyone should be thinking about this.

But please don’t argue!

challenge

The first stage of the challenge is simple, display them as a list of items and select one to display the details. I believe that this is the most common task for developing applications. Here is a simple list of owls with names, photos and descriptions. The display of this content is configured through the FlowController enumeration.

I won’t talk too much about how messy what I decided to build was, because I was testing my abstraction limits in a very short time (8 hours) and was now perfecting the code rather than adding to the project. In the next section, I’ll talk about the results of the experiment.

The results of

The first step is to remove Storyboards (the ones that start the screen on the left) and other things that you won’t use. Then start the system flow only when the application starts.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        window = UIWindow(frame : UIScreen.mainScreen().bounds)
        let configure = FlowConfigure(window: window, navigationController: nil, parent: nil)
        let mainFlow = MainFlowController(configure: configure)
        mainFlow.start()

        return true
    }
}
Copy the code

After providing the application window to the process system, it launches a system that looks like the tree shown below. To use navigation, I want to keep UINavigationController so you can start the stream from UIWindow or UINavigationController.

Basic scheme on MVVM and flow controller

When a stream is initialized, it builds a ViewModel and Model (more if needed), starts the method that creates the necessary interface, and adds its dependencies. This requires that the code coupling between these entities be more advantageous. We can see that in the OwlsFlowController case, the configuration choice whether to display data in a grid or a list is fixed in this case, but it can be tested in two ways.

import UIKit class OwlsFlowController : FlowController, GridViewControllerDelegate, ListTableViewControllerDelegate { private let showType = ShowType.List private let configure : FlowConfigure private let model = OwlModel() private let viewModel : ListViewModel required init(configure : FlowConfigure) { self.configure = configure viewModel = ListViewModel(model: model) } func start() { switch showType { case .List: let configureTable = ConfigureTable(styleTable: .Plain, title: "List of Owls",delegate: self) let viewController = ListTableViewController(viewModel: viewModel, configure: configureTable) { owl, cell in cell.textLabel? .text = owl.name } configure.navigationController?.pushViewController(viewController, animated: false) break case .Grid: let layoutGrid = UICollectionViewFlowLayout() layoutGrid.scrollDirection = .Vertical let configureGrid = ConfigureGrid(viewLayout: layoutGrid, title: "Grid of Owls", delegate: self) let viewController = GridViewController(configure: configureGrid) { owl, cell in cell.image?.image = owl.avatar } viewController.configure(viewModel:viewModel) configure.navigationController?.pushViewController(viewController, animated: false) break } } private enum ShowType { case List case Grid } func openDetail(id : Int) { let detail = FlowConfigure(window: nil, navigationController: configure.navigationController, parent: self) let childFlow = OwlDetailFlowController(configure: detail,item: viewModel.item(ofIndex: id)) childFlow.start() } }Copy the code

The beauty of this model is that most lists in the application share the same behavior and the same interface. In this case, only data and subunit changes can be passed as a parameter, creating a reusable code for all the lists.

The interesting point here is that two response protocols are implemented: one for the grid and one for the list. But the implementation is the same. This is interesting because I have separate operations for each type of interface, but common operations can be shared without inheritance.

import UIKit

struct ConfigureTable {
    let styleTable : UITableViewStyle
    let title : String
    let delegate : ListTableViewControllerDelegate
}

protocol ListTableViewControllerDelegate {
    func openDetail(id : Int)
}

class ListTableViewController: UITableViewController {

    var viewModel : ListViewModel
    var populateCell : (M.Model,UITableViewCell) -> (Void)
    var configure : ConfigureTable

    init(viewModel model : ListViewModel, configure : ConfigureTable, populateCell : (M.Model,UITableViewCell) -> (Void)) {
        self.viewModel = model
        self.populateCell = populateCell
        self.configure = configure
        super.init(style: configure.styleTable)
        self.title = configure.title
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.count()
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
        populateCell(viewModel.item(ofIndex: indexPath.row), cell)
        return cell
    }

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        configure.delegate.openDetail(indexPath.row)
    }

}
Copy the code

The implementation of the interface is clear and clean, and it is an objective infrastructure with simple parameter presentation. All creation and deletion are not implemented.

The other thing is to fill the subunit’s closed channel, which in the near future will allow us to use a parameter to decide which phone to use. The idea of this architecture is to divide the interface into two parts, with the first part being a set of off-the-shelf infrastructure and a reusable entire project.

The second part UIViews and subunits are customized for each case and for each data set. Therefore, our usual tests can cover most interfaces, increasing the implementation of security.

Note: For some reason, in some cases, Swift will not accept a generic type as a protocol parameter to an init method. Whether this is a Swift bug or a deliberate restriction is still being investigated.

The result is very clean code and maximizes interface reuse. We also looked at generics and protocols as an abstract problem. Other results are significantly faster build times.

These are the preliminary results of the last few weeks, and there are other results THAT I expect to see in other articles. If they want to follow it on Github or edit it on Medium, I’ll post it.

Next things to do and thanks.

Things to do:

  • Testing: Unit testing and mock interface testing (I started with 78% coverage)
  • Extended model: Other objects (I need to find other animals)
  • Interfaces and Infrastructure: Create other types of units using the same UIViewController

My next article will be on how to set up effective tests that are easy to maintain. Wish me luck.

Special thanks:

The idea for the hawk came from my wife in the first place. She likes owls. I also need you to thank HootSuite for this cool series of images.

I try to source all the code I cite, so please forgive me if I’ve missed anyone.

I can’t forget to thank Mikail Freitas for helping me identify generic protocol initialization errors. We never understand why it works well in one case and doesn’t in another.