RestKit: How to reject an entire mapping when a value is not valid - ios

So I'm trying to support offline usage of an iOS application I'm making that uses a REST API. Here's what I have so far:
A server running with a REST interface to manipulate my data model.
An iOS application that uses RestKit to retrieve the data stored on my server.
RestKit stores server responses locally in Core Data.
When the server is unavailable I still want users to be able to update the data model and then when the server becomes available again I want those updates to be pushed to the server.
The issue I've run into is that when a value that has been updated locally (but not pushed to the server yet) is received from the server it is overwritten with the contents of the server's response. To prevent this I am trying to cancel the save to my local storage if the new 'updatedAt' date is before the current 'updatedAt' date.
This is my current validation function:
- (BOOL)validateUpdatedAt:(id *)ioValue error:(NSError **)outError
{
if(self.updatedAt && [self.updatedAt compare:((NSDate *)*ioValue)] == NSOrderedDescending) {
*outError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorMappingDeclined userInfo:nil];
return NO;
}
return YES;
}
This works, but only prevents this individual value from being changed. I want the entire update of that object to be canceled if this one field is invalid. How do I do this?

The support available from RestKit is to set discardsInvalidObjectsOnInsert for the whole object to be discarded when validation fails. But, this won't work for you as it only works for NSManagedObject instances that are being inserted, because it uses validateForInsert:.
You could look at using validateForUpdate: to perform a similar check and then reverting the changes, but RestKit isn't really offering you anything in terms of the abort in your case.

Related

Is there a way to tell when a document has been synced with the database from the cache?

