Alireza Moradi
Alireza Moradi

Reputation: 328

how to publish the data of a network request using Combine framework and SwiftUI

I'm developing an iOS app using swiftUI and Combine framework and also MVVM. I'm want to handle the login API request in a separate class called LoginService, which is used in the LoginViewModel. Now I want to know how should I publish and observe the attributes between view and ViewModel. I mean ViewModel is an ObservableObject and is being observed in the View, But since I'm handling the network request in a Service class how should LoginService notify LoginViewModel and LoginView that the data is received and the View should be updated?

import Foundation
import Combine

class LoginViewModel: ObservableObject {

    @Published var user = UserModel()

    @Published var LoginStatus: Bool = false

    @Published var LoginMessage: String = ""

    var service = LoginService()

    func Login(With email: String, And password: String) -> Bool {
        service.validateLogin(email: email, password: password)
        return false
    }
}

This is the Code for LoginViewModel. How should LoginService change the values for LoginStatus, LoginMessage and user when data is received from the server to notify the View? I'm saying this because as far as I know you can Observe the ObservableObjects only in the View(SwiftUI).

Upvotes: 4

Views: 1365

Answers (2)

Jim lai
Jim lai

Reputation: 1409

You've established a one-way binding, i.e; model -> view

Now it doesn't matter how your service updates model, whenever model changes, view updates.

There are several ways for your service to update model.

  1. Usual URLSession, update LoginStatus and LoginMessage in callback as usual.

  2. Via Combine publishers, e.g.; URLSessionDataTaskPublisher, and do update in its sink closure.

Upvotes: 0

krjw
krjw

Reputation: 4450

Okay so I take your example and I would do the following:

I assumed your service returns true or false

import Foundation
import Combine

class LoginViewModel: ObservableObject {

    @Published var LoginStatus: Bool = false

    @Published var LoginMessage: String = ""

    var service = LoginService()

    func Login(_ email: String, _ password: String) -> Bool {
        self.LoginStatus  = service.validateLogin(email: email, password: password)
        return self.LoginStatus
    }
}

In your view:


import SwiftUI

struct ContentView : View {

    @ObservedObject var model = LoginViewModel()

    var body: some View {
        VStack {
            Button(action: {
                _ = self.model.Login("TestUser", "TestPassword")
            }, label: {
                Text("Login")
            })
            Text(self.model.LoginStatus ? "Logged In" : "Not Logged in")
        }
    }
}

It should be something around that.

I removed UserModel because you shouldn't nest models.

I hope this helps.

EDIT 1:

To validate something automatically you can use onApear() on your View Or listen to changes with olnrecieve() to update the UI or the State

import SwiftUI

struct ContentView : View {

    @ObservedObject var model = LoginViewModel()

    var body: some View {
        VStack {
            Button(action: {
                _ = self.model.Login("TestUser", "TestPassword")
            }, label: {
                Text("Login")
            })
            Text(self.model.LoginStatus ? "Logged In" : "Not Logged in")
        }.onAppear {
            // call a function that gets something from your server 
            // and modifies your state
            self.model.validate()
        }.onReceive(self.model.$LoginMessage, perform: { message in
               // here you can update your state or your ui
               // according the LoginMessage... this gets called
               // whenever LoginMessage changes in your model
        })
    }
}

Upvotes: 1

Related Questions