johnpatrickmorgan / FlowStacks

FlowStacks allows you to hoist SwiftUI navigation and presentation state into a Coordinator
MIT License
783 stars 56 forks source link

Using viewmodel pattern how should I pass the routes to other viewmodels? #37

Closed samus closed 2 years ago

samus commented 2 years ago

I'm using the coordinator with viewmodel approach. Right now I have the following setup.

enum SearchScreen {
  case home(SearchViewModel)
  case details(Int)
}
class SearchCoordinatorViewModel {
  @Published var routes: Routes<SearchScreen>
  init() {
    routes = [.root(.home(SearchViewModel()))]
  }
  func showDetails(id: Int) {
    routes.push(.details(id))
  }
}
class SearchViewModel {
  func showDetails(id: Int) {
    // Not sure how to accomplish this
  }
}

I would like to trigger the routing to a search result from the search screen. However I can't pass the routes publisher to the SearchViewModel because self isn't viable at the time that the root route is being created. I can't pass a closure that uses a weak self to SearchViewModel either for the same reason. Am I just thinking about it incorrectly? The only solutions that I've come up with is to put the routes in another object (Router), initialize routes with an empty array, and then give that object to the SearchViewModel. I'm not very fond of it which is why I'm asking here. Thanks.

johnpatrickmorgan commented 2 years ago

Thanks for your question @samus ! I tend to pass closures between view models rather than passing a reference to the routes themselves. But you're right that it can still be awkward when you want to pass a closure to a child view model before the coordinator is fully initialised. I think the easiest workaround is to initialise the coordinators routes to an empty array. After that, the coordinator will be fully initialised and you should be able to create your child view model passing a closure with a weak self reference. E.g.:

init() {
  self.routes = []

  self.routes = [
    SearchViewModel(selectionHandler: { [weak self] selectedItem in 
      self?.routes.push(.detail(selectedItem))
    }
  ]
}

Or perhaps it's nicer to initialise the routes array as part of its declaration:

@Published var routes: Routes<SearchScreen> = []

init() {
  self.routes = [
    SearchViewModel(selectionHandler: { [weak self] selectedItem in 
      self?.routes.push(.detail(selectedItem))
    }
  ]
}

I'm afk for a few days so can't check this example is correct, but hopefully this helps.

samus commented 2 years ago

Thanks for the quick response, I ended up settling on a similar idea.