Quantcast
Channel: Combine: Asynchronous Programming with Swift - Kodeco Forums
Viewing all articles
Browse latest Browse all 26

Child view-models state management with SwiftUI and Combine

$
0
0

This is more of an opinion/architecture question, with questions that arose out of chapter 15 from the Combine + SwiftUI book.

I have a rather larger screen that I am building with SwiftUI and Combine. I am using MVVM with an ObservableObject object acting as the VM. The main view itself is composed of smaller child views, each of which have their own “dummy” view models (that are currently not ObservableObject but rather simply structs) that take in the model and format the data for the component. I have a few questions and I would be curious to hear the community’s thoughts:

1. Where should the child view models be stored?

Currently, I expose the child view models as @Published within my main view model (let’s call it CheckoutViewModel). I then have a Combine publisher inside CheckoutViewModel that does a few things and updates the VMs:

class CheckoutViewModel: ObservableObject, Identifiable {
    
    // MARK: - Publishers
    
    @Published var detailsViewModel: DetailsViewModel?
    @Published var optionsListViewModel: OptionsListViewModel?
    // etc...
    
    // MARK: - Private Members

    private var subscriptions = Set<AnyCancellable>()    
    private let repository: CheckoutRepository // DI'd

    // MARK: - Actions

    func load(productId: String) {
       repository.fetchData(forProductId: productId)
            .sink(
                receiveCompletion: { _ in },
                receiveValue: { product in
                   self.detailsViewModel = DetailsViewModel(fromProduct: product)
                   self.optionsListViewModel = OptionsListViewModel(fromProduct: product)
                }
            )
            .store(in: &subscriptions)
    }
}

Within my CheckoutView, I then call load and have the child VMs passed to the child views:

struct CheckoutView: View {
   @ObservedObject var viewModel: CheckoutViewModel

   var body: some View {
      VStack {
         if let detailsViewModel = viewModel.detailsViewModel {
            ProductDetails(viewModel: detailsViewModel)
         }

         if let optionsListViewModel = viewModel.optionsListViewModel {
            OptionsList(viewModel: optionsListViewModel)
         }
         
         // ... and so on
      }
   }
}

I am not sure if I love this approach. Something feels odd about publishing these child view models like this.

Should each of these child view models be observable objects on their own that then communicate to the parent view model?

As you can see, once you bring in user interactions (i.e. a user can select an option in the OptionsList), my current approach may fall somewhat short or become unmanageable the larger the view gets.

This leads me to my second question.

2. Who should own the state? How should user interactions be facilitated?

As you can imagine, state in this case becomes very important, and with something like a checkout screen, there may be a lot of state to track. This is where I am totally lost currently. With this approach, I know I can store state directly in CheckoutViewModel, but it may just end up becoming too large and cumbersome to work with.

To deal with user interactions, I’ve tried passing down bindings directly to the child views and go from there, but that circumvents the child view models and “clutters” CheckoutViewModel with a bunch of state variables (that seems like they should be part of a model, not the view model).

Another approach was using closures in the child views, i.e. if the user selects a new option from OptionsList, call a closure with the option ID and then call the main view model from the view to update the state - but that seemed super cumbersome and I kept thinking there must be a better way to do it.

Maybe some sort of store or similar would be a good idea? I am trying to get an idea of best practices for how child views/child VMs should communicate with their parent.

Conclusion

As you can probably tell, I am a bit lost about proper state management and how to deal with child views using MVVM in SwiftUI. Any guidance or general ideas/suggestions would be greatly appreciated. Is using MVVM in SwiftUI maybe a bad idea in general? As anyone else done something similar, or am I on the completely wrong path here?

2 posts - 2 participants

Read full topic


Viewing all articles
Browse latest Browse all 26

Trending Articles