0

I built an MVVM architecture to support my app which is supposed to control the pipeline between the frontend and my firebase database. Initially, I successfully implemented the entire work by coding totally in the frontend, but there are lots of bugs when I encapsulated them into a function.

For example, the next sheet will be presented when the currently presented sheet gets dismissed. Sometimes I needed to wait for a long time until the app is unfrozen. Even worse, the app crashed down when I clicked the button.

I heard that nested models don't work yet if SwiftUI is in use (reference). However, I just cannot come up with a better solution if my classes are untested.

// This is Model
import Foundation
import SwiftUI

struct userModel {
    var uid = UUID()
    var name = ""
    var bio = ""
    var interest = ""
    var level = 1
    var xp = 0
    var email = ""
    var image: Data = Data(count: 0)
    
    init() {
        
    }
    
    init(_ name:String, _ xp: Int) {
        self.name = name
        self.xp = xp
        self.level = self.xp2Level(xp: xp)
    }
    
    func xp2Level(xp:Int) -> Int {
        if xp < 9500 {
            return xp / 500 + 1
        }
        else if xp < 29500 {
            return (xp - 9500) / 1000 + 1
        }
        else {
            return (xp - 29500) / 2000 + 1
        }
    }
}
// This is ViewModel
import Foundation
import SwiftUI
import Firebase

class userViewModel: ObservableObject {
    @Published var user: userModel = userModel()
    
    @Published var isLoading = false
    @AppStorage("status") var status = false
    private var ref = Firestore.firestore()
    private let store = Storage.storage().reference()
    var picker = false
    
