Wonhu
Wonhu

Reputation: 5

Updating data in the firebase

I'm trying to do small apps containing firebase codes to learn more about it. so here I made a todo list app, I was able to add the tasks to the firebase and I was able to delete it, the problem I have is updating the status of the task (isComplete: Bool) I've no idea how to write firebase code to update data. almost all the tutorial I read was about the data uploaded to real-time database and I'm using the cloud so I couldn't figure it out. here I wrote this code so when the task is done and I select the cell the circle turn into checkmark.circle It's work but of course the database isn't updated..

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let cell = tableView.cellForRow(at: indexPath) as! TodoCell

        if cell.isComplete == false{
           cell.doneButton.image = UIImage(systemName: "checkmark.circle")
           cell.isComplete = true
  
       } else {
           cell.doneButton.image = UIImage(systemName: "circle")
           cell.isComplete = false

}
}
}

Adding tasks to firebase codes

    public func postTask(task:String, isComplete: Bool,
                            completion: @escaping (Result<Bool, Error>) -> ()) {
      guard let user = Auth.auth().currentUser else {
          return
      }
  
        let documentRef = db.collection(DatabaseService.itemsCollection).document()

        db.collection(DatabaseService.usersCollection).document(user.uid).
        collection(DatabaseService.tasksCollection).
        document(documentRef.documentID).setData(["task" : task,
                                                  "isComplete": isComplete,
                                                  "taskId": documentRef.documentID])
        { (error) in


        if let error = error {
          completion(.failure(error))
        } else {
          completion(.success(true))
        }
      }
    }
    

SnapshotListener

override func viewDidAppear(_ animated: Bool) {
      super.viewDidAppear(true)

        guard let user = Auth.auth().currentUser else {
            return
        }
        listener = Firestore.firestore().collection(DatabaseService.usersCollection)
      .document(user.uid).collection(DatabaseService.tasksCollection)
      .addSnapshotListener({ [weak self] (snapshot, error) in

          if let error = error {
            DispatchQueue.main.async {
              self?.showAlert(title: "Try Again", message: 
               error.localizedDescription)
            }
          } else if let snapshot = snapshot {
            let task = snapshot.documents.map { TasksList($0.data()) }
            self?.todoItems = task
          }
        })
    }

based on @bkbkchoy answer I wrote these codes:

func updateTask(task: TasksList,
                isComplete: Bool,
                        completion: @escaping (Result<Bool, Error>) -> ()) {
  guard let user = Auth.auth().currentUser else { return }

    db.collection(DatabaseService.usersCollection).document(user.uid)
   .collection(DatabaseService.tasksCollection).document(task.taskId)
   .updateData(["isComplete": isComplete]) { (error) in
          if let error = error {
            completion(.failure(error))
          } else {
            completion(.success(true))
    }
  }
}
    
}

and under didSelectRow

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
     
        let cell = tableView.cellForRow(at: indexPath) as! TodoCell
        
       let isComplete = false
       
        
        if task2.isComplete == false{
           cell.doneButton.image = UIImage(systemName: "checkmark.circle")
           cell.doneButton.tintColor = .systemBlue
           cell.isComplete = true

       } else {
           cell.doneButton.image = UIImage(systemName: "circle")
           cell.doneButton.tintColor = .systemGray
           cell.isComplete = false


}
        updateStatus(isComplete: isComplete)

        
    }
       
   private func updateStatus(isComplete: Bool) {
        databaseService.updateTask(task: task2, isComplete: isComplete) 
          { [weak self] (result) in
              switch result {
              case .failure(let error):
                DispatchQueue.main.async {
                  self?.showAlert(title: "Try again", message: error.localizedDescription)
                }
              case .success:
               break
       }
      }
     }
    }

but I got an error :

No document to update: project/todo-list/database/(default)/documents/users/jYZmghQeXodeF2/tasks/1

struct TasksList {
  let task: String
  let taskId: String
  let isComplete: Bool


}

extension TasksList {
  init(_ dictionary: [String: Any]) {
    self.task = dictionary["task"] as? String ?? ""
    self.taskId = dictionary["taskId"] as? String ?? ""
    self.isComplete = dictionary["isComplete"] as? Bool ?? false
  }
}

Upvotes: 0

Views: 85

Answers (2)

bkbkchoy
bkbkchoy

Reputation: 121

There are a couple of ways to update documents in Cloud Firestore:

  1. Rewrite a specific property using setData:

db.collection(DatabaseService.usersCollection)
    .document(user.uid)
    .collection(DatabaseService.tasksCollection)
    .document(task.taskId)
    .setData(["isComplete": isComplete], merge: true)

Note: if you use setData, you must include merge: true to overwrite a single property on an existing document or else the whole document will be overwritten.

  1. Use updateData

db.collection(DatabaseService.usersCollection)
    .document(user.uid)
    .collection(DatabaseService.tasksCollection)
    .document(task.taskId)
    .updateData(["isComplete": isComplete]) { err in 
        if let err = err {
            print("error updating document: \(err)")
        } else {
            print("doc successfully updated")
        }
    }

Firestore has some great documentation online. If you want to learn more about updating/adding data here's a good place to start.

Upvotes: 1

vadian
vadian

Reputation: 285074

Your approach cannot work.

The cell is just the view, it shows the UI elements and their values, the data source is the model TasksList (why not simply Task).

Cells are reused and you will lose the isCompleted information in the cell when the user scrolls. You have to update the model and reload the view

First of all declare the model as Task and isComplete as variable. According to the naming guidelines task should be name or title and taskId should be just id

struct Task {
  let task: String
  let taskId: String
  var isComplete: Bool
}

In cellForRow set the UI elements in the cell according to the model

let task = todoItems[indexPath.row]
let imageName = task.isComplete ? "checkmark.circle" : "circle"
cell.doneButton.image = UIImage(systemName: imageName)
cell.doneButton.tintColor = task.isComplete ? .systemBlue : .systemGray

In didSelect toggle isComplete in the model, reload the row and save the task

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    todoItems[indexPath.row].isComplete.toggle()
    tableView.reloadRows(at: [indexPath], with: .none)
    let task = todoItems[indexPath.row]
    updateTask(task: task, isComplete: task.isComplete) { result in print(result) }
}

As the whole task is handed over, the second parameter isComplete is not needed in updateTask.

Upvotes: 0

Related Questions