-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Swift observeSingleEvent
closure issue.
#274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Can you not avoid the compiler error by changing the let ref = Database.database().reference()
var handle = ref.observe(.value) { (snapshot) in
ref.removeObserver(withHandle: handle)
} |
@morganchen12 nope, same issue. Swift tries to capture the value before it's initialized. I can change the sample code to a |
Thanks for creating this issue, @ryanwilson. Important Note:** I would modify the Steps to Reproduce from:
to
In cases where persistence is enabled, Swift consumers of the Firebase SDK have no recourse for retrieving a fresh Firebase node value other than storing a node handle (UInt returned by observeEvent) as a property, which is very impractical. Another workaround is to create a trampoline interface in Objective-C for Swift consumers to use, but this is also impractical. My suggestion would be to add a The final method signature might look something like this in Objective-C:
I might recommend adding this as an overloaded method so it doesn't break existing code. If we want to support Swift default values, then |
@morganchen12 I tried that approach, to no avail. There must be something I'm not understanding about how that type is captured, because the following similar pattern works in NotificationCenter: var token: NSObjectProtocol?
token = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: kNotificationOnlineStatusUpdated), object: nil, queue: OperationQueue.main) { notification in
// this works
NotificationCenter.default.removeObserver(token!)
} I speculate that it must have something to do with the way NSObjectProtocol works or the way optionals are captured. |
It's because we return a value type ( Agreed, this is pretty annoying and not immediately clear. |
@ryanwilson even in your workaround, the callback will fire twice, first returning stale data, and second returning fresh data. I tried the same thing and this had negative downstream implications in my app. I strongly support a new feature to force |
For those struggling with this issue, the cleanest workaround is probably to use a Box reference type. If you don't want to take on another dependency, you can implement your own box pretty easily. |
Thanks for the advice, @morganchen12. I'll give that route a try. @ryanwilson, has this issue gained any traction with the dev team? |
@BigSauce it's a very straightforward problem with an equally straightforward (and easily implemented) solution. The only thing that's prevented us from making this change is it's a breaking API change requiring a major version bump, as the return type of our function must be changed. |
Gotcha. So you're saying the best time for this to be implemented by the Firebase iOS SDK team is for a major release as opposed to ASAP (due to breaking changes)? If so, that makes sense. For now I'll try the boxed type workaround and see if it works on my end. In the mean time, I'll reply on this thread if I run into any issues with that, so perhaps others can benefit from my work. |
One workaround I considered was: let ref = Database.database().reference()
var handle = UInt.max
handle = ref.observe(.value) { (snapshot) in
ref.removeObserver(withHandle: handle)
} One problem I had was:
|
@ryanwilson can you tackle this for our next major release? |
I'll do my best to get this in for the next major release. I'll have to check if there's enough time to deprecate and remove the old one by the next major release. |
Guess there wasn't enough time... This whole persistenceEnabled thing is really genius, but only being able to use it with the permanent observers makes it really hard to work with... |
There's still time to get this in and I'm still giving it a shot. Apologies for the delay in response! Contributions are welcome for the implementation side of this, too 😃 |
Any updates on this? |
I just ran into this issue because I'd experienced the exact same problem. We're currently building a chat for our app. Because we're rolling out this chat after the app it self was launched, a lot of old challenges won't have a chat. Therefore we need to check if a chat exists or not. This is where "observeSingleEvent" became an issue. Because we've enabled persistence. And after doing that, it was suddenly only the creator of the challenge on the exact phone it was created from, who could find the chat. When logging in to the same user on another phone, no chat is found and therefore we don't show a chat button, even though we know the chat exists. My solution to this is:
After testing this, I can see in the console that says "No chat" first, and then "Has chat". |
Any update on this? After updating the Firebase pods on our project, the workaround described in this thread (and in my own comment above this) doesn't seem to work anymore. Before, the ".observe" returned first the local data and then the online if there were any changes. To solve this issue I had to add a boolean flag, for get both the local and online data:
This however, creates a minor issue because the observer isn't removed unless I receive data twice (offline & online). I'm getting around this by removing the observer on viewDidDisappear. These are the versions of Firebase we run: |
* compile and run * unit tests pass and swift target compiles * dump build version in activity log * standardize cross sdk imports in firebase iam code (#211) standardize cross sdk imports & polish with style script * using new sample app for testing non-development fiam sdk (#213) * in-app messaging public interface cleanup (#216) * slim down public header files * move testing source files to their correct folders * compiles * style script fine-tune * adding root object creation methods to follow the firebse iOS convension * IAM url following refactor (#218) * @available not supported by blaze build yet (#220) * Clearcut integration for in-app messaging (#222) * Update iam-master to catch up with latest master (#223) * Support multiple triggers defined for the same iam message (#224) * FIAM Clearcut Client Implmentation Improvement (#226) * fixing action url bug and set auto dismiss to be 12 seconds (#227) * use official public api dns name (#233) * Logging firebase analytics events for fiam (#232) * Fine tuning of fiam's code for loading data from caches (#234) * fiam ui fine tuning before EAP (#235) * allow fiam SDK functions to be disabled/enabled dynamically (#247) * analytics event parameter name tuning (#253) * migrate off @import for fiam code (#261) * different modes for fiam sdk at runtime (#262) * realtime clearcut when in running in simulator (#265) * Update interface based on the Firebase API review feedback (#267) * check existence of fiam SDK resource bundle (#268) * handling api keys with application restrictions (#269) * fixing typo and refoctor for unit testing (#270) * fix color method name clash and layout adjust (#271) * Yongmao/layout issue and class rename (#273) * fix long text height calc error * rename class * fiam modal view layout tuning (#274) * test on device for ios client (#275) * dynamic testing mode handling (#277) * main header file comment update and run style.sh (#281) * honor global firebase auto data collection flag in fiam (#283) * honor global firebase auto data collection flag in fiam * address comments * tuning * address review comments * tuning for comments * Rename Core global data collection switch (#287) * Support displaying a messages defined as recurring (#286) * continue * Adjust some inapp messaging dependeces due to the firebase core changes from the upcoming release * bump up version for in-app messaging * Fix impression log clearing race condition bug. (#289) * fix impression handling race condition bug * podspec and interacting interfaces for new firebaseinappmessagingdisplay pod (#290) * create inapp messaging display podspec * Default fiam display UI implementation (#292) * Default fiam display UI implementation. Created the new pod for FirebaseInAppMessagingDisplay * Make FirebaseInAppMessaging headless (#293) * import path tuning * log test message events (#296) * respect fetch wait time from server (#297) * add changelog.md for fiam headless sdk (#300) * port the fetch wait time change back into github (#304) * remove resource bundle check for headless sdk * fix a bug in which the test-on-device message does not follow action url * Add category on NSString to interlace two strings, use it to obfuscate clearcut host name * Use FIR prefix to avoid collisions * Change method name for getting server hostname * Delete Swift example project, its coverage is already duplicated in the FIRInAppMessagingDisplay UITests * Miscellaneous open sourcing cleanup tasks * Convert FIAM to use the Analytics connector (#336) * Convert FIAM to use Analytics connector along with converting to the FIRLibrary component registration model. * Update interop headers for missing methods * Convert existing unit tests * Fix style for fiam sources (#342) * Clean up non-idiomatic ObjC method names * Clean up Swift test target from Podfile * Use dummy GoogleService-Info.plist and don't expose api keys (#343) * Update plist to include fake API keys * Remove changes in non fiam code from iam-master branch (#350) * Fix a few method and parameter naming nits * Fix comment typos in several files * Delete remaining Swift project files
@ryanwilson I tried to implement a solution that doesn't need a major release as it doesn't change the - (FIRDatabaseQuery *)queryWithBypassCache {
FQueryParams* params = [self.queryParams enableBypassCache];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:self.priorityMethodCalled];;
} a if (query.params.bypassCache) {
complete = NO;
node = [FSnapshotUtilities nodeFrom:nil];
} so the observer is not called with the empty node and waiting to get called with the new data from the server. If the user is offline, then the observer is not called at all, so dev needs to do a check for so the call becomes something like this ref.child("BlaBla").withBypassCache().observeSingleEvent(of: .value, with: { [weak self] (snapshot) in Do you see any problem and how that maybe break a different part of the project, because persistence is deeply integrated in it? the commit can be found here kodika@5dc0adb |
Thank you for the attempt @kostassite! I'm not the expert on Database but @schmidt-sebastian should have more context if this is going to be a problem or not. |
I think that we can take a lot from the PR you sent over @kostassite. Let me dwell a bit on the API and find suitable APIs for our other platforms as well. If we can avoid a breaking change, then we will be able to release this much faster. Note that we just implemented this exact feature in Firestore: https://github.com/firebase/firebase-ios-sdk/blob/master/Firestore/Source/Public/FIRDocumentReference.h#L228. This was also done in a minor release. |
So when we can expect the Right solution for this issue? As its causing issues in the project and this issue is open from long ago |
any update on this? potential solution could be adding a flag to reference itself like "keepSynced" but without downloading the whole tree - it's not acceptable in many cases. Like "reference.skipCache" flag which defaults to "false" It shouldn't affect existing code as far as I can see |
We are currently in the process of designing a solution for this. This is in the very early stages, but some evidence can be found here: firebase/firebase-js-sdk#3812 |
@kostassite Any updates on this? Is this issue fixed in version 7.5.0 released on Jan 21, 2021 with getData? |
@JCsplash I have pushed a commit with a potential fix in our repo but it is up to the Firebase team on when they will merge or create a similar solution. We are using although the solution for over a year now and we didn't have any problem. |
@kostassite Gotcha, i think observeSingleEvent still has the same behavior but getData works great and always seems to fetch the latest data. Do you know of any downsides / side effects of using getData? |
@schmidt-sebastian Any update on this one? |
Wow, I didn't know this trick to avoid caching! Glad that it resurfaced! ❤️ |
While we have not addressed the original language-level issue, the I am going to close this issue as we have not yet come up with a good way to address the original issue without a breaking change. |
Describe your environment
Describe the problem
It's difficult to observe a single event without getting a cached value in Swift. The value is cached, leaving the opportunity to retrieve stale data. This can usually be mitigated by using the
removeObserver(withHandle:)
call, but that doesn't work well in Swift due to the way closures work.Steps to reproduce:
Original issue.
Database.database().isPersistenceEnabled = true
)observeSingleEvent
on a reference from Database.observeSingleEvent
again, retrieve old stale value.Attempted solution.
observeEvent
on a reference, assign the return value to avar
.removeObserver(withHandle:)
in the closure.Relevant Code:
Workaround
If the ref only has a single observer,
ref.removeAllObservers()
will work in place of theremoveObserve(withHandle:)
call. Unfortunately, this doesn't work if multiple observers are added to theref
.Adding a property declaration should work, but isn't ideal as it pollutes the
class
orstruct
being used, leaves some uncertainty whether or not theremoveObserver
call will actually happen.Potential Solution(s)
Providing a parameter on the
observeSingleEvent
method that bypasses the cache and will only fetch from the server is a possibility. Some naming thoughts:bypassCache
forceFetch
,fromServer
.The text was updated successfully, but these errors were encountered: