title: Update data stored in Firebase using MVVM nested models
body: <p>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.</p>
  <p>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.</p>
  <p>I heard that nested models don't work yet if SwiftUI is in use (<a href="https://stackoverflow.com/a/58406402/12097673">reference</a>). However, I just cannot come up with a better solution if my classes are untested.</p>
  <pre><code>// This is Model
  import Foundation
  import SwiftUI
  
  struct userModel {
      var uid = UUID()
      var name = &quot;&quot;
      var bio = &quot;&quot;
      var interest = &quot;&quot;
      var level = 1
      var xp = 0
      var email = &quot;&quot;
      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) -&gt; Int {
          if xp &lt; 9500 {
              return xp / 500 + 1
          }
          else if xp &lt; 29500 {
              return (xp - 9500) / 1000 + 1
          }
          else {
              return (xp - 29500) / 2000 + 1
          }
      }
  }
  </code></pre>
  <pre><code>// This is ViewModel
  import Foundation
  import SwiftUI
  import Firebase
  
  class userViewModel: ObservableObject {
      @Published var user: userModel = userModel()
      
      @Published var isLoading = false
      @AppStorage(&quot;status&quot;) var status = false
      private var ref = Firestore.firestore()
      private let store = Storage.storage().reference()
      var picker = false
      
      func updateXP(completion: @escaping () -&gt; Int) -&gt; Int {
          guard let uid = Auth.auth().currentUser?.uid else {
              return 0
          }
          // catch the information of the current user
          let db = ref.collection(&quot;Users&quot;)
          db.addSnapshotListener { [self] (querySnapshot, error) in
              guard (querySnapshot?.documents) != nil else {
                  print(&quot;Document is empty&quot;)
                  return
              }
              let docRef = db.document(uid)
              
              docRef.getDocument { (snapshot, error) in
                  if let doc = snapshot,
                     let xp = doc.get(&quot;xp&quot;) as? Int {
                      self.user.xp = xp
                  }
              }
          }
          return completion()
      }
      
      func updateLevel(completion: @escaping () -&gt; Int) -&gt; Int {
          guard let uid = Auth.auth().currentUser?.uid else {
              return 1
          }
          // catch the information of the current user
          let db = ref.collection(&quot;Users&quot;)
          db.addSnapshotListener { [self] (querySnapshot, error) in
              guard (querySnapshot?.documents) != nil else {
                  print(&quot;Document is empty&quot;)
                  return
              }
              let docRef = db.document(uid)
              
              docRef.getDocument { (snapshot, error) in
                  if let doc = snapshot,
                     let level = doc.get(&quot;level&quot;) as? Int {
                      self.user.level = level
                  }
              }
          }
          return completion()
      }
      
      func updateName (completion: @escaping () -&gt; String) -&gt; String {
          guard let uid = Auth.auth().currentUser?.uid else {
              return &quot;&quot;
          }
          // catch the information of the current user
          let db = ref.collection(&quot;Users&quot;)
          db.addSnapshotListener { [self] (querySnapshot, error) in
              guard (querySnapshot?.documents) != nil else {
                  print(&quot;Document is empty&quot;)
                  return
              }
              let docRef = db.document(uid)
              
              docRef.getDocument { (snapshot, error) in
                  if let doc = snapshot,
                     let name = doc.get(&quot;username&quot;) as? String {
                      self.user.name = name
                  }
              }
          }
          return completion()
      }
      
      func updateBio (completion: @escaping () -&gt; String) -&gt; String {
          guard let uid = Auth.auth().currentUser?.uid else {
              return &quot;&quot;
          }
          // catch the information of the current user
          let db = ref.collection(&quot;Users&quot;)
          db.addSnapshotListener { [self] (querySnapshot, error) in
              guard (querySnapshot?.documents) != nil else {
                  print(&quot;Document is empty&quot;)
                  return
              }
              let docRef = db.document(uid)
              
              docRef.getDocument { (snapshot, error) in
                  if let doc = snapshot,
                     let bio = doc.get(&quot;bio&quot;) as? String {
                      self.user.bio = bio
                  }
              }
          }
          return completion()
      }
      
      func updateInterest (completion: @escaping () -&gt; String) -&gt; String {
          guard let uid = Auth.auth().currentUser?.uid else {
              return &quot;&quot;
          }
          // catch the information of the current user
          let db = ref.collection(&quot;Users&quot;)
          db.addSnapshotListener { [self] (querySnapshot, error) in
              guard (querySnapshot?.documents) != nil else {
                  print(&quot;Document is empty&quot;)
                  return
              }
              let docRef = db.document(uid)
              
              docRef.getDocument { (snapshot, error) in
                  if let doc = snapshot,
                     let interest = doc.get(&quot;interest&quot;) as? String {
                      self.user.interest = interest
                  }
              }
          }
          return completion()
      }
      
      func updatePhoto (completion: @escaping () -&gt; Data) -&gt; Data {
          guard let uid = Auth.auth().currentUser?.uid else {
              return Data(count: 0)
          }
          
          // catch the information of the current user
          let db = ref.collection(&quot;Users&quot;)
          db.addSnapshotListener { [self] (querySnapshot, error) in
              guard (querySnapshot?.documents) != nil else {
                  print(&quot;Document is empty&quot;)
                  return
              }
              let docRef = db.document(uid)
              
              docRef.getDocument { (snapshot, error) in
                  if snapshot != nil {
                      let imageRef = store.child(&quot;profile_Photos&quot;).child(uid)
                      imageRef.getData(maxSize: 1000 * 64 * 64, completion: { (data, error) in
                          if let error = error {
                              print(&quot;Encountered error: \(error) when getting image&quot;)
                              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: &quot;person&quot;).resizable()
                              self.user.image = Data(count: 0)
                          }
                        })
                  } else if let error = error {
                      print(error)
                  }
              }
          }
          return completion()
      }
      
      public func getXP() -&gt; Int{
          updateXP {
              return (self.user.xp) as Int
          }
      }
      
      public func getLevel() -&gt; Int {
          updateLevel(completion: {
              return (self.user.level) as Int
          })
      }
      
      public func getName() -&gt; String {
          updateName(completion: {
              return (self.user.name) as String
          })
      }
      
      public func getBio() -&gt; String {
          updateBio(completion: {
              return (self.user.bio) as String
          })
      }
      
      public func getInterest() -&gt; String {
          updateInterest(completion: {
              return (self.user.interest) as String
          })
      }
      
      public func getPhoto() -&gt; 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: &quot;profile_Photos&quot;) { (url) in
              self.ref.collection(&quot;Users&quot;).document(uid ?? &quot;&quot;).setData([
                              &quot;uid&quot;: uid ?? &quot;&quot;,
                              &quot;imageurl&quot;: url,
                              &quot;username&quot;: self.user.name,
                              &quot;bio&quot;: self.user.bio,
                              &quot;interest&quot; : 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(&quot;Users&quot;).document(uid)
          docRef.getDocument { (document, error) in
              if let document = document, document.exists {
                  docRef.updateData([&quot;xp&quot;: FieldValue.increment(Int64(50))])
                  // update level
                  let xp = document.data()![&quot;xp&quot;] as! Int
                  docRef.updateData([&quot;level&quot;: self.user.xp2Level(xp: xp)])
              } else {
                  print(&quot;Document does not exist&quot;)
              }
          }
      }
      
      func uploadImage(imageData: Data, path: String, completion: @escaping (String) -&gt; ()){
          
          let storage = Storage.storage().reference()
          let uid = Auth.auth().currentUser?.uid
          
          storage.child(path).child(uid ?? &quot;&quot;).putData(imageData, metadata: nil) { (_, err) in
              print(&quot;imageData: \(imageData)&quot;)
              if err != nil{
                  completion(&quot;&quot;)
                  return
                  
              }
              // Downloading Url And Sending Back...
              storage.child(path).child(uid ?? &quot;&quot;).downloadURL { (url, err) in
                  if err != nil{
                      completion(&quot;&quot;)
                      return
                      
                  }
                  completion(&quot;\(url!)&quot;)
              }
          }
      }
  }
  
  </code></pre>
  <pre><code>// 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 = &quot;&quot;
      @State var edge = UIApplication.shared.windows.first?.safeAreaInsets
      @FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(key: &quot;date&quot;,
                                                                              ascending: true)], animation: .spring()) var results : FetchedResults&lt;Goal&gt;
      
  //    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
      @State private var greeting : String = &quot;Hello&quot;
      @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(&quot;Something&quot;)
      )
                                              .alert(isPresented: $showingAlert) {
                                          () -&gt; Alert in
                                          Alert(title: Text(&quot;Congratulations!&quot;), message: Text(&quot;You completed a goal today, XP+50!&quot;), dismissButton: .default(Text(&quot;OK&quot;)))
                                              }
      }
  }
  }
  </code></pre>
  <p><strong>EDIT</strong></p>
  <p>Another error I saw is <code>AttributeGraph precondition failure: attribute failed to set an initial value: 805912, ForEachChild&lt;Array&lt;userInfoModel&gt;, ObjectIdentifier, HStack&lt;VStack&lt;HStack&lt;TupleView&lt;(Text, Divider, Text)&gt;&gt;&gt;&gt;&gt;.</code></p>