    func updateXP(completion: @escaping () -> Int) -> Int {
        guard let uid = Auth.auth().currentUser?.uid else {
            return 0
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let xp = doc.get("xp") as? Int {
                    self.user.xp = xp
                }
            }
        }
        return completion()
    }
    
    func updateLevel(completion: @escaping () -> Int) -> Int {
        guard let uid = Auth.auth().currentUser?.uid else {
            return 1
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let level = doc.get("level") as? Int {
                    self.user.level = level
                }
            }
        }
        return completion()
    }
    
    func updateName (completion: @escaping () -> String) -> String {
        guard let uid = Auth.auth().currentUser?.uid else {
            return ""
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let name = doc.get("username") as? String {
                    self.user.name = name
                }
            }
        }
        return completion()
    }
    
    func updateBio (completion: @escaping () -> String) -> String {
        guard let uid = Auth.auth().currentUser?.uid else {
            return ""
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let bio = doc.get("bio") as? String {
                    self.user.bio = bio
                }
            }
        }
        return completion()
    }
    
    func updateInterest (completion: @escaping () -> String) -> String {
        guard let uid = Auth.auth().currentUser?.uid else {
            return ""
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let interest = doc.get("interest") as? String {
                    self.user.interest = interest
                }
            }
        }
        return completion()
    }
    
    func updatePhoto (completion: @escaping () -> Data) -> Data {
        guard let uid = Auth.auth().currentUser?.uid else {
            return Data(count: 0)
        }
        
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if snapshot != nil {
                    let imageRef = store.child("profile_Photos").child(uid)
                    imageRef.getData(maxSize: 1000 * 64 * 64, completion: { (data, error) in
                        if let error = error {
                            print("Encountered error: \(error) when getting image")
                            self.user.image = Data(count: 0)
                        } else if let data = data,
                                  !data.isEmpty{
//                            self.currentUser.image = Image(uiImage: UIImage(data: data)!).resizable()
                            self.user.image = data
                        } else {
//                            self.currentUser.image = Image(systemName: "person").resizable()
                            self.user.image = Data(count: 0)
                        }
                      })
                } else if let error = error {
                    print(error)
                }
            }
        }
        return completion()
    }
    
    public func getXP() -> Int{
        updateXP {
            return (self.user.xp) as Int
        }
    }
    
    public func getLevel() -> Int {
        updateLevel(completion: {
            return (self.user.level) as Int
        })
    }
    
    public func getName() -> String {
        updateName(completion: {
            return (self.user.name) as String
        })
    }
    
    public func getBio() -> String {
        updateBio(completion: {
            return (self.user.bio) as String
        })
    }
    
    public func getInterest() -> String {
        updateInterest(completion: {
            return (self.user.interest) as String
        })
    }
    
    public func getPhoto() -> Data {
        updatePhoto(completion: {
            return (self.user.image) as Data
        })
    }
    
    func updatePersonalInfo() {
        //sending user data to Firebase
        let uid = Auth.auth().currentUser?.uid
        
        isLoading = true
        self.uploadImage(imageData: self.getPhoto(), path: "profile_Photos") { (url) in
            self.ref.collection("Users").document(uid ?? "").setData([
                            "uid": uid ?? "",
                            "imageurl": url,
                            "username": self.user.name,
                            "bio": self.user.bio,
                            "interest" : self.user.interest
                        ], merge: true) { (err) in
                         
                            if err != nil{
                                self.isLoading = false
                                return
                            }
                            self.isLoading = false
                            // success means settings status as true...
                            self.status = true
                        }
            
        }
    }
    
    func increaseXPnLV() {
        //sending user data to Firebase
        let uid = Auth.auth().currentUser!.uid
        let docRef = ref.collection("Users").document(uid)
        docRef.getDocument { (document, error) in
            if let document = document, document.exists {
                docRef.updateData(["xp": FieldValue.increment(Int64(50))])
                // update level
                let xp = document.data()!["xp"] as! Int
                docRef.updateData(["level": self.user.xp2Level(xp: xp)])
            } else {
                print("Document does not exist")
            }
        }
    }
    
    func uploadImage(imageData: Data, path: String, completion: @escaping (String) -> ()){
        
        let storage = Storage.storage().reference()
        let uid = Auth.auth().currentUser?.uid
        
        storage.child(path).child(uid ?? "").putData(imageData, metadata: nil) { (_, err) in
            print("imageData: \(imageData)")
            if err != nil{
                completion("")
                return
                
            }
            // Downloading Url And Sending Back...
            storage.child(path).child(uid ?? "").downloadURL { (url, err) in
                if err != nil{
                    completion("")
                    return
                    
                }
                completion("\(url!)")
            }
        }
    }
}

// This is View

import SwiftUI
import CoreData
import Firebase
import FirebaseFirestore

struct Goals: View {
    let current_user_id = Auth.auth().currentUser?.uid
    @State private var showingAlert = false
    var ref = Firestore.firestore()
    @StateObject var currentUser: userViewModel
    @StateObject var homeData = HomeViewModel()
    @State var txt = ""
    @State var edge = UIApplication.shared.windows.first?.safeAreaInsets
    @FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(key: "date",
                                                                            ascending: true)], animation: .spring()) var results : FetchedResults<Goal>
    
//    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var greeting : String = "Hello"
    @Environment(\.managedObjectContext) var context
    var body: some View {
    ForEach(results){goal in
    Button(action: {
                                            context.delete(goal)
                                            try! context.save()
                                            if current_user_id != nil {
                                                currentUser.updateXPnLV()
                                                self.showingAlert = true
                                            }
                                        }, label: Text("Something")
    )
                                            .alert(isPresented: $showingAlert) {
                                        () -> Alert in
                                        Alert(title: Text("Congratulations!"), message: Text("You completed a goal today, XP+50!"), dismissButton: .default(Text("OK")))
                                            }
    }
}
}

EDIT

Another error I saw is AttributeGraph precondition failure: attribute failed to set an initial value: 805912, ForEachChild<Array<userInfoModel>, ObjectIdentifier, HStack<VStack<HStack<TupleView<(Text, Divider, Text)>>>>>.

