'Is it common to have only one ViewModel to manage all CoreData entities - Core Data + MVVM + SwiftUI

Is it common to have only one ViewModel to manage all CoreData entities?

For instance, in the following example, I have three Core Data entities, Car, CarService and ServiceRecord where Car has many carServices and each CarService has many serviceRecords. Everything is working fine but I feel like my CarViewModel file is growing and growing and I'm not sure if this is really a good MVVM practice.

As you can see in the following example I'm using CarViewModel to fetch data from Core Data and passing it around SwiftUI views. Again, everything is working fine but I feel like I'm missing something.

Can someone please share how you usually structure your code when using MVVM + CoreData + SwiftUI. Do you handle everything from one ViewModel as shown below or do you usually have a ViewModel for each entity? If a viewModel per each entity is the best option, what method do you use to pass viewModels around SwiftUI views?

CoreDataManager

class CoreDataManager{

    static let instance = CoreDataManager()

    lazy var context: NSManagedObjectContext = {
        return container.viewContext
    }()
    
    lazy var container: NSPersistentContainer = {
        return setupContainer()
    }()
    
    func setupContainer()->NSPersistentContainer{
        // code to setup container...
        return container
    }
    
    func save(){
        do{
            try context.save()
        }catch let error{
            print("Error saving Core Data. \(error.localizedDescription)")
        }
    }
}

CarViewModel

class CarViewModel: ObservableObject{

    let manager: CoreDataManager
    
    @Published var cars: [Car] = []
    @Published var carServices: [CarService] = []
    @Published var serviceRecords: [ServiceRecord] = []
    
    init(coreDataManager: CoreDataManager = .instance){
        self.manager = coreDataManager
        // getCars() etc.
    }

    // CREATIONS
    func addCar(name:String){}
    func addService(name:String, cost: Double){}
    func createRecord(name:String, cost: Double){}
    
    // DELETES
    func deleteCar(){}
    func deleteCarService(){}
    func deleteServiceRecord(){}
    
    // UPDATES
    func updateCar(){}
    func updateService(){}

    // GETS
    func getCars(){}
    func getServices(){}
    func getRecords(){}
    
    func save(){
        self.manager.save()
    }
}

SwiftUI Views

CarsView

struct CarsView: View {
    @StateObject var carViewModel = CarViewModel()
    var body: some View {
        NavigationView{
            VStack{
                List {
                    ForEach(carViewModel.cars) { car in
                    }
                }
            }  
        }
    }
}

ServicesView

struct ServicesView: View {
    @ObservedObject var carViewModel:CarViewModel
    var body: some View {
        NavigationView{
            VStack{
                List {
                    ForEach(carViewModel.carServices) { service in
                    }
                }
            }  
        }
    }
}

RecordsView

struct RecordsView: View {
    @ObservedObject var carViewModel: CarViewModel
    var body: some View {
        NavigationView{
            VStack{
                List {
                    ForEach(carViewModel.serviceRecords) { record in
                    }
                }
            }  
        }
    }
}


Solution 1:[1]

Personally I would create a service file which holds all the functions to a given model. I would then only expose the functions in my viewModel that my viewController needs.

For example:

CarService.swift

class CarService {
    func addCar(name:String) {}
    func addService(name:String, cost: Double) {}
    func createRecord(name:String, cost: Double) {}

    // DELETES
    func deleteCar() {}
    func deleteCarService() {}
    func deleteServiceRecord() {}

    // UPDATES
    func updateCar() {}
    func updateService() {}

    // GETS
    func getCars() {}
    func getServices() {}
    func getRecords() {}
}

CarViewModel.swift

protocol CarViewModelType {

    func addCar(name: String)
    func deleteCar()

}

class CarViewModel: CarViewModelType {

    var carService: CarService

    init(service: CarService) {
        self.carService = service
    }

    func addCar(name: String) {
        self.carService.addCar(name: name)
    }

    func deleteCar() {
        self.carService.deleteCar()
    }
}

CarViewController.swift

class CarViewController: UIViewController {

    var viewModel: CarViewModelType!

}

This is of course just one way to go about it, I believe there is no 'right' way to structure your code.

Hope it helps

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1