Skip to Content
Author's profile photo Kenichi Unnai

#11 SMP3 OData SDK for iOS – Offline Store Gotchas

/wp-content/uploads/2014/10/11_559110.png


<updated>May 14th, 2015</updated>


Hello everyone,

Back to the blog 🙂


Now that we have gone all the way through the 10 blogs, general understanding of the OData API has been deployed on top of our brains.


From now on, we’ll discuss some gotchas to watch out. As all those information are “as it is”, please expect constant update in the blog, I’ll put the timestamp explicitly whenever some important update come in in the future.


Throughout this blog I would like to cover bits and pieces around SODataOfflineStore.


Removing the offine store

In the blog #06, we learned how to create an offline store. We haven’t discussed yet how to remove it… Here’s the code to name the offline store, which is required for removal step later. The storeName property is optional for SODataOfflineStoreOptions instance.  If it is not set when opening the store, it chooses a default name.

01  SODataOfflineStore *offlineStore = [[SODataOfflineStore alloc] init];
02  SODataOfflineStoreOptions *options = [[SODataOfflineStoreOptions alloc] init];
03  options.storeName = @"MyStore";
04  ..

And the code below is about how to remove the physical store from the file system. If you don’t set the storeName during the creation, the default value will be used without the property value. As written in the line #04, the store must to be closed before the removal.


Note: For security, you should be closing your store instances whenever you do not need them (e.g. via applicationDidEnterBackground:, when you app goes to the background). Once it is closed, it can be open by openStoreWithOptions:error: method (e.g. via applicationWillEnterForeground:).


When the app comes back to the foreground, you don’t simply call openStoreWithOptions on the same store instance.  Instead you have to create a new store instance using offlineStore = [[SODataOfflineStore alloc] init]; and then call openStoreWithOptions on that.

The “offlineStore” instance here is not a new one but the same name with the closed one. As long as they use the same storeName option then it will open up the same underlying local store database and do not have to redownload anything.


01  NSError *error = nil;
02  SODataOfflineStoreOptions *options = [[SODataOfflineStoreOptions alloc] init];
03  options.storeName = @"MyStore";
04  [offlineStore closeStoreWithError:&error];
05  [SODataOfflineStore RemoveStoreWithOptions:options error:&error];
06  if (error) {
07    // store removal failed
08  } else {
09    // store removal succeeded
10  }

Defining Request

In the blog #06 and #07, we learned what Defining Request is and how to define it in the code. A few things to keep in mind:

  • Entity relationships


In order for entity relationships to be available (e.g. “CarrierCollection(‘CO’)/carrierFlights” – the relationship from one entity to another) on the client we need to have one of two things:


Option 1) Use $expands in your defining requests

Option 2) Inside OData services, implement a set of referential constraints between the two entity types


Option 1 – The $expand syntax is to name the navigation property that you want to expand, most OData producers do support $expand.


Option 2 – A referential constraint is extra information attached to an Association (or relationship) that says how the two entities are related. In OData Metadata, you should be able to find the <ReferentialConstraint> tags if it is implemented in OData services. If these are supplied, the relationships between entities is defined by the properties directly and MobiLink can use that to build up the links behind the scenes. The benefit of this approach is in case $expand can’t be used, we can go to this option.


So if the OData service implements both $expand and referential constraint for the entity relationship between Customers and Orders, both do the same job from the client perspective.

01  options.definingRequests[@"req"] = @"/Customers?$expand=Orders";
01  options.definingRequests[@"req1"] = @"/Customers";
02  options.definingRequests[@"req2"] = @"/Orders";

[There is a bit of a description of referential constraints in section 10.4 of Common Schema Definition Language (CSDL). Essentially, referential constraints are OData metadata details that can be supplied in order to make OData properties act more like relational database foreign keys.]

  • OData collection times out with HTTP 500 error

You might encounter the runtime error during the offline store creation, simply because the OData collection you’re trying to fetch is too big – you can copy & paste the OData endpoint URL in the web browser and see if it shows 500 timeout error. The issue in this case is a problem on the server-side.  Our HTTP requests for data were timing out because the backend producer was taking too long to query the data and encode it as OData.

In this case we should consider using server side paging on OData producer.


There are two types of paging in OData – server side and client side.

Client side paging is driven by having a $top value and then incrementing the $skip values for each request (e.g. $top=100&$skip=0 for first request, then $top=100&$skip=100 for second request etc.) This type of paging will not help with populating the database in the offline store because the defining requests are fixed. (Of course, you can use $top or $skip to make requests for small amounts of data to the local store after the store has been populated.)

In server side paging, even if you request the entire big OData collection, the server will only return a portion of the results, but provide a “next link” info, which can be followed to get the next portion and so on.  When the MobiLink is populating the local store it knows how to follow these next links. Server side paging via next links is OData Version 2.0 feature. The $skiptoken query is seen in OData URL if it is implemented.


Offline OData Version Support info


Is here!


OData Query Options supported by SAP NetWeaver Gateway


is here!


OData Query Options supported by Integration Gateway


is here! (You’ll find detailed info via each tree node for JDBC/JPA/SOAP/ODC)


SAP Note# 1931374 – Integration Gateway for SAP Mobile Platform 3.0 – Known Constraints

“How do we build the best OData services for offline scenario?” Series

Okie that’s all for the blog this time. I’ll cover some other practical topic in the next blog…



See you in the next blog,

Ken


List of blogs

