Offline data handling

Appfarm supports creating Apps that may operate in offline environments. Offline support may be necessary in Apps for mobile workforces operating in environments without internet connection, such as in tunnels, on the sea, or inside large buildings without internet. With an offline App, all data in App Variables and Data Sources are cached on the device. When the App goes online, a separate event handler (configured in App Settings) exists for handling the CRUD (Create/Read/Update/Delete) operations toward the database.

However, if you want an App to support both online and offline handling of data, the App needs to be designed for offline support. To change an existing App towards offline data handling is not recommended, as you may learn from this guide.

This guide will provide 2 things: A checklist/overview of the principles, settings in Appfarm Create, and patterns you must follow when designing an App with offline support, and a walkthrough of our Showroom Example where we have a working offline App set up, following our own design patterns.

A full example setup of an Offline App is available in our Showroom! You may try the App yourself, as well as access the setup in Appfarm Create. However, we recommend following the walkthrough below before diving into the setup in Appfarm Create. If you do not have access to Showroom, you may register here.

Designing for offline support - rules, settings and principles

The Rules & Settings

  • Database Connected Data Sources should be avoided, only use Runtime Only Data Sources (or Data Connector). This is recommended for full control of CRUD operations.

  • If you use Deep Data Bindings (Example: displaying Orders.Product.Price), you must use Reference Data Sources (Example: You must add Products as a Reference Data Source on the Orders Data Source)

  • Do not use the On App Load or On App Loaded event handlers for data handling (try to avoid them, as the On Online event handler will always execute when refreshing or accessing your App in online mode).

  • Use the On Online event handler for both saving unsaved data (persisting new/update objects, or deleting objects) and reading data.

  • All Actions that are intended to save data (persist objects) should handle both Offline and Online state. Example: Save Product should have one IF block for offline (based on the App Variable Is Online) where data is read into a Data Source holding all data and being the "offline master data source" for that object type. In addition, there should be one IF block for online mode, where the Product is saved (persist objects) in addition to being added to that same master data source

  • The App must have the Enable Offline flag ticked. This is in fact the only required setting.

  • If offline opening of documents is required, those document URLs must have been opened at least once before the App goes offline. You need to make sure the files are cached while online first, before using Open URL on these documents while offline. You may run this code snippet from the App (while online) to force caching of all file data.

    // myFiles must be added as a data source parameter
    Promise.all(myFiles.map(item => {
        return fetch(item.__fileContentLink)
    }))
        .then(resolve)
        .catch(reject)

Principles

We recommend having as few Data Sources as possible, and all Data Sources should be Runtime Only. In other words, do not have multiple Data Sources of the same type for holding objects - the only case where this should be the case is for the single-cardinality Data Sources used for Creating or Updating single objects

You may add a multi-cardinality Runtime Only Data Source Products (master) used for holding all Products, and another single-cardinality Data Source Product (new/edit) used for editing or creating a new Product.

Read all updated or created objects into a multi-cardinality Runtime Only Data Source. When objects are updated or created, all data sources holding the same objects must be manually refreshed. This is automatically done when using Database Connected Data Source upon persisted changes, but in offline Apps, we do not have that luxury.

When you want to create a new Product, you may do it in the Product (new/edit) data source. But the Save button should read the created object into Products (master). If the App is Online, the Product (new/edit) should also be Persisted.

All Actions performing updates to data should Persist when Online. The Action should have explicit logic handling the Online state and the Offline state.

Example:

Deleted objects should be marked as deleted, not actually deleted, while offline. You need to know the ID of the object to be deleted. Therefore, you should add a Runtime Only property Marked for delete or similar to the Data Source, and set this flag to true when an object is deleted. All UI and logic should exclude the objects with Marked for delete = true.

The Dialog for editing a Product may have a Delete button. But instead of actually deleting the object, you may perform an Update Object of that object in the Products (master) Data Source, setting the flag Marked for delete to true.

Use a Data Connector Data Source for deleting objects once online. When the App goes online, you may perform a Delete Objects towards that Data Connector Data Source, deleting the objects Marked for delete.

Use the On Online Event Handler for Persisting unsaved data and Deleting objects to be deleted. The Action should start with persisting new objects, then persisting updated objects (in iterations, with catch exception), and then delete objects marked for delete (in batch, or in iterations, depending on the requirements for exception handling) with exception handling.

Then the Action should read all data from the database.

The On Online Event Handler runs on every refresh of the App when it is online. So, if the App is always online, nothing will ever be persisted or deleted in the first part of this action, but data will always be read in the second part. If the App has been offline and goes online, this Action is triggered, and data is both saved/deleted and then refreshed from the database.

Make the user aware of the offline state in the App, and disable unnecessary functionality. You should display some information to the user when the App is in offline mode. Some functionality may be considered "unnecessary" to have in the short timespans the App is offline. Offline functionality comes with complexity (=cost), so you might just want to hide or display certain buttons.

Example:

Testing and debugging

Start by testing everything in online mode as normal. The Enable Offline flag in the App Settings is the "trigger" that changes how this App behaves at runtime. Make sure this flag is enabled while testing.

You may simulate Offline Mode in your browser from the Developer Console -> Networks tab.

The first thing you may encounter is that loading data fails because Reference Data Source has not been set. When deep data bindings are used, Appfarm generates some GENERATED data sources (for the joins towards the connected object classes). Your Offline App should not have any Generated Data Sources. You may toggle the display of these Data Sources in the Dev Tool if you open the Console in the browser and type the following: appfarmDebug.toggleGeneratedDataInDevTools()

Offline App - Walkthrough of Showroom Example

We have created an example app in our Showroom. You may test the App here, and you may locate the App inside Appfarm Create (if you have signed up for Showroom) by selecting the solution Appfarm Showroom in the top left menu.

About the App

The App allows the user to see all Products and filter Products by Category. The user may add new Products (and upload a Product Image), edit Products and delete Products.

The App operates as normal if the user is online. When the App goes offline, the user is visually notified, and all data added, updated, or deleted are stored on the device. The user may safely refresh the App while offline. When the App goes online, the new and updated Products are saved to the database (persisted), and the deleted Products are deleted from the database.

App Data

All Data Sources are Runtime Only, except the Data Connector Data Source.

  • Products (data connector for delete): Only used for deleting products once the App goes online and Products have been Marked for delete while offline.

  • Products (master): Holds all products, including all new, updated, and deleted products while offline.

  • Product Images (master): Holds all Product Images, and is used as a Reference Data Source for Products (master). It also holds all new Product Images added to existing or new Products.

  • Product (new/edit): Used in the Dialog for adding new Products or editing existing Products. For creating new Products, we create a new object into this Data Source, and upon save - read this object into the Products (master) Data Source. For editing Products, we read the object to be edited from the Products (master) Data Source into this Product (new/edit) Data Source, and back again upon save.

  • Product Image (new/edit): For saving a new Product Image to be linked to the new or existing Product. New Product Images are read into the Product Images (master) upon save.

  • App Variables.Offline edit done: We set this boolean variable to true every time we create, update or delete data while offline. This is not required, but we do it for having a condition in the On Online Action, in order to skip the whole block of persist/delete logic if no changes have been done.

Actions and UI

  • Save Product: Logic for both Online and Offline mode. Both of them read the updated or created Product and Image back into the master data sources, and close the dialog. But if the App is online, we also persist the changes to the database.

  • Delete Product: If the App is offline, we mark the Product "Marked for delete" and close the dialog. Note that the list of Products in the UI filters out the Products Marked for delete, so these Products appear to be deleted to the user. If the App is online, we delete the Product directly towards the database (using the Data Connector Data Source), and we also remove it from the Products (master). Remember, Runtime Only Data Sources need to be manually refreshed, so a deleted object in one data source does not reflect automatically on other data sources holding that same object.

  • On Online (event handler): The Action starts with a block for persisting new Products, persisting changes to updated Products, and deleting Products Marked for delete. Note that we use the built-in Object Class Property Object State for differentiating between new and updated products!

And finally: The Enable Offline flag is ticked in the App Settings.

Last updated