M1X
M1X

Reputation: 5354

SwiftUI where to load data in MVVM

I have a view named Requests and this has a view model called RequestViewModel. This view model takes as parameter a model of type Request and then it provides several methods to the view. The problem is that I need to load these requests first from firebase but I do not know where.

Is view model a good place to fetch data because I have this feeling that it is not? I have similar situations like this for other view models; for ex PackageViewModel and more...

Should I load this data somewhere else for ex in a Service and then pass it to this view model or there are other ways to do this?

class RequestViewModel: ObservableObject {
    @Published var request: Request
    @Published var requests: [Request] = []

    init(request: Request) {
        self.request = request
    }

    func loadRequests() -> Void {
        network call here {
            self.requests = requests
        }
    } 
}

Upvotes: 0

Views: 2801

Answers (2)

Jim lai
Jim lai

Reputation: 1409

Use a dedicated network service.

Search your feelings, you know it to be true.

The problem is, it goes against everything you think you know about MVVM.

Most MVVM devs use view model as a convenient wrapper for networking requests (which have side effects), business logic, and view controls.

The idea behind it is to refactor out view-binding model and associated logic/effects.

And as with all design patterns, some devs would abuse it, twist it, copy and paste it from java ... etc.

As time goes by, devs began to take ViewModel for granted, since it's a norm in Android, it must work in iOS, right?

Any dev worth their salt would ask these questions when they try to port MVVM to SwiftUI:

  • Is there a binding mechanism in SwiftUI? How does it work?

  • struct Model: View {...}, is this a model-view mapping? If so I may not need to declare a separate ViewModel? (pro only question)

  • Whoever conforms to ObservableObject can be observed (to trigger view update), this goes beyond the narrow definition of view model (view-related properties). An ObservableObject can be a service or shared resource. E.g.; A networking service. Congratulations, you just took the first step into a much larger world.

  • Why would I introduce a reference type view model to augment a value type model view for every such value type that conforms to View in a SDK built around value type? Swift is not java?

Now we have enough setup to properly answer your question.

  1. Create a dedicated networking service, e.g.;

    final class Resource: ObservableObject { // don't even need inheritance, OOP isn't always the best solution in all situations 
        @Published var requests = [Requests]()
        // publish other resources
        func loadRequests() {
            /* update requests in callback */
        }
    }
    
  2. use it, e.g.;

    struct Model: View {
        @State var model = MyModel() // can be "view model", because it maps to view 
        @EnvironmentObject var res: Resource
        // or as an alternative, @ObservedObject var res = Resource()
        var body: some View {
            // access the automatic binding provided by SwiftUI, e.g.; res.requests
            // call res.loadRequests() to mutate internal state of a reference type
            // note that stateful part of the "view model" is refactored out 
            // so that `model` can be value type, yet it provides binding via @State
            // instead of ObservableObject, which has to be reference type
            // this is the brilliance of SwiftUI, it moves heaven and earth so you have 
            // the safety of immutability from value type, **unlike MVVM**
        }  
    }  
    
  3. Done.

  4. Why do we need ViewModel again? Let me know in the comments.

Upvotes: 4

youjin
youjin

Reputation: 2549

It really depends. Are you using the data for just this one View? Or are you using it throughout the app?

Data to be used for just this view

Fetching data from the ViewModel is fine. Checkout this repo for an example.

Be aware that data stored an ObservableObject doesn't persist. So you'd have to store the ViewModel as a @State in the parent view. Look here.

Data to be persisted for the whole app

Then I suggest having some sort of "store", loaded into your views with .environmentObject(). Checkout this repo for an example.

Upvotes: 1

Related Questions