Passing data between ViewModels in MVVM-C

1.8k views Asked by At

I am using MVVM with Coordinator to design an application. One thing that i am having doubts on is on how to pass data between different ViewModels. Normally the previous viewModel would just create the next viewModel and would just do a method dependency injection in prepareforsegue. However now that i am responsible for all the navigation how do i achieve this ?

Class AppCoordinator : NSObject, Coordinator, UINavigationControllerDelegate {

    var childCoordinators = [Coordinator]()
    var navigationController: UINavigationController
    var dependencyContainer : MainDependencyContainer
    
    func start() {
        let vc = ViewController.instantiate()
        vc.coordinator = self
        vc.viewModel = dependencyContainer.makeMainViewModel()
        navigationController.delegate = self
        navigationController.pushViewController(vc, animated: true)
    }

    func createAccount() {
        let vc = CreateAccountViewController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }

}

I could ofcourse create the ViewModel for CreateAccountViewController in MainViewModel and pass the ViewModel as a paramter in createAccount method but is it the right way to do it here ? What will be the unit testing implications here ?

1

There are 1 answers

0
Ben On

Ideally, you don't want both ViewModels to interact with each other and keep both elements separated.

One way to deal with it is to pass through the minimum data required for the navigation.

class AppCoordinator : NSObject, Coordinator, UINavigationControllerDelegate {

    var childCoordinators = [Coordinator]()
    var navigationController: UINavigationController
    var dependencyContainer : MainDependencyContainer

    func start() {
        let vc = ViewController.instantiate()
        vc.coordinator = self
        let viewModel = dependencyContainer.makeMainViewModel()

        // for specific events from viewModel, define next navigation
        viewModel.performAction = { [weak self] essentialData in
            guard let strongSelf = self else { return }
            strongSelf.showAccount(essentialData)
        }

        vc.viewModel = viewModel
        navigationController.delegate = self
        navigationController.pushViewController(vc, animated: true)
    }

    // we can go further in our flow if we need to
    func showAccount(_ data: AnyObject) {
        let vc = CreateAccountViewController.instantiate()
        vc.viewModel = CreateAccountViewController(with: data)
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }
}

Going further, you can create a specific Coordinator for CreateAccountViewController that will get initialized with those data. The start() method will do create whatever is needed for its ViewController.

// we can go further in our flow if we need to
func showAccount(_ data: AnyObject) {
    let coordinator = CreateAccountCoordinator(data: data, navigationController: navigationController)
    coordinator.start()
    childCoordinators.append(coordinator)
}

In this last example, the coordinator is only responsible to build its view and pass through essential information to next coordinator whenever needed. The viewModel is only exposed to its view, and eventually the view is unaware of both. It could be a good alternative in your case.

Finally, you can test using a protocol abstraction to make sure performAction triggers showAccount, that showAccount create a child coordinator, and so on.