Parse.com syncing network with Local Datastore - pinning & unpinning issue - ios

I have a Parse backend setup where I have three classes:
User
Place - with restaurant information
SavedPlace - an object modelling the User and Place relationship with pointers to both User and SavedPlace.
What I'm trying to do is to be unable to sync my network and local datastore but only where there are changes i.e. only for SavedPlace objects that are different in the network and local (using updateAt). However, I'm running into issues with pinning and unpinning and I've looked everywhere including the following post below but I cannot seem to resolve it.
Parse Local Datastore + Network Sync
See my code for this function where I want to fetch only the updated SavedPlace objects, unpin the old ones in Local Datastore, and re-pin into the Datastore.
The issue seems to be when I re-pin the updated SavedPlace objects retrieved from the network - it seems to delete the Place objects in the Local Datastore. As you can see in the image below, there are both SavedPlace and Place objects pinned and re-pinning the SavedPlace objects delete all the Place objects except for the SavedPlace object I repinned.
SQLite of Local Datastore
Any way round this? Am I using pinAllInBackground correctly?
Appreciate your help on this.
func fetchUpdatedSavedPlacesRemotelyAndPinLocally() {
if let user = PFUser.currentUser(),
let lastUpdateLocalDatastore = self.userDefaults.objectForKey("lastUpdateLocalDatastore") {
// Fetch the places from Parse where lastUpdateDate in Parse is newer than the lastUpdateLocalDatastore
print("Current lastupdateLocalDatastore: \(lastUpdateLocalDatastore)")
let savedPlaceQueryParse = PFQuery(className: "SavedPlace")
savedPlaceQueryParse.whereKey("user", equalTo: user)
savedPlaceQueryParse.includeKey("place")
savedPlaceQueryParse.includeKey("objectId")
savedPlaceQueryParse.whereKey("updatedAt", greaterThanOrEqualTo: lastUpdateLocalDatastore)
savedPlaceQueryParse.findObjectsInBackgroundWithBlock {
(updatedSavedPlacesNetwork: [PFObject]?, error: NSError?) -> Void in
if let updatedSavedPlacesNetwork = updatedSavedPlacesNetwork {
if updatedSavedPlacesNetwork != [] {
print("Success - retrieved \(updatedSavedPlacesNetwork.count) updated places from Parse")
// Create an array of objectIds of the updated saved places to match against in the Local datastore
var updatedSavedPlaceObjectId = [String]()
for updatedSavedPlaceNetwork in updatedSavedPlacesNetwork {
updatedSavedPlaceObjectId.append(updatedSavedPlaceNetwork.objectId!)
}
// Fetch these updated saved places from the Local Datastore
let savedPlaceQuery = PFQuery(className: "SavedPlace")
savedPlaceQuery.fromLocalDatastore()
savedPlaceQuery.whereKey("user", equalTo: user)
savedPlaceQuery.includeKey("place")
savedPlaceQuery.includeKey("objectId")
savedPlaceQuery.whereKey("objectId", containedIn: updatedSavedPlaceObjectId)
savedPlaceQuery.findObjectsInBackgroundWithBlock {
(updatedSavedPlacesLocal: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let updatedSavedPlacesLocal = updatedSavedPlacesLocal {
// Unpin the updated saved places from the Local Datastore
PFObject.unpinAllInBackground(updatedSavedPlacesLocal) { (success: Bool, error: NSError?) -> Void in
if (success) {
print("Success - \(updatedSavedPlacesLocal.count) updated saved places unpinned from Local Datastore")
// Pin the updated saved places from Parse to the Local Datastore and update the lastUpdateLocalDatastore
PFObject.pinAllInBackground(updatedSavedPlacesNetwork) { (success: Bool, error: NSError?) -> Void in
if (success) {
print("Success - \(updatedSavedPlacesNetwork.count) updated saved places pinned to Local Datastore")
self.userDefaults.setObject(NSDate(), forKey: "lastUpdateLocalDatastore")
print("New lastUpdateLocalDatastore: \(self.userDefaults.objectForKey("lastUpdateLocalDatastore"))")
}
else {
print("Fail - updated saved places not pinned and returned with error: \(error!.description)")
}
}
}
else {
print("Fail - updated saved places not unpinned and returned with error: \(error!.description)")
}
}
}
}
else {
print("Fail - updated saved places not fetched in Local Database and returned with error: \(error!.description)")
}
}
}
else {
print("No updates")
}
}
else {
print("Fail - load from Parse failed with error: \(error!.description)")
}
}
}
}

Related

Parse won't save objects in local datastore after I close application and disconnect from Wi-Fi

