-2

I have an Firebase-backed app with the following database structure:

posts
      /uid
           /postId

Originally, i'd load data from the posts/uid node using ObserveEventOfType with .childAdded. This would load stale data frequently (~5 times a day) for all users of my app simutaneously. When attempting to update the data by making a new post, Firebase would still return stale data.

As a result, I decided to try keepSynced. Now, if my reference looked like this:

reference = Database().database.reference.child("posts").child(uid)

keepSynced would load all of the data at that node, which could amount to very large downloads if there are many children in that node. So, I decided to change the reference/query to:

reference = Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25)

When turning keepSynced on for this node, it syncs for the last 25 children in the node successfully. However, I still am facing the issue of receiving stale data rather frequently. So here are my questions:

  1. When adding the keepSynced mode on the limited query, does it only sync from the initial node you added it to, or does it always just sync the 25 latest children under that node?

  2. Where is the best place to add the keepSynced(true) line in code? Before we load the reference, in viewWillAppear, or inside of the actual download callback?

  3. Similarly, where is the best place to use keepSynced(false)?

  4. Do the keepSynced listeners delete when the app fades into the background?

  5. Why does keepSynced sometimes not address for child updates?

I currently use keepSynced(true) inside of the function I use to load posts which is called on viewDidLoad.

Thanks in advance.

BITWISE
  • 115
  • 1
  • 8

1 Answers1

2

As its name implies keepSynced(true) keeps whatever query or reference you call it on synchronized in the local cache. It quite literally just attaches an empty observer to that query/reference. So in your Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25) it will sync the last 25 child nodes, and keep synchronizing those (removing previous ones as new ones are added).

Firebase Realtime Database caching mechanism works most reliably if you repeatedly listen for the exact same data. Specifically, a .value listener attached to Database().database.reference.child("posts").child(uid) may not see data that was cached through Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25). This is because the Firebase client guarantees to never fire events for partial updates, and in this example it can't guarantee that it has all data from the first reference.

For your questions:

  1. See above...

  2. It's most common to add them in viewWillAppear.

  3. I'm not sure why you'd want to call keepSynced false, so can't recommend anything there.

  4. Not sure if this is what you mean, but keepSynced(true) is not persisted between runs of the app. So you have to call keepSynced(true) each time your app/view starts.

  5. See above...

In general you seem to try and work around the way the API works, by calling the API in different ways. I typically don't see great results from that. If you want your app to behave differently than the API does, consider creating a custom wrapper, and caching the data there.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Hi Frank, thanks for your reply. I’m a bit confused: you said that keepSynced will synchronize the last 25 child nodes. This is true when I am in the page that observes the posts- and it updates in real time. However, when refreshing the app (closing & re-opening), the reference does not correctly synchronize the last 25 nodes and it loads stale data. – BITWISE Apr 04 '20 at 18:47
  • Since you asked how `keepSynced()` works, I answered all of your questions. If you're seeing behavior that doesn't match with how (you think) the API is supposed to work, edit your question to include the [minimal, complete/standalone code with which anyone can reproduce that problem](http://stackoverflow.com/help/mcve). That's the only way to know we're looking at the same behavior and code. Standalone here means: 1) use hard-coded values where possible, 2) log output, instead of UI. – Frank van Puffelen Apr 04 '20 at 18:51
  • Thanks for the info Frank, I didn't get a good look at the answer because I was in the car at the time. Just to clarify- I call keepSynced(false) so the nodes don't remain synced outside of the page where I do not need them. Unless I am misunderstanding something, I thought keeping keepSynced true would waste bandwidth. In question #4, by going in the background, I mean keeping the app in the background but not killing the process and going to a different app. – BITWISE Apr 04 '20 at 23:07
  • Update: I solved my problem. It turns out that Firebase was not actually returning stale data; it was returning correct data, but my query was based off the assumption that all auto-generated keys are pre-sorted in some type of order. Turns out they aren't. – BITWISE Apr 06 '20 at 01:12
  • Good to hear that you found the problem Catas! – Frank van Puffelen Apr 06 '20 at 01:37
  • Thanks Frank, please never stop doing what you're doing, your work has changed my life! – BITWISE Apr 06 '20 at 04:21