an honest guide to canvas app offline (part 1)

I have found the topic of offline capability in canvas apps to be a bit of a black box: search for it online, and you will find a number of articles, posts and forum messages that are mere variations of the official Microsoft documentation. A basic guide, using a rather absurd example (honestly, who requires Twitter post capability when offline?), to the correct expressions and syntax to make offline work. Microsoft hasn’t released a detailed blog about canvas offline since 2018, and I haven’t seen much out there about real-life scenarios which took a production canvas app offline.

Friends, I lived it (and lived to tell about it), so this month’s blog post will dive us deeply into the good, the bad and the ugly of offline capability in canvas Power Apps. It’s an in-depth topic, so I will be breaking it up into a few parts.

feasibility assessment

So you think you want to take your canvas app offline? Before you go down this path, let’s start with a checklist:

  • Is your app already built?
  • Will your app be used on employee consumer devices?
  • Will your app require large amounts and/or variable amounts of data?
  • Will your app be complex, with many kinds of tasks or functionality included?
  • Do your users require many canvas apps with offline capability?
  • Will your app’s users be able to modify any data, regardless of ownership?
  • Is the app’s offline capability a nice-to-have?

πŸ›‘ If you answered YES to any of these questions: caution! You may not want to proceed with an offline architecture. I will call out these risks with the ‘stop’ icon throughout the rest of this post.

🟒 If you answered NO to all of the questions, then offline architecture is likely a good fit for your use case. Read on!

⚠ Watch out for these gotchas as we go.

the basics

expressions

You’ll be using a few new expressions to implement offline capabilities.

Connection.Connected()

  • This special expression can monitor the connection status of the device running Power Apps Mobile. It requires no parameters within the parentheses, and evaluates in the formula to either true (online) or false (offline).
  • You can also use Connection.Metered() to check when the device is on a metered connection (e.g., cell phone data instead of WiFi) if you need to limit network chatter when the device is using data. When monitoring for either situation, try AND(Connection.Connected(), Connection.Metered()) or similar syntax in your If() expressions.
  • ⚠ Note: Connection.Connected() will always evaluate to true when creating the app in the browser App Studio. This creates a challenge when testing offline capabilities. Check the testing best practices section in part 2 for details.

SaveData(), LoadData(), ClearData()

  • These expressions control how data is stored on the local device for when the device goes offline.
  • SaveData(Collection, Name): The parameter syntax includes the Collection of the data you want to store and the Name of the file when you save it offline.
    • If we have a Collection named “MasterData” and we want to save it offline, our expression would be SaveData(MasterData, "Local Master Data");
    • Notice that only Collections can be stored to the local device.
  • LoadData(Collection, Name, IgnoreNonexistentFile): The parameter syntax is the same for SaveData(), but includes a third optional boolean parameter (true/false) for showing or suppressing an error message if the file you are trying to load does not exist.
    • To retrieve the data we saved above, our expression would be LoadData(MasterData, "Local Master Data", false);
  • ⚠ ClearData(Name): The parameter syntax allows you to selectively clear saved offline data, but be careful! If you don’t specify a file name, it will clear all data saved offline for this app.

Power Fx code

The basic pattern for an offline architecture begins with App.OnStart. For this example, I’ll use the Dataverse Accounts table filtered by the ‘My Active Accounts’ View.

If(Connection.Connected,
     ClearCollect(colMyAccounts, Filter(Accounts, 'My Active Accounts')),
     LoadData(colMyAccounts, "My Offline Accounts", false)
);
SaveData(colMyAccounts, "My Offline Accounts");

What does this code do? When the app starts up, the first thing it will do is check the device’s connectivity status. If it’s online, Connection.Connected will be true, so the code which runs will be to create the colMyAccounts Collection and populate the ‘My Active Accounts’ View into it. Next, it will save the colMyAccounts Collection into the “My Offline Accounts” file on the local device.

Else, if the device starts when it’s offline, it will load in the “My Offline Accounts” file into the colMyAccounts collection. I left the SaveData() on the outside of the If() on purpose to make sure there is always data stored locally whether they start up online or offline.

⚠ Notice that I left the LoadData() error suppression parameter as false: if the user loads the app for the first time in offline mode (or otherwise the local device cache has been cleared), they’ll receive a runtime error since there is no data to show them. You can set this to true to suppress the error, but you’ll need to replace it with a user-friendlier error since the app won’t have any data!

Data must be in a Collection in order to store it offline. This goes for any online data source like Dataverse or SharePoint you want to take offline, as well as locally-defined data such as global variables. If you want to save it, Collect it!

gotta collect them all!

