For apps it can be important to handle situations where you don’t have a connection. These Offline Apps need to work, without losing any functionality while you are offline.

2 Main options for handling offline

First of all, for Offline Apps you have two choices:

In this post I will focus on the second option as this covers interaction with any data sources.

Things to avoid

When your app needs to be able to be offline there are a number of things that you have to avoid. These include:

  1. Don’t run any flows from your Offline Apps as they would fail to get triggered. Flows are just like any other connectors, they will need a connection.
  2. Rely on Connection.Connected to give you the current status
  3. Running in a browser. The offline functions LoadData and SaveData work within the mobile and desktop app only

Planning your app

When you start the app you should be online, so that you can initially read data from your data sources. As you are using the offline app you may need to resync your data back to your databases.

The patterns in this post will describe the situation where we have a number of sections on a form to fill in before the form gets submitted. Throughout this process we need to keep a copy of the data in the data sources ( e.g. Multiple tables in Dataverse, Lists/Libraries in SharePoint and any other data sources that you might need). However the data sources may not be available.

To keep a copy of the data when we can’t write to our database, we will use SaveData to make sure that we can sync these updates to our database at a later stage.

Additionally, we will keep data related to the current form in collections. I will call this my offline database. This needs to be kept in sync at all time. Whenever we display data in our app, this offline database will be used as we cannot rely on the data sources being there.

Offline apps synchronising data to data sources such as Dataverse and SharePoint
Offline apps synchronising data to data sources such as Dataverse and SharePoint

To implement the above data sync process within the Power Apps you might find the following 7 patterns useful.

Pattern 1 – Identify if we are online or offline

As mentioned in my earlier post about Connection.Connected in Offline Apps, the out of the box way to identify if we are online or not isn’t 100% reliable. To replace this I’m using the following pattern.

        IfError(
            First('My Forms').'Any Property';
            Set(varWeAreOnline, true);
            ,
            Set(varWeAreOnline, false);
        );

Using the IfError function, I’m identifying if we are online by trying to read data from one of my Dataverse tables. If this fails, then my variable is set to false and otherwise this variable is set to true. Remember that in the above code there is a small moment where the variable is set to true before it is set to false.

For further details on this please read my post about the IfError function. The above code could of course be implemented as a separate User Defined Function.

Pattern 3 – Saving a single record to your device

SaveData allows you to write a collection to a local device. However a single object/record/item, you can’t store to the local device.

Imagine that I’ve a variable containing an object called gblActiveForm. And this data needs to be stored locally on my device.

Creating a collection using ClearCollect, will allow me to write the data to the local device.

ClearCollect(gbl_col_ActiveForm, gblActiveForm);
SaveData(
    gbl_col_ActiveForm,
    "FileNameForForm"
)

At a later stage we can then go read the data using LoadData as follows:

LoadData(gbl_col_ActiveForm, "FileNameForForm");
Set(gblActiveForm, First(gbl_col_ActiveForm));

However LoadData may fail if there is no file with he name FileNameForForm found. Especailly if you have data from multiple data sources that need to be synchronised you might want to include some error handling around the above code.

IfError(
      // Load the main form's data
      LoadData(gbl_col_ActiveForm, "FileNameForForm");
      Set(gblActiveForm, First(gbl_col_ActiveForm));
      Set(varSyncNeeded, true);
,
      Set(varSyncNeeded, false);
); 

Now our varSyncNeeded code will tell us if we need to bother synchronising our other data sources as well. This is of course assuming that we have a main record that relates to all other data used by our app.

Pattern 4 – Panic Mode

Throughout my app I’m introducing a variable that tells us if there is any offline data store on the local device. So when we try to update a database while we are offline, the below pattern will set our varPanicMode variable when an update to the databases fails and we need to store data locally on the device.

 IfError(        
    Patch(
        'My Forms'
    ,
        LookUp('My Forms', 'Form ID' = gblActiveForm.'Form ID')
    ,
        {  
            ...      
        }            
    )
 ,
    If(varRunningInApp,
       Set(varPanicMode, true);

       // Convert object to collection as Save Data will need this
       ClearCollect(gbl_col_ActiveForm, gblActiveForm);
       SaveData(
           gbl_col_ActiveForm,
           "FileNameForForm"
       )
    ,

       // Running in the browser and the app failed to save data
       Notify( "Failed to save data, please contact your system administrator",NotificationType.Error
    )  
)

Pattern 5 – Create the Offline Database

One part of the pattern described in this post is the creation an offline database. This is a series of collections that stays in memory. This is used to present data in galleries, forms and wherever else you need the data.

In the example below an Offline Database collection is created ready for my Canvas App to use. When the app is loaded (or when you are online and create that initial record that starts your process) the collections matching the data that you need will need to be created. So typically one collection for each table or list that you use.

 
    ClearCollect(
        OffDBMyTable,
        Filter(
            'My Table',
            FormID = gblActiveForm.ID
        )
    );
    

Pattern 6 – Data Sync User Defined Function

First I’m putting a User Defined Function in place to handle the data sync from our data stored locally on the devices to the databases.

It is important to note that when there is no need to synchronise data or when we are offline, the user defined function will do nothing.

StartSyncData() : Void = 

{

    If(
        varPanicMode
    , 
     
        IfError(
            First('My Forms').'Any Property';
            Set(varWeAreOnline, true);
            ,
            Set(varWeAreOnline, false);
        );

        If(varWeAreOnline,

            IfError(
               // Load the main form's data
               LoadData(gbl_col_ActiveForm, "FileNameForForm");
               Set(gblActiveForm, First(gbl_col_ActiveForm));
               Set(varSyncNeeded, true);
            ,
               Set(varSyncNeeded, false);
            ); 

            If(varSyncNeeded,

                // Read Other Tables from device
                Set(origOffDBOtherTable, OffDBOtherTable);
                IfError(
                    Clear(OffDBOtherTable);
                    LoadData(OffDBOtherTable, "DataOtherTable");
                    Set(varOtherTableAvailable, true);
                    Set(OffDBOtherTable, origOffOtherTable);
                ,
                    Set(varOtherTableAvailable, false)                                    
                );
                // Repeat the above section for other tables that you may have

                If( varCladdingUpdatesAvailable, 

                          ... // implement here your patches that need to update your table using the data from the device
                        
                );

        )

    )
}

It is important to make this app perform well. At the same time we want to synchronize data as quickly as possible to out databases. Therefore, the above UDF could be used every time a user presses a save button. This is one of the reasons why the above UDF would first check if a specific table has been updated (local file exists) before doing the actual update work.

Pattern 7 – Save buttons and update data in Offline Apps

For every save button, we can implement code similar to the following pattern. We will always start with an attempt to synchronize the data using our UDF from one of the earlier patterns.

//Try a Data Sync
StartSyncData();

If( 
    varIsDirty
,

    // Offline update first
    Set(
        gblActiveForm,
        Patch(
            OffDBForms,
            LookUp(
                OffDBForms,
                'Form ID' = gblActiveForm.'Form ID'
             ),
            {  
                ...
            }
        );
    );            

    // Try to save the same update to the actual tables
    IfError(        
       Patch(
           'My Forms',
           LookUp(
               'My Forms',
                'Form ID' = gblActiveForm.'Form ID'
           ),
           {  
              ...    
           }            
        )
    ,
        If(varRunningInApp,

            Set(varPanicMode, true);

            // Convert object to collection as Save Data will need this
            ClearCollect(gbl_col_ActiveForm, gblActiveForm);
            SaveData(
                gbl_col_ActiveForm,
                "DataMainForms"
            )

        ,

            // Running in the browser and the app failed to save data
            Notify( "Failed to save data, please contact your system administrator",NotificationType.Error)  

        )

    );                      
          
           
    Set(
        varIsDirty,
        false
    );

);

Navigate(ctxNextNav);   // Navigate to the next screen

In the above code, we will always first keep our Offline Database (Collections with a name that starts with OffDB).


Discover more from SharePains

Subscribe to get the latest posts sent to your email.

Related Posts

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.