I am building an instant messaging app, akin to WhatsApp, and I need to display the send status of the message: Sending, Sent; sending being if it is the Firebase cache, due to being offline, and sent if the message is on the online database.
Is there a way to tell if a specific document has been uploaded to the database?
I currently have a live update listener listening on the messages collection in firestore that fires when either the cache or database updates. It can tell where the update came from but that returns all of the messages and doesn't specify any individual documents.
Here is a snippet of the completion block for the listener.
let source = snapshot.metadata.isFromCache ? "local cache" : "server"
print ("metadata: data fetched from DB: \(source)")
for diff in snapshot.documentChanges {
print("diffData: \(diff.type.rawValue)")
print("messageId: \(diff.document.documentID)")
switch diff.type {
case .added:
print("added")
case .removed:
print("removed")
case .modified:
print("modified")
}
}
var messages: [ChatEntry] = []
let deletedIds = [String]()
let permanentlyDeletedIds = [String]()
for document in snapshot.documents {
if document.exists {
let message = ChatEntry(fromSnapshot: document)
print("MessageId: \(message?.id ?? "null")")
print("messageText: \(message?.message ?? "Null")")
}
}
The callback runs when either the cache or DB updates, but if one updates, then the other updates then the document changes is emptied. meaning the I can't tell what happened.
Any advice would be appreciated, Thanks in advance.
You can find precisely that information in the SnapshotMetadata of a QuerySnapshot, which has an isFromCache property that:
Returns YES if the snapshot was created from cached data rather than guaranteed up-to-date server data.
You'll want to listen for metadata changes to be able to update the UI once the data is synchronized to the server.
My suggestion on this this would be to:
Set the status to "Sending" when you write a value to Firebase from the client - regardless of if you are online or offline (Firebase already handles this process for you so don't reinvent it).
Use your existing callback to notify you of new messages matching that one which you can then mark as "Sent" once you receive them (via the callback).
To make this easier you may even want to create a temporary array of ID's that you expect will be coming back from Firebase once written to the Firebase DB. This will allow you to easily validate that the message sent was the one you expected back.
Let me know if this helps.
Andrew

Bypass Service-Worker caching

I have a progressive web-app, which speaks to an API. The calls to this api get cached by a service worker, which works great.
But now, I want to add a reload-button, which ideally forces the service worker to try to bypass the cache and update it if successful, also it should not return the cached result if a connection could not be made.
I am a bit unsure how to solve this. I am using the sw-toolbox.
All requests go through the fetch callback which receives a request object. Thus, before returning a cached response you can look for an additional header parameter (you need to include it into your request to API) to skip the logic returning cached response.
Based on your description, you are using the application cache. It can be accessed from the app fronted independent of the sw-tool box.
function onReloadButtonClicked(event) {
//Check for browser cache support
if ('caches' in window) {
//Update cache if network query is successful
caches.open('your_cache_name')
.then(function(cache) {
cache.add('your_url');
}).catch(function(err) {
// Do something with the error
});
}
}

Multiple contexts , create only if not exists

I have a view controller that is performing an afnetworking request , and upon the response , I am adding that response to my main MOC , and saving it.
My app is a realtime app , and so , performing certain kinds of requests will send messages from server to client via a long lived web socket. What happens is ,when a client sends a single message , he will receive the same message via socket (and I am going to add apns functionality later on, so he will receive the same functionality via apns as well)
I want the web socket to use a background context, so my UI is taxed less.
Right now I am performing a fetch to see if data already exists. If it does not, then I am adding to the context. This check is being done on the socket side as well as the api side.
My concern is , since they are both going to be checked at such close time intervals to each other, is it possible for them to both say that data doesn't exist, and then both will add to the db, causing an unwanted duplicate?
This is my code for clarification:
if ([Chat fetchById:[jsonBody valueForKey:#"id"] withContext:self.managedObjectContext]==nil) {
NSLog(#"GOT NIL FROM SOCKET");
Chat *chat = [Chat initWithContext:self.managedObjectContext];
chat.id = [jsonBody valueForKey:#"id"];
chat.body = [jsonBody valueForKey:#"body"];
chat.user = [User fetchByID:[jsonBody valueForKey:#"user_id"] inContext:self.managedObjectContext];
NSString *date = [jsonBody valueForKey:#"created_at"];
chat.created_at = [TimeFormatter NSDateFromServerString:date];
chat.group = [Group fetchByID:[jsonBody valueForKey:#"grp_id"] inContext:self.managedObjectContext];
[self.managedObjectContext save:nil];
}

iOS - How does caching work?

I am working on an app representing JSON from web API.
The remote source updates several times a day.
All I want to do is:
// pseudo code
makeRequest() {
if (network not available){
if (cache not exists) {
showEmptyScreen()
}else if (cache exists){
useCache()
}
}else if (network available){
if (cache not exists) {
loadFromRemote()
}else if (cache exists){
if (cache is older than latest update) {
loadFromrRemote()
}else {
useCache()
}
}
}
}
I've read about NSURLCache from Apple and from NSHipster.
It's still confusing that if NSURLCache could do what I want. For instance, how does it work to check if there's a newer version of data without really download anything?
And if NSURLCache could not handle the checking, how could I code it myself?
Thanks for any advice!
How caching behaves is mostly up to you and your server. NSURLCache does not make decisions on its own other than perhaps what to do when its capacity limit is exceeded.
When you are making a request, headers like If-Modified-Since will determine if data is transferred or not by comparing the timestamps of the cached data.
Server based headers such as Cache-Control can also affect how long data remains valid in the cache.
In summary, NSURLCache can handle what you need but the implementation will be based on a combination of the configuration of NSURLCache, how you make requests, how cache control is implemented in responses and whether you override the policies given by the headers.

How to call xml-rpc webservice using objective c

Suppose, the webservice provider's server exposing the webservice in the form http://example.com:8000/api
What is best framework or library to access the webservice in ios 7 project
There are a few marshaling frameworks that support generating an object-graph from XML, however I would simply go for the following:
Invoke the service endpoint. My favorite library is BBHTTP, however you could use AFNetworking, NSURLConnection with gcd or whatever you prefer for asynch network calls.
Extract the relevant contents of the XML payload onto your use-case specific payload object using RaptureXML
I recommend having use-case specific payload objects because they model exactly what is needed for a given service invocation - supporting the notion of contract-first development. This allows you to change you internal model without effecting the integration to external systems. Similarly the external API can change without effecting your model.
You can create a category method on RXMLElement to return the element mapped to a use-case-specific object. A typical mapping usually takes just a handful of lines of code to marshal from wire-format to your payload object for the service invocation.
Here's an example (the code that I took it from wanted the payload wrapped in a SOAP envelope - just ignore that bit).
- (void)request:(MyUseCaseRequstPayload*)request onComplete:(void (^)(MyResponsePayload*))onSuccess
onError:(void (^)(NSError*))onError;
{
//Even more XML! You can stick your payload inside an envelope if you want
SoapEnvelope* envelope = [SoapEnvelope envelopeWithContent:[request xmlString]];
[[BBHTTPRequest postToURL:_serviceUrl data:[envelope data] contentType:#"text/xml"] execute:^(BBHTTPResponse* response)
{
RXMLElement* element = [RXMLElement elementFromXMLData:[response content]];
MyResponsePayload* response = [[element child:#"elementToBeMapped"] asMyObjectType];
if (onSuccess)
{
onSuccess(response);
}
} error:^(NSError* error)
{
LogDebug(#"Got error: %#", error);
if (onError)
{
onError(error);
}
}];
}

Resources