I'm trying to save some objects locally so when I close my application and disconnect from my Wi-Fi I'll still be able to have the data but this does not work.
I have created this function to save my database.
func saveAllQueriesLocally() {
let queries = PFQuery(className: "Directory")
PFObject.unpinAllObjectsInBackground()
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
} else if let object = object {
// The query succeeded with a matching result
for i in object{
i.pinInBackground()
}
} else {
// The query succeeded but no matching result was found
}
}
}
and I have called this function inside viewDidLoad() of my main ViewController. I am not sure but I am guessing the function searches the database when it is offline and since it does not retrieve anything it rewrites the cache as empty.
While being connected to the internet, objects get retrieved correctly.

PFQuery always return same results even though Parse server changed

I'm developing an iOS project using Parse.com as backend server.
Basically, I'm currently implementing a very basic feature which just simply retrieve some objects with simple condition.
However, the objects can only be correctly retrieved the first time. No matter how I changed any values in Parse "Core" via Web, I still cannot get updated values by refreshing in the app.
For example, I have a class called "Event", the fields are changed from Parse server, but the result I retrieve are never updated.
let eventServerQuery = Event.query()
// I tried to clear all cached results
PFQuery.clearAllCachedResults()
eventServerQuery?.whereKey(EventFields.Campus.rawValue, equalTo: campus!)
eventServerQuery?.findObjectsInBackgroundWithBlock({ (allEvents, error) -> Void in
self.refreshControl?.endRefreshing()
self.toggleRefreshButtonWithSpinner(false)
if error != nil {
print(error?.localizedDescription)
}else{
if allEvents?.count > 0 {
// Display on the map
for eventObject in allEvents! {
let event = Event.initializeFieldsFromPFObject(eventObject)
self.delegate?.addEventToMap(event)
self.events.append(event)
print("\(event.updatedAt)")
print("\(event.title) has \(event.numberOfTasks) tasks")
}
// Event TVC data source
self.tableView.reloadData()
}
}
})
If I delete the app in my device and run the project again, it will of course reload everything from scratch, so that the data will become correct again...
Any help will be appreciated!
Finally, I worked out by myself. I found that whenever the PFObject was pinned, its fields will not be updated. The solution is that the object need to be unpinned before retrieve from server.
Event.unpinAllInBackground(events, block: { (success, error) -> Void in
if error != nil {
print(error?.localizedDescription)
}else{
self.events.removeAll()
let eventServerQuery = Event.query()
eventServerQuery?.whereKey(EventFields.Campus.rawValue, equalTo: self.campus!)
eventServerQuery?.findObjectsInBackgroundWithBlock({ (allEvents, error) -> Void in
print("Debug: retrieving events from server")
self.refreshControl?.endRefreshing()
self.toggleRefreshButtonWithSpinner(false)
if error != nil {
print(error?.localizedDescription)
}else{
if allEvents?.count > 0 {
// Display on the map
for eventOnline in allEvents! {
let event: Event = eventOnline as! Event
event.pinInBackground()
self.delegate?.addEventToMap(event)
self.events.append(event)
}
// Event TVC data source
self.tableView.reloadData()
}
}
})
}
})
Welcome to add comments here regarding the internal logic of Parse library, as sometimes it is not quite clear I think.

Parse Local Datastore + Network Sync

I am using Parse.com with my iOS application (written in Swift) since 6 month and I would like to use Parse local Datastore for many reasons :
Make my application usable (retrievable) offline
Reduce data usage (many queries returning « non updated data »)
Reduce loading time (mainly when starting application and loading all data from network)
In order to do so I would like to write a global function handeling these scenarios for all the query I do from my application.
I already have a specific idea of what the function should do but I don’t know how to technically write this function :)
Scenarios :
Sign Up / Log In (chain multiple query) :
Get data from Network
Save date inside « lastUpdateLocalDatastore » variable in NSUserDefaults
Pin data in Local Datastore
Display data from Local Datastore —> RETURN & update TableView
Loading App (chain multiple query) :
Display data from Local Datastore —> RETURN & update TableView
Get data from Network (where « lastUpdateDate » in Parse is newer than « lastUpdateLocalDatastore » from NSUserDefault)
Pin data in Local Datastore
Display updated data from Local Datastore —> RETURN & update TableView
Trigger update (simple query) :
Get data from Network (where « lastUpdateDate » in Parse is newer than « lastUpdateLocalDatastore » from NSUserDefault)
Pin data in Local Datastore
Display updated data from Local Datastore —> RETURN & update TableView
Log Out :
Unpin all data in Local Datastore
Clear « lastUpdate » values in NSUserDefault
Function structure :
IF ( "First login" -> Local Datastore is empty ) {
Get data from Network
Pin data in Local Datastore
Save « lastUpdateLocalDatastore » in NSUSerDefaults
—> RETURN data in Cache
} ELSE {
IF ( "Launching application" -> Cache is empty ) {
Get data from Local Datastore
—> RETURN data in Cache
} ELSE IF ( "trigger update" ) {
Get data from Network
Pin new data in Local Datastore
Save « lastUpdateLocalDatastore » in NSUSerDefaults
—> RETURN data in Cache
}
}
Problems :
How to handle multiple (asynchronous) returns
How to make a function capable of chaining multiple queries (for example I need to retrieve data from 6 different queries when I load my app)
Finally I found a way to do it based on this GitHub topic :
https://github.com/ParsePlatform/ParseUI-iOS/issues/53
Here is the function I use :
func findObjectsLocallyThenRemotely(query: PFQuery!, block:[AnyObject]! -> Void) {
let localQuery = (query.copy() as! PFQuery).fromLocalDatastore()
localQuery.findObjectsInBackgroundWithBlock({ (locals, error) -> Void in
if (error == nil) {
println("Success : Local Query", msg: "\(query.parseClassName)")
block(locals)
} else {
println("Error : Local Query", msg: "\(query.parseClassName)", error: error)
}
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if(error == nil) {
println("Success : Network Query", msg: "\(query.parseClassName)")
PFObject.unpinAllInBackground(locals, block: { (success, error) -> Void in
if (error == nil) {
println("Success : Unpin Local Query", msg: "\(query.parseClassName)")
block(objects)
PFObject.pinAllInBackground(objects, block: { (success, error) -> Void in
if (error == nil) {
println("Success : Pin Query Result", msg: "\(query.parseClassName)")
} else {
println("Error : Pin Query Result", msg: "\(query.parseClassName)", error: error)
}
})
} else {
println("Error : Unpin Local Query", msg: "\(query.parseClassName)", error: error)
}
})
} else {
println("Error : Network Query", msg: "\(query.parseClassName)", error: error)
}
})
})
}
TO DO : I need to add the "lastUpdateDate" option to fetch only modified data from network.

