0

I run the code below and using print statements the Database is saying the children are null

enter image description here

But in the Database there is definitely something there:

enter image description here

The only thing I can think of is I let a user delete a message (by deleting the -LoyeLv... key) but if they want to add it back they can. I keep a copy of the deleted key and just send it back to the database so that the message in still in sync. The only problem with that is even when I don't do it (as in my example) and I delete the message, come back and make a completely new one with a completely new key, it's still coming up as null?

How is this possible even though there are children there? Btw this is the first time this has ever happened to me.

Database.database().reference()
            .child("favorite")
            .child(uid) // TKAETTLWAuREMZXCvrVeZd8yIPw2
            .queryOrderedByKey()
            .queryLimited(toLast: 10)
            .observeSingleEvent(of: .value, with: { [weak self](snapshot) in

                print(snapshot.key) // prints TKAETTLWAuREMZXCvrVeZd8yIPw2
                print(snapshot.value) // prints null

                if snapshot.hasChildren() {
                    print("yay")
                } else {
                    print("nay") // prints nay
                }

                // doesn't go past here
                guard let children = snapshot.children.allObjects.first as? DataSnapshot else { return }

Update I just tried this below and it works fine but the above still doesn't work:

Database.database().reference()
            .child("favorite")
            .child(uid) // prints TKAETTLWAuREMZXCvrVeZd8yIPw2
            .observeSingleEvent(of: .value, with: { [weak self](snapshot) in

                print(snapshot.key) // prints TKAETTLWAuREMZXCvrVeZd8yIPw2
                print(snapshot.value) // prints the json data

                if snapshot.hasChildren() {
                    print("yay") // prints nay
                } else {
                    print("nay")
                }

I woder does deleting the key at that ref than adding the same key than deleting it again than adding a brand new key somehow throws the db off?

Update Again Instead of using .queryOrderedByKey() I changed it to use .queryOrdered(byChild: "timeStamp") and it works fine:

Database.database().reference()
            .child("favorite")
            .child(uid) // TKAETTLWAuREMZXCvrVeZd8yIPw2
            .queryOrdered(byChild: "timeStamp")
            .queryLimited(toLast: 10)
            .observeSingleEvent(of: .value, with: { [weak self](snapshot) in

When deleting a key then adding the same exact key back to the db then querying by queryOrderedByKey() must cause some sort of internal problem that doesn't just mess up the key, it messes up the entire ref. I think what happens is after I delete the key, whatever internal tracking mechanism wipes it from the system. When I add it back it's no longer the same key but instead just a regular String with no internal meaning and that's why is says null. I'm asking it to query something with no meaning. This is a wild guess.

But I would like to know why the same problem occurs with brand new keys that have never been deleted and added back at that same ref?

Here's the code that @FrankvanPuffelen requested in the comments:

This is the vc that sends/deletes the data.

1- sendDataToFirebase()
2- deleteDataFromFirebase()
3- sendDataToFirebase()

Do it in that order

var someOtherId = "12345" // this id has nothing to do with firebase and might be something like "x778-248-000"

var snapKey: String?
var originalTimeStamp: Double?

func sendDataToFirebase() {

    guard let uid = Auth.auth().currentUser?.uid else { return }
    guard let fbKey = Database.database().reference().child("favorite").childByAutoId().key else { return }

    let timeStamp = Date().timeIntervalSince1970

    var favoriteDict = [String: Any]()
    favoriteDict.updateValue(uid, forKey: "uid")
    favoriteDict.updateValue(originalTimeStamp ?? timeStamp, forKey: "timeStamp")

    var whichKeyToUse: String?

    if let snapKey = self.snapKey {

          whichKeyToUse = snapKey
    } else {

          whichKeyToUse = fbKey
    }  

    var carDict = [String: Any]()
    carDict.updateValue(originalTimeStamp ?? timeStamp, forKey: whichKeyToUse!)

    let favoriteRef = "favorite/\(uid)/\(whichKeyToUse!)"

    let carRef = "carType/\(someOtherId)/\(uid)"

    var multiLocationDict = [String: Any]()
    multiLocationDict.updateValue(favoriteDict, forKey: favoriteRef)
    multiLocationDict.updateValue(carDict, forKey: carRef)

    Database.database().reference().updateChildValues(multiLocationDict, withCompletionBlock: { (error, ref) in

           if self.snapKey == nil {

               self.snapKey = fbKey
           }

           if self.originalTimeStamp == nil {

               self.originalTimeStamp = timeStamp
           }

            // if no error this 100% saves it the way it's supposed to
    })
}

func deleteDataFromFirebase() {

    guard let uid = Auth.auth().currentUser?.uid else { return }
    guard let snapKey = self.snapKey else { return }

    let favoriteRef = "favorite/\(uid)/\(snapKey)"

    let carRef = "carType/\(someOtherId)/\(uid)"

    var multiLocationDict = [String: Any]()
    multiLocationDict.updateValue(NSNull(), forKey: favoriteRef)
    multiLocationDict.updateValue(NSNull(), forKey: carRef)

    Database.database().reference().updateChildValues(multiLocationDict, withCompletionBlock: { [weak self](error, ref) in

            // if no error this 100% deletes what it's supposed to
    })
}

2. This is the vc that reads the data, this is an entirely different vc and where the problem lies

func firstCheckIfCurrentUserExistsAtFavoriteRef() {

    guard let uid = Auth.auth().currentUser?.uid else { return }

    let favoriteRef = Database.database().reference()
        .child("favorite")
        .child(uid)
    favoriteRef.observeSingleEvent(of: .value) { [weak self](snapshot) in

        if !snapshot.exists() {
            return
        }

        print(snapshot.key) // prints the key
        print(snapshot.value) // *** the value prints fine here but in handlePagination it prints null ***

        if snapshot.hasChildren() {
            print("yay") // prints yay
        } else {
            print("nay")
        }

        self?.handlePagination(with: uid)
    }
}

var startKey: String?
func handlePagination(with currentUserId: String) {

    if startKey == nil {

        Database.database().reference()
            .child("favorite")
            .child(currentUserId)
            .queryOrderedByKey() // it works fine with this >>> .queryOrdered(byChild: "timeStamp")
            .queryLimited(toLast: 10)
            .observeSingleEvent(of: .value, with: { [weak self](snapshot) in

                print(snapshot.key) // prints the uid
                print(snapshot.value) // *** prints null but in firstCheckIfCurrentUserExistsAtFavoriteRef() it prints fine ***

                if snapshot.hasChildren() {
                    print("yay")
                } else {
                    print("nay") // prints nay
                }

                guard let children = snapshot.children.allObjects.first as? DataSnapshot else { return }

                // ...

            })

    } else {

        // it never makes it this far but it's the same code from above
        Database.database().reference()
            .child("favorite")
            .child(currentUserId)
            .queryOrderedByKey() // it works fine with this >>> .queryOrdered(byChild: "timeStamp")
            .queryEnding(atValue: startKey!)
            .queryLimited(toLast: 11)
            .observeSingleEvent(of: .value, with: { [weak self](snapshot) in

}

I call firstCheckIfCurrentUserExistsAtFavoriteRef() in viewDidLoad

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • The description doesn't sound like a behavior I've ever seen from the Realtime Database. Can you show a single snippet of code that anyone can run to reproduce the problem? This should likely include the code that deletes/writes the nodes, as those seem key to your problem. – Frank van Puffelen Sep 17 '19 at 11:38
  • @FrankvanPuffelen I added the entire code. And everything gets added and deleted with no problem to the correct refs. It works exactly how I expect it to other than the odd issue raised in the question. – Lance Samaria Sep 17 '19 at 12:43
  • You're using `observeSingleEvent` everywhere, which means the listeners don't stay active. Where are you calling those methods. Also note that you now shared three listeners, instead of just the one from before. Is there a way you can [construct a single code block that someone can run end to end to (reasonably reliably) reproduce the issue](http://stackoverflow.com/help/mcve)? – Frank van Puffelen Sep 17 '19 at 13:07
  • I’m calling firstCheckIfCurrentUserExistsAtFavoriteRef in viewDidLoad. Other then the issue I’m having the code works in other vcs. When I paginate everything works. This is data the user checks like in their history so there’s no need to use .observe because no new data will appear. It just paginates as they scroll. – Lance Samaria Sep 17 '19 at 13:29
  • As a test, I copy an pasted your exact code that's failing into a project. I then duplicated your structure and ran the code. It prints the key *TKAETTLWAuREMZXCvrVeZd8yIPw2* and the child node value as well as *yay*. So the code works, there must be something else going on. Also note that Firebase cannot have a nil value. Nodes with nil values don't exist. – Jay Sep 17 '19 at 16:32
  • 1
    Do you have persistence enabled? – Jay Sep 17 '19 at 16:38
  • Hi jay thanks for the help. I know it’s a very odd problem I’m not sure why it’s happening because it doesn’t happen in other modes and I’m 100% sure it’s happening. I do have have persistence enabled in app delegate. Do you think that could be causing the issue? – Lance Samaria Sep 17 '19 at 16:41
  • Another Q: What do you rules look like for that node? – Jay Sep 17 '19 at 16:42
  • read: auth.uid != null, write: auth.uid != null. After switching it to use timestamp I added .indexOn: [timeStamp] but the issue occurs before I used timeStamp – Lance Samaria Sep 17 '19 at 16:44
  • @Jay you was correct about persistence. I turned persistence off and then switched back to using .queryOrderedByKey and now it works fine. That must’ve been what was causing the problem because I did t do anything else to the code, rules, nor db. Can you post that as answer, I’ll accept it. – Lance Samaria Sep 17 '19 at 17:10

1 Answers1

0

I have seen this issue before with persistence turned on - the issue is the data is partially sync'd but not fully.

For a quick test, try turning persistence off and re-running the query.

Database.database().isPersistenceEnabled = false

That's obviously not a long term solution but just for testing.

If you want to use persistence, you also need to ensure the data is kept fresh and always in sync. Here's the code

let postsRef = Database.database().reference(withPath: "posts")
postsRef.keepSynced(true)

The Firebase Realtime Database client automatically downloads the data at these locations and keeps it in sync even if the reference has no active listeners. You can turn synchronization back off with the following line of code

postsRef.keepSynced(false)
Jay
  • 34,438
  • 18
  • 52
  • 81
  • thanks Jay, once I turned it off the problem went away. Cheers! – Lance Samaria Sep 17 '19 at 17:51
  • hey I have a few questions about the persistence answer you gave. 1. Where do you call .keepSynced(false) -in viewWillDisappear? Because when paginating, I only you observSingleEvent and if the user switches tabs and comes back I’ll need to cut synchronization back on. 2. Why not use .referehce(withPath: “xyz”) instead of .child(“xyz”). 3. Does persistence on download the entire ref? That seems expensive monetary wise if you use it on all singleEvent refs and it defeats the purpose of pagination. – Lance Samaria Sep 21 '19 at 21:55
  • if you have some time to glance this over, these guys said keepSynced(true) significantly raised their bill and suggested not to use it: https://pamartinezandres.com/lessons-learnt-the-hard-way-using-firebase-realtime-database-c609b52b9afb – Lance Samaria Sep 22 '19 at 00:35
  • Let me address the questions. 1) You'll see over and over the firebasers say *Disk persistence and once/single-value-event-listeners just don't mix.* so don't do that. Read [this answer] from @frankvanpuffelen. 2) Because the documentation is somewhat intermittent but the official API calls for [.child](https://firebase.google.com/docs/reference/swift/firebasedatabase/api/reference/Classes/DatabaseReference) 3) At that location yes so you need to be selective in what you sync but it's a requirement to avoid a case where the user may be looking for stale data. – Jay Sep 22 '19 at 13:03
  • 1
    3) continued. A higher monetary cost is correct. However, in your case the app *requires* fresh data so there's no choice. Alternatives exist such as no queries when the app is offline or nil results because the data is stale. The weather app I use does that - when I try to get weather for a city when there's no internet, it just says 'no internet connection - will refresh once connection has been established. See [How Persistence Works](https://stackoverflow.com/questions/34486417/firebase-offline-capabilities-and-addlistenerforsinglevalueevent/34487195#34487195][1]) – Jay Sep 22 '19 at 13:06
  • thanks for the info. That's how my app works now, if there is no internet connection, any screen within the entire app shows a "no internet connection". I read somewhere (can't remember) that keeping persistence on in App Delegate is what kept the data in sync regardless of wether you wanted offline 'connections' or not. It perplexed as to why it wouldn't be in sync without it on when it has to make a dip down then there. But thanks, I'm just going to keep it off since users can't use my app with internet anyway. Plus saving $$ is important when starting out. – Lance Samaria Sep 22 '19 at 15:52