Parse Local Datastore + Network Sync - ios

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.

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.

AWSKinesisRecorder get the records that are not streamed

In AWSKinesisRecorder (here), how can we check if our records are submitted to the server / reached the AWS or check if we have records on disk that are not yet submitted?
kinesisRecorder.submitAllRecords()?.continueOnSuccessWith(block: { (task: AWSTask<AnyObject>) -> Any? in
if let error = task.error as NSError? {
Logger.log(method: .error, "\(#function) \(#line) \(#file)", "Error: \(error)")
}
if let result = task.result {
Logger.log(method: .info, "\(#function) \(#line) \(#file)", "Result: \(result)")
}
print("FINISHED AWSTask kinesisRecorder", task, task.error, task.isCompleted, task.isFaulted, task.isCancelled)
return nil
})
The completion block never returns an error neither does the task.result is also nil, even if the internet is turned off on the device.
Not Possible
Seems like there is no public API available to fetch the records that are written onto the local mobile storage, neither you can read the sent records from Kinesis.
Its aim is to stream data in a unidirectional way.
I had to create another API to get the details of records received on the server end and had to rely on the Kinesis that each record is 100% written safely onto the local storage. So, far I have not seen any data loss.

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

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)")
}
}
}
}

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: Does not query saved objects in local datastore

I am currently developing a inventory app. My goal is to retrieve objects from Parse and then saving onto the local datastore. Querying objects from Parse and saving them works (because of the console message) but querying later on from the local datastore, does not retrieve anything! Here's my code:
let query = PFQuery(className: "Publication")
query.limit = 150
query.selectKeys(["publication_id","publication_Type","publication_Name"])
dispatch_async(dispatch_get_main_queue()) { () -> Void in
query.findObjectsInBackgroundWithBlock({ (pubObject, error) -> Void in
if error == nil {
print("Succesfully retrieved \(pubObject!.count)")
PFObject.saveAllInBackground(pubObject, block: { (success, error) -> Void in
print("Saved \(pubObject!.count) in local DataStore")
})
}
})
}
This message comes out from the XCode console:
"Succesfully retrieved 103
Saved 103 in local DataStore"
So far so good right?
This is my code when I am about to query from the local datastore:
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let bookQuery = PFQuery(className: "Publication")
.fromLocalDatastore()
bookQuery.whereKey("publication_Type", equalTo: "Book")
bookQuery.findObjectsInBackgroundWithBlock { (bookObject, error) -> Void in
if error == nil{
print("Books found: \(bookObject!.count)")
self.displayData(bookObject!)
}
}
}
And I get from the console: Books found: 0.
What gives? What am I doing wrong? I read and read and read. NOTHING. I thought the ".ignoreACL()" would work but it didn't. Can anyone help me please?
I don't see where you are pinning the PFObjects into the local datastore. Perhaps that is your problem.
Calling any of PFObjects save methods saves them back to your parse server, not a local datastore. Look up how to use pin to accomplish what you want.
Also, dispatching these asynchronous calls to the main queue makes no sense. They are already executing on a background queue. In most cases, you only need to dispatch back to the main queue if you want to do something to the UI and that should be done in the completion handler.

Resources