How to prevent duplicate entry on parse?

I' trying to save song info to parse, but if the song already exist in parse I want my code just do nothing.
I've tried this code below:
var Music = PFObject(className:"Musics")
var query = PFQuery(className:"Musics")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
var songTitle = object.objectForKey("songTitle") as? String
if songTitle != title {
Music["createdBy"] = PFUser.currentUser()
Music["songTitle"] = title
Music["albumCover"] = imageFile
Music["songArtist"] = artist
Music.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
println("succeed")
} else {
// There was a problem, check error.description
println("error jeh")
}
}
}else{
println("song already exist")
}
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
the code above give below result on log:
Successfully retrieved 4 scores.
song already exist
Successfully retrieved 4 scores.
song already exist
Successfully retrieved 4 scores.
song already exist
Successfully retrieved 4 scores.
song already exist
succeed
succeed
succeed
succeed
succeed
succeed
succeed
succeed
succeed
succeed
succeed
succeed
Why my for loop , looping more than the Objects.count? and how can I prevent dupiclate entry on parse?
give me any advice, doesn't matter in obj c or swift
I suggest to implement a simple beforeSave trigger, on Parse Cloud code, in order to check if the new entry song already exist (basically you're going to make one or more field uniques. For example:
Parse.Cloud.beforeSave("Musics", function(request, response) {
var newEntrySong = request.object;
var querySongs = new Parse.Query("Musics");
querySongs.equalTo("title", newEntrySong.get("title"));
querySongs.equalTo("description", newEntrySong.get("description"));
// this could be a sort of signature for your song, to make more unique (skipping spaces and new lines for example)
querySongs.equalTo("md5Title", newEntrySong.get("md5Title"));
querySongs.first({
success: function(temp) {
response.error({errorCode:123,errorMsg:"Song already exist!"});
},
error: function(error) {
response.success();
}
});
});
Hope it helps.

Parse Local Datastore: Unpin objects seems broken in Swift

I want to unpin a list of objects, which I had successfully locally stored earlier, and replace it with a new one. The code below should do that trick, but the locally pinned objects simply don't get updated. I tried everything including PFObject.unpin, nothing removes the old pinned objects except a complete reset of the simulator
func updateCountryList(server:Int, local:Int) {
let query = VEPCountry.queryAll()
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error != nil {
// throw error
} else {
if local != 0 {
VEPState.unpinAllObjectsWithName(String("countryListVersion\(local)"))
}
VEPState.pinAll(objects, withName: String("countryListVersion\(server)"))
defaults.setObject(server, forKey: "localCountryListVersion")
}
}
}
Appreciate help or pointer to known issues around unpinning in Swift
I wonder if your unpin has't really finished, it's going off to the database after all.
Can you try:
query
.findObjectsInBackground()
.continueWithSuccessBlock({ (task: BFTask!) -> AnyObject! in
// ...
return VEPState.unpinAllObjectsWithNameInBackground("name"))
})
.continueWithSuccessBlock({ (task: BFTask!) -> AnyObject! in
// ...
return VEPState.pinAllInBackground(objects, withName: "name"))
})
I may have the syntax a little off and the background method names not quite right. Also I'm using promises/tasks which is not a bad habit to get into.

Resources