Calling Model from another model

Hi CodeCrew,

Got a basic issue but I dont know how to solve it hence asking CodeCrew… Thanks :slight_smile:

Use case is that I have a func in a model that I want to run in the background. This func needs to access data from another model… Can you help how to do this properly or why published variable of a model is not getting updated if I access it from another model?

To do this, I create an instance of the model() I need to access in another model.

Example below: TestModel2() needs access to TestModel()

class TestModel2 : ObservableObject {
    init() {}
    
    func printTestModel1() {
        let testModel1 = TestModel()
        
        print("testModel.testModel.element:\(testModel1.test.element)")
    }
}

class TestModel : ObservableObject {
    @Published var test: Test = Test()
    
    init() {}
    
    func printTestModel() {
        print("testModel.testModel.element:\(self.test.element)")
    }
    
    func setElement(element: String) {
        DispatchQueue.main.async {
            self.test.element = element
        }        
    }
}

Whilst I can access the function of TestModel() from TestModel2(), the published property in TestModel() is blank.

To test this, I have the view code below… if I tap on the button “TestModel1: printValue”, value is printed out. If I tap on the button “TestModel2: print element in TestModel1”, nothing is printed.

Here is the output from my console

testModel.testModel.element:123
testModel.testModel.element:
struct TestModelView: View {
    @EnvironmentObject var testModel1: TestModel
    @EnvironmentObject var testModel2: TestModel2
    
    @State var stateElement: String = ""
    
    var body: some View {
        VStack (alignment: .leading) {
            TextField("SomeValue", text: $stateElement)
            
            Button {
                testModel1.setElement(element: stateElement)
            } label: {
                Text("TestModel1: Set element")
            }
            
            Button {
                testModel1.printTestModel()
            } label: {
                Text("TestModel1: printValue")
            }
            
            Button {
                testModel2.printTestModel1()
            } label: {
                Text("TestModel2: print element in TestModel1")
            }

        }
        .padding(10)
    }
}

Super interesting problem. I’ve spent some time thinking about this too, you can check it out right here:

I’m guessing both of your ViewModels are injected somewhere in the view hierarchy as environment objects.

The thing is, your TestModel2 does not have access to your TestModel1 at the moment.

This method in TestModel2 is instantiating a new TestModel1. NOT accessing the TestModel1 instance you injected somewhere in the view hierarchy.

Thanks for getting back to me…

Good point… It looks like I’m creating a new instance rather that using the old one… Yes I do have them as env objects and I can access both models without problem under a view… The problem I have is that I need to access a model inside another model…

My use case for this is for my Upload Queue Flow… Basically my upload button adds the upload item into the queue… When runUpload task is triggered, it goes through the queue and upload the items one by one in the background. The upload queue needs to access several models (e.g. “Upload images to cloud which is handled my a different model”, “Update CoreData which is another model”, so forth and so on…)… App notifies the user when upload is done via a notification (DB update via a listener)…

Can you suggest how to solve this? Is there anyway to access model contents like published variables inside another model without creating a new instance of it?

Or is there a better way to deal with with background task which needed access to different models properties and functions?

The best way to always have one instance of your CoreData model is to set is as a StateObject in the parent View and then add that object as an .environmentObject(). eg: Lets say you define it in the @main view (where your struct that conforms to App exists).

So inside that struct do something like this

struct MyProjectApp: App {
    @StateObject var coreDataViewModel = CoreDataViewModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .enviromentObject(coreDataViewModel)
        }
    }
}

That will mean that there is only ever one instance of your CoreData model as long as in each View you reference it like this (for example):

@EnvironmentObject var cdModel: CoreDataViewModel