Parse - downloading only updated rows of Data - ios

I am running parse server on AWS. Data is being stored on mlab. At app launch I make a query:
let query = PFQuery(className: foodDataClassName_)
query.findObjectsInBackgroundWithBlock({(objects : [PFObject]?,error : NSError?) -> Void in
})
It returns me all the rows of data. I save them locally using CoreData. When any row of data is updated, I delete the previous locally stored data and download all the data again and save it. This is not a good approach. What I want is that I only download the rows which are updated not all the rows. How can I achieve that? Thanks.

what you can do is the following:
The first time the user log in to your app you need to query for all the user rows from the server so in this case you will execute the query without any condition. In this query you can use limit in order to limit the results that will be returned by the server. When you get results from the server you will need to:
Store all rows in your local database
Store the current NSDate inside NSUserDefaults
The next call to your server will be to get only the updated rows. In order to achieve it you will need to:
Get the last sync date from your NSUserDefaults (the one that we save above)
Execute the query but this time with a condition of greaterThan your lastSyncDate
at the end your code to fetch the items should look like the following:
// check if we synced the date before
let lastSyncDate = NSUserDefaults.standardUserDefaults().objectForKey("lastSyncDate")
let query = PFQuery(className: "MyParseObjectClassName")
if (lastSyncDate != nil){
// get only records that were created/updated since the last sync date
query.whereKey("updatedAt", greaterThan: lastSyncDate!)
}
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
// store objects in parse local data store
if (objects?.count > 0){
PFObject.pinAllInBackground(objects)
}
// save the last sync date in user defaults
NSUserDefaults.standardUserDefaults().setValue(NSDate(), forKey: "lastSyncDate")
NSUserDefaults.standardUserDefaults().synchronize()
}
Please notice here i used parse local data store which allows you to store parse objects to your local data base easily without using core data. Parse local data store is provided by parse iOS SDK and save a lot of time and effort for you so i strongly recommend you to leverage it.
local data store also take care for you to objects that were created/updated and it will automatically create new object and will update existing ones.
you can read more about it in here

Related

Is there a way to pull the last CoreData Entry only?

I have a dashboard view that I'd like to display a few variables from my last entered CoreData entry. However, I can't figure out how to fetch only the last data entered into a variable so I can display it. Any ideas?
EDIT: I'm trying to setup a NSFetchRequest inside of a called function that is called only onappear. However, I'm getting errors and am lost.
func singleEntryPull() -> [Item] {
let request: NSFetchRequest<Item> = Item.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "todaysDate", ascending: false)]
request.fetchLimit = 1
let singleEntry = FetchRequest(fetchRequest: request)
return singleEntry
}
And then the return from the function should only show 1 result and I can then use the returned value to display the variables I need?
Well, not sure if this is the cleanest or best way to do this but I got it working like I want for now until a better solution comes up. I'm still using #FetchRequest which im now aware is pulling data live and updating it, but that might work as if someone keeps the app open overnight and updates it in the morning, I'd want it to display that latest entry. I used this:
ForEach(singleEntry.prefix(1)) { item in
A fetch limit of 1 on a fetch request will return you a single value. However, when you're setting up a #FetchRequest, you're doing more than this - you're making the initial fetch and then continuing to monitor the context for changes, so it live updates. This monitoring only uses the predicate of your fetch request.
Depending on your order of operations, you could be seeing the latest data, and then any new data inserted since you started that view. My experiments with the SwiftUI core data template project prove this out - on initial run you get the a single latest entry, but as you add newer ones, the fetch-limited screen picks up the new entries.
Depending on how this view is actually used, you have two choices - you can do an actual fetch request on appear of the view and store the result as an observable object, or you can make sure you only ever use the first record from the fetch request's results array, which will always be the latest record because of your sort ordering:
var body: some View {
if let latest = singleEntry.first {
// Some view describing the latest entry
} else {
Text("No record")
}
}

How to remove all data from DynamoDB without for loop?

Suppose i have 100 data in DynamoDB then to remove all data i have to execute for loop .So is there any way to remove all data without for loop ?
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
for var key in 0 ..<100 {
let itemToDelete: CategoriesDB = CategoriesDB()
itemToDelete._index = key
dynamoDbObjectMapper.remove(itemToDelete, completionHandler:
{(error: Error?) -> Void in
if let error = error {
print(" Amazon DynamoDB Save Error: \(error)")
return
}
print("A Category was deleted.")
})
}
If you want to remove all the data in a DynamoDB table, it is better to just delete the table and create it again as that wouldn't cost Write Capacity Units.
Ignoring the swift SDK for a moment, DynamoDB has the DeleteItem method to remove a single item from a table, but it also has a batch method.
Quote
In addition to DeleteItem, Amazon DynamoDB supports a BatchWriteItem
action for deleting multiple items at the same time.
The problem is, I can't see a way to access BatchWriteItem through the swift SDK. Even if you could use BatchWriteItem, it can only take 25 items at a time, so you would still end up having to write a loop.
Sorry its not a more complete answer, but thought this might be useful. As far as I know there isn't a simple (single) method for doing this.

CloudKit: CKFetchRecordChangesOperation, CKServerChangeToken and Delta Download

My question is related to the "Delta Download" thing as it was named in WWDC 2014 Advanced CloudKit.
I'm trying to make syncronization for my Core Data app, which is iPhone only for now (think: there is only one device active). So, basically the app will store user records in the cloud from one same device, for the most cases for now.
I have trouble with understanding custom zone feature which is based on CKFetchRecordChangesOperation aka Delta Download.
As I got it right, we have CKServerChangeToken's to maintain sync operations (I mean download only those records which was added/modified/deleted by another device), as was presented on WWDC.
But, what I can't understand is that we recieve that token only after CKFetchRecordChangesOperation, when we save records to the cloud we don't get new token.
And if we make fetch with the current available token (since it changes only after fetch), we recieve records that was saved from our previous save operation. Basicaly we get save recods that already have on our device. Why? I'm missing something here?
What if we seeding some data to the cloud (from device A), it is justified for situation when device B is fetching the zone records, but what if device A be? Download all the records again?
I found recordChangeTag in the CKRecord, is this a property I can use for resolving conflicts with local objects - fetched objects (same or different version), if so can somebody give me example of how I need to do this: save recordChangeTag to Core Data when save record to CloudKit for the first time or how?
The lack of documentation is such a headache.
I found a time to write an answer for this question. I won't dig into implementation, but I will discuss the concept.
CloudKit provides a way to data synchronisation between your device and the CloudKit server.
What I use to establish synchronisation process in my case between iPhone and server only (again, if you have iPhone + iPad app, the process require more steps.):
I have custom zone in the private cloud database.
I use OperationQueue to establish different asynchronous processes which depend on each other. Some operations have own operation queues.
Steps:
1) Check if my custom zone is exist
1.1) If there is no custom zone
1.2) Create new custom zone. (Optional: add records)
1.3) Refresh zone change token
You can refresh zone change token by: performing
CKFetchRecordChangesOperation,
fetchRecordChangesCompletionBlock returns CKServerChangeToken
save it to UserDefaults (for example) using NSKeyedArchiver). This operation's task is to refresh token and it's performed at the end synchronisation process.
2) If there is custom zone already
2.1) Get changes from zone using previously saved zone change token. (CKFetchRecordChangesOperation)
2.2) Update and delete local records.
2.3) Refresh zone change token.
2.4) Check for local changes (I'm using last cloud sync timestamp to check what records was modified after).
2.5) Upload records to cloud kit database
2.6) Refresh zone change token again.
I highly recommend Nick Harris article series: https://nickharris.wordpress.com/2016/02/09/cloudkit-core-data-nsoperations-introduction/
You'll find there implementation and design concepts. It worth reading. I hope somebody'll find all of this helpful.
As of iOS 13 there is a super helpful method in Core Data called NSPersistentCloudKitContainer. This method will automatically take care of all local caching and syncing with iCloud on private databases. You can set it up by simply changing
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "ShoeTrack")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
to
static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "ShoeTrack")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
You will have to modify the Core Data Model file in your project and check "Use with CloudKit on each configuration.