master imperative development first

In canvas apps, it’s possible to develop an app that has no separation between the data layer and the presentation layer. The App Studio will let us write expressions which tightly couple our controls together, directly referencing data sources and other controls and components in our formulas for everything from data set filters to component appearance. In the pro dev world, this is referred to as declarative development: here’s what I want to do, with little-to-no control over how it’s done.

Collections and Variables (both global and local context) are a more imperative approach to canvas app development. You can create a buffer for the data sources within local Collections, and decouple controls by passing them through Variables. In this way, we control exactly how the app executes our commands and separate controls from data sources and one another. I recommend this kind of development approach for all canvas apps to make them more robust to future changes!

(For citizen developers, declarative expressions are what they teach you in every App In A Day, Lab and basic Learning Path. If you wish to proceed with building an offline-capable app, you’ll need to learn how to use Collections and Variables in imperative development techniques to be successful.)

so: is your app already built?

When introducing offline capabilities within a canvas app, we now have two different potential data sources: when online, we can access the data source directly; when offline, we need to access the Collection.

πŸ›‘ This can be a big problem if you have already built your app. Depending upon the size, complexity and development style of the app, adding offline capabilities post hoc could be a huge, complicated effort! You will need to update every control within your app that directly references an online data source to switch it out for a Collectionβ€” and test thoroughly to ensure no feature breaks. Not to mention the new delegation issues which might be introduced as a result!

It is therefore my recommendation to start fresh when you intend to include offline capabilities so they are baked in from the start.

working with production data sets

When a user has gone offline with their canvas app, they are only able to access data which has been stored locally in the app. We’ll need to provide the user with their primary set of data to work with, along with all master or reference data they may need to edit or create new records.

⚠ This may seem like a simple task, but production data sets for real world use cases are generally large, and can involve thousands (if not hundreds of thousands) of rows in the tables. Rock, meet hard place: we know now that all data must be in a Collection to save it offline, and yet we also know that the Collect() and ClearCollect() expressions do not support delegation.

Even for simple app use cases, users often require access to large or even very large data sets. Think about a simple scenario where I need to create a new record and associate it to an Account. I need to bring in all active Accounts, likely filtered by status or an attribute like region, so the user is able to search for any of their customers when working offline. The delegation limit of 2000 records is just not going to work!

Power Automate to the rescue

My favorite way to get around delegation issues in canvas apps is to leverage Power Automate. The canvas app can call Power Automate to offload the heavy lift of fetching and filtering any data set to the server, and simply return the response back to Power Apps. This method can return many thousands of rows from a data source, filtered and sorted any way you please, fully avoiding any issues with client-side filtering or delegation.

🟒 This is a great solution for any issues with delegation, not just for offline scenarios. It’s a little fiddly to set up (see the how to implement instructions below), but I recommend to put this one in your toolbox!

⚠ This is an asynchronous method for retrieving data, and can have a several second impact on app/screen loading time. To enable a fully-offline scenario, this additional load time is worth it, but it may not be acceptable for all use cases.

how to implement

  1. Add a temporary button to the main screen of your app.
  2. Connect the Button OnSelect property to Power Automate.
  1. Select Create a new flow and select the PowerApps Trigger for your instant cloud flow.
  2. Write your Power Automate flow to retrieve your desired filtered data set using OData filtering.
    • Make sure to use the Select column query option to limit the columns you are selecting. If we’re taking the data offline, we only want to pick up useful data.
  3. Add a Response Action to send the data to your canvas app in the body field.

⚠ Do not use the Respond to a PowerApp or flow action! This will let you respond with simple data types only. The Response action is an HTTP response which can send a set of data back in an array.

