1

my goal is to set a timer in one vc when a firestore document is created and fire it in another vc when the timer is up to delete the firestore document.

In my vc where I create the document, I have this block of code in the button that creates the document in firestore:

db.collection("school_users/\(user?.uid)/events").addDocument(data: ["event_name": nameTextField.text, "event_date": dateTextField.text, "event_cost": costTextField.text, "for_grades": gradesTextField.text, "school_id": schoolIDTextF.text, "time_created": Date()]) { (error) in
                if error != nil {
                    self.showError("There was an error trying to add user data to the system.")
                } else {
                    self.setTimerForEventDeletion()
                    self.dismiss(animated: true, completion: nil)
                }
            }

The setTimerForEventDeletion() function contains this :

  func setTimerForEventDeletion() {
    let date = dateTextField.text!
   let dateToDelete = formatter.date(from: date)!
    let timer = Timer(fireAt: dateToDelete, interval: 0, target: self, selector: #selector(callDeleteEventFunction), userInfo: nil, repeats: false)
    
}

The date format is correct as well I already have all that setup. For the objc method, I call another function from a different vc (will explain) in that function.

 @objc func callDeleteEventFunction() {
    otherVC.deleteEventAtEventTime()
}

The reason I call this function from the other vc is because the other vc is storing the details of the created document, you can't fetch the details of a document that isn't created yet, so I have it in another VC. The function looks like this...

@objc func deleteEventAtEventTime() {
    db.document("school_users/\(user?.uid)/events/\(selectedEventDocID!)").delete { (error) in
        if let error = error {
            print("There was an error deleting the doc: \(error)")
        } else {
            print("Doc deleted!")
        }
    }

}

Now before posting this question, I tested it by firing the timer in the same vc that stores the document details and it worked, but when I exited that vc, the cell deleted and the tableview shrank, basically glitching out the app, and eventually crashed when I clicked another cell with different details.

So i want to be able to have the timer be set when the document is created in one vc, and have the timer fire in another vc when the deletion date arrives.

dsonawave
  • 137
  • 2
  • 12
  • In terms of *location* basically a timer fires at the place of its creation. However the action method is called on the object specified in the `target` parameter; `self` points to the current class, you have to set it to the (actual) instance of the other controller. – vadian Feb 13 '21 at 21:55
  • ok so I put the actual vc in `target`, now, if I want to fire the timer in the `viewDidLoad()` of the target vc, how would i do it, make a global variable of the timer in the vc it's created in and use it in the target vc? Or is there another approach? @vadian – dsonawave Feb 13 '21 at 22:19
  • The timer calls the method specified in the `selector` parameter when it fires, it cannot *fire* somewhere else. – vadian Feb 13 '21 at 22:33
  • This is a hard question to answer, because the answer is pretty extensive. The abridged version is: "Your controller shouldn't know anything about databases, timers, and such. You should build a data model layer which exclusively deals with the db, exposing simple methods like `delete(event: Event)`, and such. From there, multiple view controllers could have access to a shared set of these data layer objects, which can then trigger timers, respond to events, etc." – Alexander Feb 13 '21 at 22:48
  • Do you know where i can find the extensive answer? I can't dodge this sort of app functionality, it's a must have. Plus I don't get what you mean by create data models for document deletion, Firestore document queries already come with a `delete()` method. @Alexander – dsonawave Feb 13 '21 at 22:55
  • @dsonawave Here's an article that has a decent summary https://www.swiftbysundell.com/articles/logic-controllers-in-swift/ Consider this, how many of your view controllers "know" about Firebase? By "know", I mean that, if you decided one day that you don't want to use Firebase anymore (e.g. Google kills it, the pricing model changes and you don't want to be trapped paying higher costs, etc.), how many view controllers would you need to change? Presumably, a lot of them. The issue that your view controllers do more than "controlling views". In principle, they shouldn't know anything about ... – Alexander Feb 14 '21 at 00:38
  • Firebase, the network, database, caches, file systems, or any other such things. They're there to control views. There should be other classes that are responsible for that. For example, an `EventManager`, whose job it is to interact with firebase, and expose a facade that completely hides the fact that firebase is working under the hood. This has two benefits. The first one, which is relevant to your problem, is that it gives a central location to implement "cross-viewcontroller" behavior such as your timer. The second huge benefit is that hides that it uses Firebase ... – Alexander Feb 14 '21 at 00:42
  • It can just expose methods like `createEvent() -> Event`, `delete(event:)`, etc. You could actually implement a different version that uses Realm, or ObjectBox, or CoreData, or an SQLite database, or a custom server, or a custom database, or an in-memory store used for testing. For example, this would let you write tests of your view controller that don't need to depend on firebase (they can use the in-memory mock EventManager), so now you can run your unit tests offline, and faster (since they don't make network requests). – Alexander Feb 14 '21 at 00:44
  • More specifically, the issue you're struggling with here is that because you've intertwined "model" code, (firebase interacting) and "view" code (view controllers), you're struggling to implement model-related functionality that needs to span between views. The correct solution is to extract all this model-related code into a single object, and have multiple view controllers share that object, and use it to achieve their goals (loading events, saving events, deleting events, whatever) – Alexander Feb 14 '21 at 00:46
  • Are you saying I should switch databases to accomplish my goal, cause if that's the case, I'll try any workaround, there's no way i'm starting all over again lol. Hopefully the article can deliver cause I genuinely don't know how to approach what you just explained. @Alexander – dsonawave Feb 14 '21 at 01:25
  • Ok after reading it, I get what you're saying but that's something I can fix later on down the line of production where I clean up code, sharpen up UI, run testing, but for now I just wanna solve basic app functionality and in this case know how to solve my Timer problem directly. @Alexander – dsonawave Feb 14 '21 at 01:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/228681/discussion-between-alexander-and-dsonawave). – Alexander Feb 14 '21 at 04:00

1 Answers1

0

After doing some research and reading around, I've figured it's best to use Googe Cloud Functions to perform the task of auto-deletion of a Firestore document in a database as well as in my app.

After doing some even more research, I realized that going through this wasn't necessary at least for now. I will provide a very simple workaround that took about 20 minutes to figure out and prevented me from having to learn Node to do Cloud Functions.

So what I wanted to get done was be able to delete documents when they expire automatically and I got that done with a couple blocks of code.

So first, I declared 2 variables to help with formatting.

let rightNow = Date()
let formatter = DateFormatter()

These will help with the next function I'm about to show.

  func setupDate(completion: @escaping ((String?) -> ())) {
    formatter.dateFormat = "EEEE, MMMM d yyyy, h:mm a"
   let dateToString = formatter.string(from: rightNow)
    completion(dateToString)
}

This function gave me the date as a String so I could put it inside my query function which I will show as well. I used nsdateformatter.com to get the exact format I had in my textfield, I recommend using that site for Date() related code in regards to formatting.

This next function basically sealed the deal and is the reason why everything works.

func deleteExpiredCreatedEvents() {
    setupDate { (dateforexpiry) in
            self.db.collection(Constants.Firebase.schoolCollectionName).whereField("expiresAt", isLessThanOrEqualTo: dateforexpiry!).addSnapshotListener { (querySnapshot, error) in
                if error != nil {
                    print("Error fetching docs/User deleted account.")
                } else {
                    for document in querySnapshot!.documents {
                        let documentident = document.documentID
                        let docalgoliaid = document.get("algoliaObjectID") as! String
                        
                        deleteIndex.deleteObject(withID: ObjectID(rawValue: docalgoliaid)) { (result) in
                            if case .success(let response) = result {
                                print("Record Deleted!: \(response.wrapped)")
                            }
                        }

                        self.db.document("school_users/\(self.user?.uid)/events/\(documentident)").delete { (err) in
                            if err != nil {
                                print("Errr")
                            } else {
                                print("No errs")
                                self.tableView.reloadData()
                            }
                        }
                    }
                }
            }
    }
}

I then call this function in the viewDidLoad() of the desired vc and the events loaded are only the ones not expired. Hope this can shine some light on somebody in the future having trouble with the same issue.

dsonawave
  • 137
  • 2
  • 12
  • Also be sure to add `-0000` at the end of the format style if you're in EST time zone, this ensures that the record deletes at the right time. I also added `.current` to the `timeZone` and `locale` properties of the `DateFormatter()`. This now works perfectly for me. – dsonawave Feb 21 '21 at 14:04