Memphis Meng
  • 1,267
  • 2
  • 13
  • 34
  • 1
    Why is `userInfoModel` an `@ObservableObject` if it doesn't have any `@Published` properties? Seems like it should be a `struct` and then it wouldn't be a nested observable either. – jnpdx May 20 '21 at 05:50
  • I noticed that and modified it as well. However, the latency(runtime error) still exists. Looks like it is not the problem of the structure of modules. – Memphis Meng May 20 '21 at 15:31

1 Answers1

1
  1. AppStorage is for use in a View while it may appear to be working all the SwiftUI wrappers with the exception of @Published inside an ObservableObject seem to be unreliable outside of a struct that is a View.

https://developer.apple.com/documentation/swiftui/appstorage

  1. As a standard practice all your class and struct should be capitalized so change class CurrentUserViewModel andclass UserInfoModel

Also, change @StateObject var currentUser: currentUserViewModel to @StateObject var currentUser: CurrentUserViewModel = CurrentUserViewModel()

The initialization is the most important part that is missing.

  1. Everything in the ForEach is just floating it isn't within a variable, function or inside the body. Where is your body?

This is probably what the error is talking about. wrap all of that code in a body

var body: some View {
    //All the code for the ForEach
}
  1. Your Button seems to be missing a title or label

  2. Remove this line

    () -> Alert in

I am sure there are other little things. I suggest you start from scratch in this View and you start putting in code line by line.

Here is a starting point. The Firebase part needs quite a but of work but you should be able to get started by focusing on the code that I commented out and removing the code to mimic a response from Firebase.

All of this is in the FirebaseManager class

Once this is working the rest will work.

The code as is works so you can see it in action with the fake responses