Difference between the response actions. We want to use the second one.
  1. Test your flow. When the run is successful, open the data source Action outputs to confirm it returned the desired data set. Copy the Body from the Outputs section.
  2. Edit your flow. In the Response Action, open advanced options and click the Generate from sample button. Paste in the Body you copied from the test run in step 6.
    • Steps 6-7 are required to ensure the response to Power Apps will display the data correctly. If you skip setting up the schema, you won’t get any data back in Power Apps!
  3. Adjust the schema to avoid nested Collections.
    • I’ve included a truncated example of the schema I’m using to return filtered Dataverse Accounts in my canvas app. You want your schema to start with the array from the value section of the body.
    • Remove everything that’s highlighted in your schema, including the headers. Include one { at the beginning and the appropriate number of } at the end of the code block.
{
    "type": "object",
    "properties": {
        "statusCode": {
            "type": "integer"
        },
        "headers": {
            "type": "object",
            "properties": {
                "Vary": {
                    "type": "string"
                },
                (...Headers...)
            }
        },
        "body": {
            "type": "object",
            "properties": {
                "@@odata.context": {
                    "type": "string"
                },
                "@@Microsoft.Dynamics.CRM.totalrecordcount": {
                    "type": "integer"
                },
                "@@Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded": {
                    "type": "boolean"
                },
                "value": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            (...Properties...)
                     },
                    "required": [
                         (...Required...)
        ]
    }
}               
  1. Return to the Power App Studio to finish adding your new flow in the Button OnSelect.
  2. Complete the Power Automate run code in the Button OnSelect property with any input parameters required by your flow. Wrap the Power Automate run expression in a ClearCollect() to store the response as a collection.
    • ClearCollect(OfflineData, OfflineDataRetrieval.Run(Parameters));
  3. Copy the Power Fx code from step 9 and paste it into App.OnStart.
  4. Delete the temporary button.

If all goes well, you should be able to create these small Power Automate instant cloud flows to return all data sets required for your offline use case.

Wrap all flow runs in Concurrent() to ensure parallel performance, but know the more complex your flow is, the longer it will take for your app to start up. Consider adding in an animated GIF loading screen here for a better user experience.

data storage considerations

Now you have the tools to get all of your data stored into Collections to take it offline. Before we roll full speed ahead into the rest of our offline architecture, however, you need to consider whether the local device will have sufficient storage for all your data.

πŸ›‘ Canvas app offline data is not stored in device storage. It is stored in app memory. Your device may have many gigabytes of data storage available on the phone or in SD cards, but currently Power Apps Mobile does not store any data there. App memory is much more restrictive and hard to track, making it difficult to estimate how much memory is truly available.

The official documentation itself is not clear on just how much app memory Power Apps Mobile will dedicate to your offline data. The best guess is between 30-70 MB, and where you will fall in that range is dependent upon a few factors:

  • Device model
  • Operating system
  • Memory being consumed by Power Apps Mobile
  • Individual canvas app complexity
    • Number of screens
    • Number of controls
    • Amount of embedded media

checking data set size

You can use Monitor to check the response size for the data you are loading in. If you’ve never used Monitor, it’s a simple way to test what’s happening behind the scenes as you play your canvas app. It can be a real life-saver when troubleshooting issues!

To start a Monitor session, open Advanced Tools in the left-hand menu of the App Studio. Put your app into preview Play mode (F5) and start interacting. I left my temporary button on the home screen so I could trigger the test, and Monitor’s output showed a 200 success response of 1.6 seconds duration and 14.6KB size to retrieve the 10 Account rows I have in my sample Dataverse.

In this way, you can determine the amount of app memory you will need at a bare minimum to successfully store your data offline. Then, calculate size of all embedded media planned for the app (those images and animated GIFs can really add up).

🟒 If you’re well under 30MB, you’ll likely be OK.

⚠ Between 30-70MB, you’ll want to be careful with the complexity of your app as it could cause you to run out of room on older devices.

πŸ›‘ Over 70MB, and you need to reduce the amount of embedded media and/or amount of data to store as you will likely run out of room on most devices.

re-assess feasibility

Let’s revisit the checklist from the beginning of this post. If you answered YES to any of the first five questions, hopefully this post helped you learn why offline might not be a great fit for your app. I’ll cover the last two questions in part 2.

The offline storage limitation is what truly causes my hesitation. The limit isn’t well understood or monitored, and you can’t control for it across different devices. The last thing I would want from one of my apps is an inconsistent poor user experience where some users don’t get access to all their data or get ugly runtime errors when Power Apps Mobile runs out of space.

I would not attempt an offline architecture for a canvas app that has any of these characteristics:

CharacteristicExplanation
❗ App is already built / existing App uses declarative expressionsAdding offline capabilities to an existing app may require large parts of the app to be rebuilt entirely
❗ Dependent upon the user’s consumer deviceA large variation in the device type, resolution, operating system and device age will likely create an unacceptable variation in user experience
❗ High quantity and volumes of dataRequires an unfeasibly large amount of data to be saved for offline reference
❗ Complex appThe number of screens, components and/or embedded media can reduce the available memory storage for the offline data
❗ Multiple offline appsUsers running multiple apps in offline mode may reduce overall memory available

If you’re still with me and eager to implement offline in your production app, head over to an honest guide to canvas app offline (part 2), in which I will walk through managing user-modified data, synchronizing changes to the server, testing best practices and a few more gotchas!

3 thoughts on “an honest guide to canvas app offline (part 1)

Add yours

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: