In the month since the launch of our iOS SDK, we have been hard to work to make it as easy as possible for apps to work offline and minimize the amount of data transferred. This post will tell you about different strategies for delivering the best experience to your users even when they have bad reception or no internet connection at all. If you are not familiar with the SDK yet, you should first check out our documentation to get started or look at some examples on GitHub.
There are basically two approaches for getting content for offline use (or for any use for that matter):
You should be familiar with search queries from our last blog post, so here is a quick rundown on how using synchronization works:
An initial synchronization will download all content of a Space and return a CDASyncedSpace instance which contains all the Resources and also keeps track of the synchronization process. Subsequent synchronizations can be performed like this:
If you want to continue a synchronization session after an app restart, store the syncToken and lastSyncTimestamp values and create a shallow Space like this:
Keep in mind that continuing a synchronization session like this will not reinstate the previous data, so you have to use the delegate to keep your own copy of the data current. You can also check the Delivery API documentation for some more information on the synchronization API.
In addition to that, there are many way to actually persist your data, you might want to use property lists, SQLite or something else entirely. If you need more than just persistence, your choice will probably be Core Data. That is why our SDK is very flexible in this regard. Out of the box, all Resources and also CDASyncedSpace support NSCoding so that you can simply serialise some data to disk. If you need more flexibility, there is the abstract CDAPersistenceManager class with a sample implementation on top of Core Data. Let's look at the options in detail:
Writing any Resource to disk can be done like this:
For reading it, you can use a class method, depending on the root object:
Such an object will act just like it would if it was just retrieved via the API, including the possibility to continue your synchronization session if you persisted a CDASyncedSpace:
This approach makes it easy to just cache some data, but it has the drawback of needing to load all content into memory at once. It also makes it difficult to associate your own local data, for example a read status on news items, to data retrieved from Contentful.
To make it easier to store data retrieved from Contentful into any local data store, the CDAPersistenceManager class exists. For our Core Data examples, a subclass of that was created which should cover your basic needs, but feel free to extend it as you see fit, that is why it is not a part of the SDK itself.
We do not provide an abstraction of Core Data, even though some of the boilerplate code is avoided when using the CoreDataManager class. At first, you will need to create your data model and managed object classes, which need to conform to the CDAPersistedAsset, CDAPersistedEntry and CDAPersistedSpace protocols. The protocols ensure that there is a minimal set of information needed to identify and continue synchronizations. You can now set up your manager instance:
This will make the data model and managed object subclasses known to the manager. As it only provides a reference implementation, it is assumed that there is only one class for all your Entries and also that you do not need to store additional data for Assets or the Space.
For Entries, a mapping is defined to store certain Fields in their corresponding properties:
This will store the Field value specified by the key in the property specified by the value of each mapping dictionary entry.
Both the initial fetch as well as subsequent synchronizations can be done like this:
The manager will add new Resources to the managed object context and delete/update existing ones, until it finally saves the context automatically.
If you want to use a search query to fetch Entries, you can use an alternative initializer:
Using this approach allows you to only fetch a limited data set from your Space. It will use sys.updatedAt in later queries to only fetch updated Resources and use an additional selective synchronization session to also delete no longer existing Resources. Depending on your use case, this will still fetch more data than desirable, in that case, you should also modify the provided reference implementation.
Another example demonstrates how to ship your app with a pre-seeded SQLite database for Core Data, so that your users will not even need a data connection when they are using your app for the first time.
This is achieved by running a commandline OS X application which uses the SDK to fetch Resources and also all the Asset content, which can then be copied into your app as part of your build process or manually. You will have to modify this tool for your needs, specifying the data model, Space information and conditions on what Asset content to fetch.
The CDAPersistenceManager provides a convenience method for copying the database and cached Assets from your bundle into the right place:
The method will also ensure that the pre-seeded data is only used after the first launch of your application. After that, you can just update your local content like you normally would.
With this, our overview of synchronizing and keeping your content available for offline use is done. You should be able to provide a great experience for your users regardless of their data connection.