Parse PFObject.fetch is not updating to current data from server

In my app, I have a screen where the user can edit an object's attributes. The screen has 2 actions: save or revert.
When revert is selected, I want to revert the object's attributes to whatever is stored on the server and discard all local changes. I do this with the the fetch method:
println("1: \(localObject)")
if localObject.isDirty() {
localObject.fetchInBackgroundWithBlock { serverObject, error in
println("2: \(localObject)")
println("3: \(serverObject)")
}
}
isDirty() works correctly and only returns true if something was locally modified and not saved.
According to the docs, fetch: fetches the PFObject with the current data from the server. But what I see in the log is that all 3 print statements show the same local attributes. I would expect serverObject to be whatever is stored on the server, but it's not, serverObject also shows the locally modified attributes.
I'm not using the Local Datastore.
What is going on? Is this a bug with the Parse SDK? Or is there a caching issue?
If you call fetch without using the local datastore, Parse will give you a error in the console.
Since you're not getting an error from Parse when you call fetchInBackgroundWithBlock, it means that at some point you've called localObject.pinInBackground() and placed that object in the local datastore.
For locally stored objects, you can call revert() before fetchInBackgroundWithBlock to discard local changes before updating data from the server.
if localObject.isDirty() {
localObject.revert()
localObject.fetchInBackgroundWithBlock { serverObject, error in
println("2: \(localObject)")
println("3: \(serverObject)")
}
}
I haven't seen the docs for fetch, but I do know that you can accomplish what you're trying just by performing a query. Doing the following will query from the server (although you can adjust the cache policy if you've already used the query to look at cache or network first):
var query = PFQuery(className:"GameScore")
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error == nil && gameScore != nil {
println(gameScore)
}else {
println(error)
}
}
Alternatively, you could just save a copy of the local object before any changes can be made, and then if it is dirty, you can just replace it with the copy. That's probably what I'd do to reduce the number of parse API calls.

How can i save Data to a User in Parse?

I want to backup the Data of an UITableView to Parse. First the User logs in. Now every user should have it's own cloud Storage save of their UITableView, but how to upload this UITableView or the UITableViewCell when pressing on a Button (Save)?
You can create separate Model objects for populating each cell of the UITableView . Saving objects into parse is very well in explained in the documentation:
Also take a look at this tutorial if you still need more explanation:
Tutorial
Parse is just a backend which allows you to save data by calling their functions.
The concept of parse is to show the data in your app by using their api and database.
An example to save a PFObject (A parse object) in the parse database:
let testObject = PFObject(className: "TestObject") // Create the PFObject with the classname/tablename on parse
testObject["foo"] = "bar" // set the property "foo" to "bar"
// save the object in background (not on the gui thread to prevent lags on the UI)
testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
println("Object has been saved.")
}
ATTENTION: Before to be able to call these functions and create there objects you need to import the Parse Library and initialize your app to parse.

Resources