I'm working on a app with Firebase Database and I also want to allow the user adding entries when he's offline. Imagine the following case (everything offline; it works perfectly online):
The user adds a client (shown in a main tableview - works)
On the detail view of the freshly added client, he adds an appointment with him. The appointments are listed in a tableview in the Client Detail ViewController.
When the appointment is added, the tableview isn't refreshed, because the callback doesn't call.
In code:
navCont.child = userDB.child(entityNameClients).childByAutoId()
navCont.child?.setValue(post) where post is a dictionary with the values of the customer
On the viewDidLoad Method of the detail view controller, I call the following code: getUsersDBReference().child(entityNameAppointments).child(clientKey).observe(.value). The Callback isn't called (probably because there are no elements in it)
Adding Appointment
var appointment:DatabaseReference
appointment = getUsersDBReference().child(entityNameAppointments).child(clientKey).childByAutoId()
appointment.setValue(appointmentDict)
The Callback of 2 isn't called (--> and the tableview not refreshed with the new appointment)
BTW, I set Database.database().isPersistenceEnabled = true in App Delegates applicationDidLoad-method.
And if I add the client online and go offline then, it works too.
Answer of Firebase Support:
The SDK attempts not to fire Value events with "partial" data. Since you are only updating a subnode of the location being observed, we don't have "complete" data and so the observer is not fired. If the SDK has ever had "complete" data for that node (e.g. because you overwrote the whole node or because you were previously online and got complete data for it), then it will fire events when you update it.
One workaround would be to use ChildAdded (and ChildChanged, etc.) events instead of Value.
Related
The docs for the current Firebase Admin SDK as well as the 2.5.1 Firebase iOS SDK (now legacy) mentions some guarantees regarding events, the most relevant one to me being:
Value events are always triggered last and are guaranteed to contain updates from any other events which occurred before that snapshot was taken.
Does this still stand with respect to the iOS SDK (the current docs for which have since removed the table containing the guarantees), especially when interlacing observe and observeSingleEvent calls? In other words, if I were to call this code on app startup:
ref.child("users").observe(.childAdded) { snapshot in
print("Child added")
}
ref.child("users").observeSingleEvent(of: .value) { snapshot in
print("Value event")
}
Will I get a guarantee that the childAdded events are fired before the value event? In other words, will I get something this in the console, assuming there are 3 children under users?
Child added
Child added
Child added
Value event
More context:
I'm trying to load an initial blob of data from Firebase, after which I want to inform the application that it has received all initial data. In other words, I want to do something like this answer has suggested. Some simple experiments affirm that the guarantee is maintained, but this answer also suggests that interlacing observe and observeSingleEvent calls when local data is available will break the guarantee.
Exactly like on Twitter I've got a little bubble that appears at the top of my view when new posts come in that prompt the user to do a pull to refresh.
I'm basing this all off a .value observer of a "invitedPosts" node in Firebase. When a user receives or sends a post, the information gets sent into this invitedPosts section. The reason the post the user sends himself is sent to invitedPosts is simply because it makes the rest of my app so much easier to work with..this is the one exception. Refactoring at this point would be a massive rework of the whole app.
I obviously don't want for this bubble to show up when a user posts, so I figured I'd handle this client side by something like this. (Pseudo code)
//User hits the post button, sets a bool to false
func takingAPictureForPost(){
self. shouldCheckForPosts = false
}
//AVCapture session has taken the photo, information is sent to Firebase
func pictureTakenAndPostDataAvailableToSendToFirebase(){
firebase.setValue(post)
}
//Checks against shouldCheckForPosts before making a bubble
invitedPostsRef.observe(value){
if shouldCheckForPosts {
makeNotification()
} else {
//If it was false because the user just made the post, set it back to true so new posts can be seen.
shouldCheckForPosts = true
}
}
}
The glaring problem with this is if at the beginning of the posting process to firebase, asynchronously a node from inviteToPosts gets added to deleted, and that "eats up" the check for shouldCheckForPosts, so then when the user finishes posting it shows the notification for their own post.
I would just like to use .childAdded and check the actual userUID on the post vs my logged in user's, but childAdded does an initial check on every single node which triggers once for each, so the notification is then fired off a million times initially, and I don't see a way to run code after childAdded is done running through all of the initial children. At that point I'd just set a "firstTimeLoad" bool to check against to prevent the notifications from popping up during that first mass check, then check each incomming childAdded against my usersUID.
How should I do this?
Any ideas on how best to do this?
I am trying out Realm.io on my Swift project. The insertion and update of objects are pretty straightforward, but here comes a problem: I am not able to catch a new object insertion/update notification.
What I want to achieve is simple, I save a list of objects in Realm. And upon app start/refresh, the app will request a new list of objects from remote server and then perform realm.add(objects, update:true) (I've set id as the object's primary key so that the same objects will not be duplicated), then the UI end of my app should be notified whenever there's a new object, or any existing objects have been updated.
I've tried using realm.addNotificationBlock(_:) but it's called every time with a RLMRealmDidChangeNotification event, even though there is no new object/update.
How do I achieve this?
Edit: code sample
public class DataStorageManager {
var token : NotificationToken?
static let sharedInstance = DataStorageManager ()
public func saveListA(list: [A]?, realm:Realm) {
self.token = realm.addNotificationBlock({ (notification, realm) -> Void in
print("database changed")
})
if list?.count > 0 {
try! realm.write {
realm.add(list!, update:true)
}
}
}
}
You should call addNotificationBlock only once and not everytime you call saveListA. So you could move it to the DataStorageManager's init method.
But you wrote that you want to update your UI whenever the list is updated, so instead of having the token inside your DataStorageManager class you could directly add the NotificationToken as a property to your UIViewController class and call addNotificationBlock in your view controller's viewDidLoad method. Then you can directly update your UI inside the notification block.
EDIT:
If you only want to update your UI when certain data gets updated you cannot use Realm's notification system (which sends a notification everytime any data is changed).
You can do one of the following
Use KVO on your Realm objects. This is described in the Realm Docs
Send your own NSNotification whenever the data is updated that needs a refresh of your UI. So in your case you can send an NSNotification everytime your list gets changed in saveListA. Then you register your view controller as an observer to that notification and update your UI whenever you receive that notification.
As I understand it, you should fetch the desired objects somewhere you want to hold them (like your data manager or view controller), getting Results object and subscribe to this Results change.
Please refer to https://realm.io/docs/swift/latest/#collection-notifications.
It doesn't matter where the object would be changed as long as you hold to that notifications token.
The downside is, currently "modifications" array always include every "touched" element, regardless of if there had been any changes at all. It is considered a bug, but no progress there since August '17.
https://github.com/realm/realm-cocoa/issues/3489
For now, I compare modifications manually after getting the notification.
I am developing an app that fetches data from the web and displays it to the user. Assume that the data is reviews of a restaurant and one review is displayed on one view. The user can swipe left or right to go to the prev/next review. The data is fetched asynchronously (one thread for each review).
Here is the problem statement - Assume that 5 reviews have been fetched and the user is looking at the 3rd one currently. Now, the 6th review is fetched and I want to display it as the 4th review to the user (because the publish date of the 6th review is more recent than the 5th review). How should my model class inform the view controller?
I have considered some options -
Provide an array to the view controller and then send NSNotifications about new items to be inserted in-between the array at a specific index
Use an NSFetchedResultsController (this is a bit tricky because I am not using it with a table view controller)
View controller always asks for the next review to be displayed (from the model) and does not have a array of reviews with it
Are there any established design patterns that are employed in such a scenario? Other suggestions apart from the 3 above are welcome!
Just use an NSFetchedResultsController. When using NSIndexPaths just ignore the section. It's basically a glorified NSArray with free notifications.
Here's how I think I'd do it:
Make sure that the NSFetchRequest for your NSFetchedResultsController is sorted by publish date.
Handle NSFetchedResultsControllerDelegate methods.
When the NSFetchedResultsController updates, save the current object, reload the collection view, and then scroll to the saved object without any animation. This will appear to the user as if nothing happened to the current page.
While there is no perfect design pattern for every programming problem, the closest I can think of that relates to your problem is a combination of the Command and Observer patterns.
https://en.wikipedia.org/wiki/Command_pattern
The observer pattern is used in the NSNotification center.
While it's unclear as to why you'd want to skip a review, you could have two arrays to store them when fetched. The first holds all reviews that you have fetched. The second holds all reviews that are displayed.
Then you can get the last review in the fetched array, as if it were a stack. This way you always have the last one loaded displayed to the user.
I am confused why the order of display is different than the true order, ie why the 6th review comes before the 5th, but you asked about patterns to help.
Apart from MVC and observer, which are in the other answers and comments, I'd suggest using lazy loading with a virtual proxy. When reviews have been fetched, you can just display their proxy (eg with a "loading..." Message until they're fully in memory).
See more here: http://en.wikipedia.org/wiki/Proxy_pattern
I would recommend using the observing pattern to inform your controller than new data as been fetched. When receiving the signal, your view controller could update its array of "restaurant review" (either by adding the old one and reordering it according to some sort descriptors of your flavor or by querying the DAO directly).
Let's say you are fetching your data from internet and populating a CoreData entity with the results. Once you got your downloaded data you can populate your core data "Review" entity.
In order to "listen" at the change happening in core data, your controller should, in the viewDidLoad body, register itself as an observer for the NSManagedObjectContextDidSaveNotification.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(updateInfo:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then in your updateInfo, you can get the changes
- (void) updateInfo:(NSNotification *)notification
{
self.reviews = [self.managedObjectContext performRequest:myFetchRequest error:nil];
}
There are several view controllers in my app where I need to sync the local contents with server using a method running in a background thread. Sometimes I need to insert data to my database on server if user has created any. The approach I am using here is to set a flag(something like isSynced = NO) on objects that I need to sync with server (there objects are in Core Data). When the syncing is complete my method will get rid of the flag(e.g. isSynced = YES) so it won't be sent again next time.
Now the problems is that the syncing method takes very long to complete(1 or 2seconds.). If now user pops out this particular view controller and swiftly comes back the previous call is still in progress and next one will be kicked off. The consequence is that there might be duplication in database.
My approach now is the make the syncing method to be called by a Singleton object:
#property (nonatomic) BOOL isSyncing;
//every time before syncing. check if object is available for syncing
if (!isSyncing) {
isSyncing = YES;
// sync server
// when complete
isSyncing = NO;
// post notification to view controller to reload table
} else {
// cancel because previous call is not finished
}
My concern is that if the call is cancelled my view controller will not be able to receive the notification is waiting for. I can fix this by posting another notification in the event of cancelation. I am wondering if this is the right to do this because I think that this problem should be pretty common in iOS development and there should be a standard way to deal with it
Your singleton approach may not be necessary. I don't see the harm in sending a database insert for each new object. You will still need to ensure each object is synched. That is, update the "isSynched" flag. Keep each object that needs to be synced in a "need to synch" list.
Then, update the "isSynced" flag by performing a background query on the database to check if the object exits. Then, use the result of the query to set the isSynched flag.
If the query result indicates the object is not in the database you then resend the object and leave it's "isSynced" flag set to NO.
If the query result indicates the object is in the database, set the "isSynced" flag to YES and remove it from your "need to synch" list.
An approach for preventing duplicate database entries is to make a unique key. For example, tag each with a hash based on the time and date. Then configure the table to ensure each key is unique.