///Keep all the Firebase Code HERE
class FirebaseManager{
    //private var ref = Firestore.firestore()
    //private let store = Storage.storage().reference()
    func retrieveFromDB(collectionName: String, variableName: String, completion:  @escaping (Result<Any, Error>) -> Void) {
        print(#function)
        //This is sample code, likely has errors because I dont have Firebase setup but you can see the logic so you can touch up
        
        //        guard let uid = Auth.auth().currentUser?.uid else {
        //            completion(.failure(FirebaseError.notLoggedIn))
        //            return
        //        }
        // catch the information of the current user
        //        let db = ref.collection(collectionName)
        //        db.addSnapshotListener { [self] (querySnapshot, error) in
        //
        //            if let error = error{
        //                completion(.failure(error))
        //                return
        //            }
        //            guard (querySnapshot?.documents) != nil else {
        //                print("Document is empty")
        //                completion(.failure(FirebaseError.emptyDocument))
        //                return
        //            }
        //            let docRef = db.(document(uid)
        //
        //            docRef.getDocument { (snapshot, error) in
        //                if let error = error{
        //                    completion(.failure(error))
        //                    return
        //                }
        //
        //            completion(.success(snapshot.get(variableName)))
        //            }
        //        }
        //For sample purposes I will mimic response remove this in your actual code
        DispatchQueue.main.async {
            if variableName == "xp" || variableName == "level"{
                completion(.success(Int.random(in: 0...200)))
            }else{
                let strings = ["apple", "orange", "banana", "kiwi", "startfruit"]
                completion(.success(strings.randomElement()!))
            }
        }
    }
    ///For Int variables
    func retrieveFromUsers(variableName: String, completion: @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        retrieveFromDB(collectionName: "Users", variableName: variableName, completion: {result in
            switch result {
            case .success(let value):
                let xp = value as? Int
                if xp != nil{
                    completion(.success(xp!))
                }else{
                    completion(.failure(FirebaseError.wrongType))
                }
                return
            case .failure(let error):
                print(error)
                completion(.failure(error))
            }
        })
    }
    ///For String variables
    func retrieveUserProperty(variableName: String, completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveFromDB(collectionName: "Users", variableName: variableName, completion: {result in
            switch result {
            case .success(let value):
                let username = value as? String
                if username != nil{
                    completion(.success(username!))
                }else{
                    completion(.failure(FirebaseError.wrongType))
                }
                return
            case .failure(let error):
                print(error)
                completion(.failure(error))
            }
        })
    }
    func retrieveXP(completion: @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        retrieveFromUsers(variableName: "xp", completion: completion)
    }
    
    func retrieveLevel(completion: @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        retrieveFromUsers(variableName: "level", completion: completion)
    }
    
    func retrieveName (completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveUserProperty(variableName: "username", completion: completion)
    }
    
    func retrieveBio (completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveUserProperty(variableName: "bio", completion: completion)
    }
    
    func retrieveInterest (completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveUserProperty(variableName: "interest", completion: completion)
    }
    
    //Database code to retrieve Image needs to be added
    
    func updateDB(collectionName: String, variableName: String, incrementBy: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        //sending user data to Firebase
        //        let uid = Auth.auth().currentUser!.uid
        //        let docRef = ref.collection(collectionName).document(uid)
        //        docRef.getDocument { (document, error) in
        //            if let document = document, document.exists {
        //                docRef.updateData([variableName: FieldValue.increment(incrementBy)])
        //let newValue = document.data()![variableName] as! Int
        //                completion(.success(newValue))
        //            } else {
        //                completion(.failure(FirebaseError.documentDoesntExist))
        //            }
        //        }
        //For sample purposes I will mimic response remove this in your actual code
        DispatchQueue.main.async {
            completion(.success(Int.random(in: 0...200) + incrementBy))
            
        }
    }
    
    func updateDB(collectionName: String, variableName: String, value: String, completion:  @escaping (Result<String, Error>) -> Void) {
        print(#function)
        //sending user data to Firebase
        //        let uid = Auth.auth().currentUser!.uid
        //        let docRef = ref.collection(collectionName).document(uid)
        //        docRef.getDocument { (document, error) in
        //            if let document = document, document.exists {
        //                docRef.updateData([variableName: value])
        //let newValue = document.data()![variableName] as! Int
        //                completion(.success(newValue))
        //            } else {
        //                completion(.failure(FirebaseError.documentDoesntExist))
        //            }
        //        }
        //For sample purposes I will mimic response remove this in your actual code
        DispatchQueue.main.async {
            
            let strings = ["apple", "orange", "banana", "kiwi", "startfruit"]
            completion(.success(strings.randomElement()!))
            
        }
    }
    func updateDB(collectionName: String, variableName: String, value: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        //sending user data to Firebase
        //        let uid = Auth.auth().currentUser!.uid
        //        let docRef = ref.collection(collectionName).document(uid)
        //        docRef.getDocument { (document, error) in
        //            if let document = document, document.exists {
        //                docRef.updateData([variableName: value])
        //let newValue = document.data()![variableName] as! Int
        //                completion(.success(newValue))
        //            } else {
        //                completion(.failure(FirebaseError.documentDoesntExist))
        //            }
        //        }
        //For sample purposes I will mimic response
        DispatchQueue.main.async {
            completion(.success(Int.random(in: 0...200)))
            
        }
    }
    func updateUsers(variableName: String, value: String, completion:  @escaping (Result<String, Error>) -> Void) {
        print(#function)
        updateDB(collectionName: "Users", variableName: variableName, value: value, completion: completion)
    }
    func updateUsers(variableName: String, value: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        updateDB(collectionName: "Users", variableName: variableName, value: value, completion: completion)
    }
    func updateUsers(variableName: String, incrementBy: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        updateDB(collectionName: "Users", variableName: variableName, incrementBy: incrementBy, completion: completion)
    }
    
    //Code to update Image will need to be added
}

Here is the rest

import SwiftUI
import CoreData
//Capitalized no other changes here
struct UserModel {
    var uid = UUID()
    var name = ""
    var bio = ""
    var interest = ""
    var level = 1
    var xp = 0
    var email = ""
    var image: Data = Data(count: 0)
    
    init() {
        print(#function)
    }
    
    init(_ name:String, _ xp: Int) {
        print(#function)
        self.name = name
        self.xp = xp
        self.level = self.xp2Level(xp: xp)
    }
    
    func xp2Level(xp:Int) -> Int {
        print(#function)
        if xp < 9500 {
            return xp / 500 + 1
        }
        else if xp < 29500 {
            return (xp - 9500) / 1000 + 1
        }
        else {
            return (xp - 29500) / 2000 + 1
        }
    }
}
//This is to standardize what comes from your Firebase Code. Try to condense code that is duplicated
enum FirebaseError: Error {
    case notLoggedIn
    case emptyDocument
    case wrongType
    case documentDoesntExist
}

Capitalize
class UserViewModel: ObservableObject {
    let alertVM = AlertViewModel.shared
    @Published var user: UserModel = UserModel()
    @Published var isLoading = false
    //AppStorage wont work here
    var status: Bool{
        get{
            UserDefaults.standard.bool(forKey: "status")
        }
        set{
            UserDefaults.standard.set(newValue, forKey: "status")
        }
        
    }
    //Separate all Firebase Code
    let firebaseManager = FirebaseManager()
    
    init() {
        populateAllVariables()
    }
    
    func increaseXPnLV() {
        print(#function)
        //sending xp to Firebase
        firebaseManager.updateUsers(variableName: "xp", incrementBy: 50, completion: {result in
            switch result {
            case .success(let newXp):
                self.user.xp = newXp
                //sending level to Firebase
                self.firebaseManager.updateUsers(variableName: "level", value: self.user.xp2Level(xp: newXp), completion: {result in
                    switch result {
                    case .success(let newLevel):
                        print("newLevel = \(newLevel)")
                        self.user.level = newLevel
                        self.alertVM.scheduleAlert(title: "Congratulations!", message: "You completed a goal today, XP+50!")
                        return
                    case .failure(let error as NSError):
                        //Show alert here
                        self.alertVM.scheduleAlert(error: error)
                        print(error)
                    }
                })
                return
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
                
            }
        })
    }
    func populateAllVariables() {
        print(#function)
        getXP()
        getLevel()
        getName()
        getBio()
        getInterest()
    }
    public func getXP() {
        print(#function)
        firebaseManager.retrieveXP(completion: {result in
            switch result {
            case .success(let xp):
                self.user.xp = xp
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getLevel() {
        print(#function)
        firebaseManager.retrieveLevel(completion: {result in
            switch result {
            case .success(let level):
                self.user.level = level
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getName() {
        print(#function)
        firebaseManager.retrieveName(completion: {result in
            switch result {
            case .success(let name):
                self.user.name = name
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getBio() {
        print(#function)
        firebaseManager.retrieveBio(completion: {result in
            switch result {
            case .success(let bio):
                self.user.bio = bio
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getInterest() {
        print(#function)
        firebaseManager.retrieveInterest(completion: {result in
            switch result {
            case .success(let interest):
                self.user.interest = interest
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    ///This will need work
    //    public func getPhoto() -> Data {
    //        updatePhoto(completion: {
    //            return (self.user.image) as Data
    //        })
    //    }
    //It is best to separate work from the View
    func deleteGoal(moc: NSManagedObjectContext, goal: Goal) -> Bool{
        print(#function)
        var result = false
        moc.performAndWait  {
            moc.delete(goal)
            do{
                try moc.save()
                result = true
            }catch{
                self.alertVM.scheduleAlert(error: error)
                result = false
            }
        }
        return result
    }
}
//This is to centralize alerts. When you are using the web there will be errors and therefore alerts that the user should be aware of 
struct CustomAlert: Identifiable {
    let id: UUID = UUID()
    let title: String
    let message: String
    let dismissButtonTitle: String
}
//Again to centralize the alerts. I like putting this on the uppermost View so you can send alerts from anywhere
class AlertViewModel: ObservableObject {
    //Singleton keeps everything connected
    static let shared: AlertViewModel = AlertViewModel()
    @Published var currentAlert: CustomAlert?
    private init() {
        //Required because you need to share the instance
    }
    //Use this for a custom message
    func scheduleAlert(title: String = "ERROR", message: String, dismissButtonTitle: String = "OK") {
        currentAlert = CustomAlert(title: title, message: message, dismissButtonTitle: dismissButtonTitle)
    }
    //Use this if you have a fully formed Error
    func scheduleAlert(error: Error) {
        let error = error as NSError
        currentAlert = CustomAlert(title: "ERROR", message:  (error.localizedFailureReason ?? "") + error.localizedDescription + (error.localizedRecoverySuggestion ?? ""), dismissButtonTitle: "OK")
    }
    
}
struct Goals: View {
    //The View should never be aware of where your data is stored. all Firebase code should be removed
    @StateObject var currentUser: UserViewModel = UserViewModel()
    //This observes the alerts that are sent from anywhere
    @StateObject var alertVM: AlertViewModel = AlertViewModel.shared
    //No code provided
    //@StateObject var homeData = HomeViewModel()
    @FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Goal.date, ascending: true)], animation: .spring()) var results : FetchedResults<Goal>
    
    @Environment(\.managedObjectContext) var context
    var body: some View {
        VStack{
            Text("Name = " + currentUser.user.name.description)
            Text("Level = " + currentUser.user.level.description)
            Text("XP = " + currentUser.user.xp.description)
            ForEach(results){goal in
                Button(action: {
                    //There should be some kind of check here to make sure the goal got deleted
                    if currentUser.deleteGoal(moc: context, goal: goal){
                        //No code provided
                        //if current_user_id != nil {
                        currentUser.increaseXPnLV()
                    }
                    //}
                }, label: {
                    //Missing Brackets
                    Text("Goal \(goal.name?.description ?? "") Completed")
                    
                })
                //This gets presented from everywhere
                .alert(item: $alertVM.currentAlert, content: {current in
                    Alert(title: Text(current.title), message: Text(current.message), dismissButton: .cancel(Text(current.dismissButtonTitle)))
                })
                
            }
        }
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
  • Thannks for checking out! Yes, I made a mistake by not including the body bracket into my snippet. And I made some changes to the model architecture later, you may check it out if it doesn't bother you. And what do you mean when mentioning @AppStorage? What variables should be @Appstorage? – Memphis Meng May 24 '21 at 20:59
  • Don’t use AppStorage in a ViewModel it only works in a View. I don’t use Firebase traditionally so I couldn’t be of much help with that part but at simple glance there could be inconsistencies and needs a complete revision – lorem ipsum May 24 '21 at 22:00
  • Look above I posted some sample code but the Firebase part will need a lot of work – lorem ipsum May 25 '21 at 01:34
  • I think i got your point. But what's the benefit to create a class that only focuses on Firebase Management? Should that get response from the database faster? – Memphis Meng May 25 '21 at 16:14
  • 1
    Organization.. now you can use that class in other `View`s without having to repeat code. Anything you copy and paste should be simplified/condensed to avoid copy and pasting. Think about your ViewModel now what if you decide to switch to another database service like Azure? All you have to do is replicate the methods and your ViewModel would just work. Ideally, you should be able to swift from one database to another and just change one line of code. That requires a bit more setup but can be easily done when your View/ViewModel is not concerned with Persistence. Do some research on decoupling – lorem ipsum May 25 '21 at 16:29
  • 1
    Your 'View' should never do "work". It is strictly there for User Interaction. As your app grows you will see the benefits of this rule. – lorem ipsum May 25 '21 at 16:37