Assigned Tags

      11 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member

      Hi Kenichi,

      Very good stuff!

      I have several questions.

      1) Is server side paging via skiptoken available if you are using Integration Gateway? skiptoken could be retrieved in a custom code script but I can't see how include in the response payload "next" link with new skiptoken value.

      2) Is server side paging and refresh with delta enabled OData producer compatible processes to load and refresh data? In the load data phase, SMP will send a new data request to the OData Producer while it is still receiving "skiptoken" next link from datasource. At the last page received from OData Producer, it will receive a "deltatoken" link from OData producer instead of aforementioned "skiptoken" next link. SMP saves "deltatoken" link in order to use during next "refresh" data from client. Is this process correct?

      Thanks you so much!

      Best regards,

      José

      Author's profile photo Kenichi Unnai
      Kenichi Unnai
      Blog Post Author

      Hi Jose,

      Thanks for posting.

      #1> Not yet. I had added a new link in the blog so that you can check the latest details about the OData Query options in IGW.

      #2> Sorting out with the dev. folks now. Hold on a moment...

      Best regards,

      Ken

      Author's profile photo Former Member
      Former Member

      Hi Kenichi:

      Thanks so much for your quick answers, they are helpful 🙂 .

      I have a few more questions:

      - Currently, what could be a feasible solution for server-side paging with IGW?


      Maybe you could retrieve data from online by client-side paging and insert your data in a custom database. This could be a feasible solution when you have a lot of read-only data. The drawback is that you should manage all stuff related with delta token and refresh.

      - Is it planned to support IGW and server side paging?

      At the end, maybe it could be simlar to the current support to deltas & tombstone. We need to inform at one of the post processing method script, that we want to include a "next"  link in next service payload response.

      Maybe it could be great to have an option to modify the final service payload response (another hook method, I'm not sure if it is technically possible) in order to have more flexibility to solve problem like this. "ODatacontext" is included in the headers, but I'm not sure if you could include "next" link information in some manner through this object in the service response.

      - Is it possible to modify this timeout? I'm assuming that problem is when SMP is making calls from MOData to others OData Producers, IGW or SAP NW Gateway.

      - Is there any recommendation about number of data limit that could be synchronized without server side paging?

      I think it is a very difficult stuff that depends on data, service, ... General recommendation is to use server side paging, because it is a well designed model...

      Best regards,

      José

      Author's profile photo Kenichi Unnai
      Kenichi Unnai
      Blog Post Author

      >I think it is a very difficult stuff that depends on data, service, ...


      Happy to have your advanced thought, more than welcomed 😉


      I'll get back to you.


      Best regards,

      Ken

      Author's profile photo Kenichi Unnai
      Kenichi Unnai
      Blog Post Author

      >#2.

      Offline store can handle the mix of delta query and $skiptoken.

      Here's another blog that covers detailed info.

      Author's profile photo Former Member
      Former Member

      Hi Kenichi Unnai

      I have been trying to close and re-open an offline store based on your note:

      For security, you should be closing your store instances whenever you do not need them (e.g. viaapplicationDidEnterBackground:, when you app goes to the background). Once it is closed, it can be open by openStoreWithOptions:error: method (e.g. via applicationWillEnterForeground:)

      However, when I try to reopen the store using the same options I get this error:

      Cannot open the store because it has been previously closed.

      I have looked everywhere, but I cannot find the reason for this. Do you know why this is happening?

      Thanks in advance!

      Jeff

      Author's profile photo Kenichi Unnai
      Kenichi Unnai
      Blog Post Author

      Hi Jeff, thanks for the inquiry, it is a good question.

      And a bit of apologies from my side. After checking with a SDK fellow developer, it turned out I had missed a little explanation on it.

      When the app comes back to the foreground, you don’t simply call openStoreWithOptions on the same store instance.  Instead you have to create a new store instance using store = [[SODataOfflineStore alloc] init]; and then call openStoreWithOptions on that.

      The "store" instance here is not a new one but the same name with closed one. As long as they use the same storeName option then it will open up the same underlying local store database and not have to redownload anything.

      Hope this helps! I'll update some explanation in the H2G accordingly.

      Author's profile photo Former Member
      Former Member

      Thanks Kenichi,

      I came to the same solution after some experimenting myself. Your blogs have been immensely useful at learning to use SMP!

      I do have another question, is there a way to inspect the options of a store it was opened with? I am trying to see if I can get the store's name and a list of its defining requests.

      Thanks!

      Jeff

      Author's profile photo Kenichi Unnai
      Kenichi Unnai
      Blog Post Author

      Happy to hear they are helping you.

      >a way to inspect the options of a store it was opened with

      I double checked and there's no way to look at the store object only and see the store options.

      The API design simply assumes just looking at the store options object we used to open it with.

      Author's profile photo Former Member
      Former Member

      Hi Ken,

      You have mentioned to close the store whenever the app moves to background fro security reasons. Consider an application used by a sales person. While calling the refresh, he has moved the application to background for some reason. In that case, the store will be closed and the latest data wont be updated in the application. If the same thing happens all the time he does refresh, how the updated data will reach the application?

      Is it necessary to close the store while moving the app to the background? What all are the threats if we keep it open even in background mode?

      Regards,

      Dhani

      Author's profile photo Kenichi Unnai
      Kenichi Unnai
      Blog Post Author

      Generally speaking, it is up to the app developer to specify what to do when the app (and the offline store) goes into the background. 

      If the app developer chooses to close the store, then refreshes/flushes will be interrupted and will not complete (*actually* - my dev. colleagues recognized that the return is  incorrectly a success in this case, but currently being processed as a bug fix so that an error is returned instead because the refresh/flush did not actually finish). 

      In this case, the next time the app does a refresh/flush it should resume from where it left off.  But this does mean that the only progress being made is while the app is in the foreground. 

      On the other hand, the app developer can write the necessary code so that the refresh/flush continues while the app is in the background (on some platforms the amount of time it can continue may be limited).  In this case, the refresh/flush MAY complete while in the background.  If it doesn’t complete while in the background, the next time a refresh/flush is performed, it should continue from where it left off.