#10 SMP3 OData SDK for iOS – Offline APIs
Hi everyone,
Working with the error archive.
As discussed in the previous blog, we need to manually execute delete operations against the error archive entries to purge them. In theory it is pretty simple, you know how to delete an entity from an OData collection (covered in the blog #04).
But it actually turns out that we need to come up with a way to associate and identify each request and its error in the error archive.
There could be different approaches how to do it – and one possible tip is to use the simple technique called “custom tag”. By assigning each request a unique custom tag, it will appear in both the request object in the callback and the property of the error archive entity.
How do we add a custom tag? In the blog #04, we learned how to do the CUD operation by means of schedule<CUD>Entity methods.
For example, the Create operation is invoked with this method.
01 [store scheduleCreateEntity:entity collectionPath:@"CollectionName" delegate:self options:nil];
Alternatively, if we want to add a custom tag, we can invoke the Create operation with scheduleRequest: method.
01 SODataRequestParamSingleDefault *requestParam 02 = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeCreate 03 resourcePath:@"CollectionName"]; 04 requestParam.payload = entity; 05 requestParam.customTag = @"myCustomTag"; 06 [store scheduleRequest:requestParam delegate:self];
From the functional perspective, both do the same job – but the scheduleCreateEntity: does the better abstraction, in fact it calls very similar logic behind the scenes. In the bottom of this blog you can refer the CRUD operations code with scheduleRequest:.
Hold on, now I have a second thought with the line #05. It is a good idea to give each request some kind of unique custom tag, it can be useful for debugging.
01 requestParam.customTag = [NSString stringWithFormat:@"tag%@", [[NSUUID UUID] UUIDString]];
Now that a custom tag has been assigned to a CUD operation, how do we make use of it in the offlineStoreRequestFailed:?
01 - (void) offlineStoreRequestFailed:(SODataOfflineStore*) store 02 request:(id<SODataRequestExecution>) requestExecution 03 error:(NSError*) error 04 { 05 ... 06 id<SODataRequestParamSingle> request = (id<SODataRequestParamSingle>)requestParam; 07 NSString *resourcePath = [NSString stringWithFormat:@"ErrorArchive?$filter=CustomTag eq '%@'", requestParam.customTag]; 08 [offlineStore scheduleReadEntityWithResourcePath:resourcePath delegate:self options:nil]; 09 }
First of all, your custom tag can be referred in the request object. In #08, you see how the requestParam.customTag is obtained. It should have the assigned tag value like “tag25716A15-667A-4468-AC88-B56598D42E0D”. And it build an OData query string like “ErrorArchive?$filter=CustomTag eq tag25716A15-667A-4468-AC88-B56598D42E0D“, which let you identify the associated error item out of the error archive. With the line #08, it triggers OData query against the error archive, which locally exists with the offline store.
It will invoke requestServerResponse: method, and as it was queried, it returns the entity set – simply delete the associated error entity out of the error archive.
01 - (void) requestServerResponse:(id<SODataRequestExecution>)requestExecution 02 { 03 id<SODataRequestParam> requestParam = requestExecution.request; 04 if ([requestParam conformsToProtocol:@protocol(SODataRequestParamSingle)]) { 05 id<SODataRequestParamSingle> request = (id<SODataRequestParamSingle>)requestParam; 06 if (request.mode == SODataRequestModeRead) { 07 id<SODataResponseSingle> responseSingle = (id<SODataResponseSingle>)requestExecution.response; 08 if ([responseSingle.payload conformsToProtocol:@protocol(SODataEntitySet)]) { 09 id<SODataEntitySet> errors = (id<SODataEntitySet>)responseSingle.payload; 10 for (id<SODataEntity> entity in errors.entities) { 11 [offlineStore scheduleDeleteEntity:entity delegate:self options:nil]; 12 // purging an error item is complete 13 } 14 } 15 ...
That’s all about how to handle the situation in which the OData Producer fails one of the queued requests during a flush. Hope you get some solid idea how all gizmos are working in the offline scenario and how to work with them.
This is a kind of closing of the offline scenario – but not the end of the show. I’ll add some more things, which should be helpful for the actual implementation (that’s a whole idea of these blogs). Tiny but important bits and pieces. Thanks for keeping company so far.
See you in the next blog,
Ken
Appendix:
Create operation (= HTTP POST)
01 [store scheduleCreateEntity:entity collectionPath:@"CollectionName" delegate:self options:nil];
is equivalent to
01 SODataRequestParamSingleDefault *requestParam 02 = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeCreate 03 resourcePath:@"CollectionName"]; 04 requestParam.payload = entity; 07 [store scheduleRequest:requestParam delegate:self];
Read operation (= HTTP GET)
01 [store scheduleReadEntityWithResourcePath:entity.resourcePath delegate:self options:nil];
is equivalent to
01 SODataRequestParamSingleDefault *requestParam 02 = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeRead 03 resourcePath:entity.resourcePath]; 04 [store scheduleRequest:requestParam delegate:self];
Update operation (= HTTP PUT)
01 [store scheduleUpdateEntity:entity delegate:self options:nil];
is equivalent to
01 SODataRequestParamSingleDefault *requestParam 02 = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeUpdate 03 resourcePath:entity.editResourcePath]; 04 requestParam.payload = entity; 07 [store scheduleRequest:requestParam delegate:self];
Delete operation (= HTTP DELETE)
01 [store scheduleDeleteEntity:entity delegate:self options:nil];
is equivalent to
01 SODataRequestParamSingleDefault *requestParam 02 = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeDelete 03 resourcePath:entity.editResourcePath]; 04 [store scheduleRequest:requestParam delegate:self];
I was wondering if the deep create is supported by the offline odata as well?
Yes you can do it by going through multiple OData requests to explicitly create the entities and then link them together. In addition to it, you can expect a new feature to simplify those steps in the upcoming SPs.
Thanks Ken for the quick response, i have another question in the following scenario is SMP is going to send request one after another to the GW and does it keeps the order in which i make the calls.
[store scheduleCreateEntity:parententity collectionPath:@"SalesOrder" delegate:self options:nil];
[store scheduleCreateEntity:childentity collectionPath:@"SalesItem" delegate:self options:nil];
[store scheduleCreateEntity:childentity collectionPath:@"SalesItem" delegate:self options:nil];
I am trying to link the child object to parent entity when the create call is made for the child entity.
Good question. This code should work for linkage:
---
SODataLink link = [[SODataLinkDefault alloc] initWithResourcePath:@"OrderItems(1)"];
SODataRequestParamSingleDefault *requestParam
= [[SODataRequestParamSingleDefault alloc]
initWithMode:SODataRequestModeCreate
resourcePath:@"Order(101)/$link/OrderItems"];
requestParam.payload = link;
[store scheduleRequest:requestParam delegate:self];
---
You can obtain both parent & child entities locally. So you can change the example value such as OrderItems(1) or Order(101) with variables such parentEntity or childEntity as written here.
The Offline Store will keep the order of requests when it applies them to the backend.
And you can execute all the create and link logic locally and just do one flush in the end for the entire deep insert.
Hi ken,
i have some problem during perform scheduleCreateEntity on SMP3 SP9 - Connection lost at scheduleCreateEntity method
Possible you can give some advise?
Regards
Choong
Please create a new Discussion marked as a Question. The Comments section of a Blog (or Document) is not the right vehicle for asking questions as the results are not easily searchable. Once your issue is solved, a Discussion with the solution (and marked with Correct Answer) makes the results visible to others experiencing a similar problem. If a blog or document is related, put in a link. Read the Getting Started documents (link at the top right) including the Rules of Engagement.
NOTE: Getting the link is easy enough for both the author and Blog. Simply MouseOver the item, Right Click, and select Copy Shortcut. Paste it into your Discussion. You can also click on the url after pasting. Click on the A to expand the options and select T (on the right) to Auto-Title the url.
Thanks, Mike (Moderator)
SAP Technology RIG
Hi,
Have you worked with the AffectedEntity navigation property on the ErrorArchive? From the documentation it sounds like this should give a reference to the entity related to the error, but from my trial and error i am not able to expand it.
Does anyone have experience with this?
Br Olav
Thanks for pointing out. It is a new feature of SDK SP11.
You can use it to access entities that are in an error state due to a particular failed request, you can only access them through a specific error archive entity.
So /ErrorArchive?$expand=AffectedEntity will throw an error
But /ErrorArchive(1)/AffectedEntity should work fine.
As this remark is not documented, there will be some update on the help doc soon.
Thank you for getting back to me! It works for me the way you explain it - thats great.
I am wondering though if something else is possible:
In our application we have a list of items that the user is working on. Each item can be updated and we would like to show in the list with a small icon if an item has local changes and if an item has resulted in an error. For local changes we check for the instance annotation com.sap.vocabulary.Offline.v1.islocal - this works fine. But do you know if something similar exists for errors?
I am guessing it does not, but it would be a really nice feature 🙂
Best regards,
Olav
There is an annotation that marks entities and links that are in an error state called:
com.sap.vocabulary.Offline.v1.inerrorstate
(I suggest you to bring this to your forum post - the QA can be searchable)