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!

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

Add yours

  1. Hi, I’m trying to implement the Flow but I’m stuck on Step 8.

    My schema is as follows, but I get an error in PowerApps Monitor:
    JSON parsing error, expected ‘object’ but got ‘array’

    {
    “type”: “array”,
    “items”: {
    “type”: “object”,
    “properties”: {
    “client_no”: {
    “type”: “integer”
    },
    “title”: {
    “type”: “string”
    },
    “surname”: {
    “type”: “string”
    },
    “forename”: {
    “type”: “string”
    },
    “dob”: {}
    },
    “required”: [
    “client_no”,
    “title”,
    “surname”,
    “forename”,
    “dob”
    ]
    }
    }

    Like

    1. Hi Duncan,
      Did you make changes to your Power Automate cloud flow after it was already added to the canvas app? I have experienced issues previously where my flow gets “cached” by Power Apps and so structural changes to the flow schema aren’t recognized when I test the canvas app and trigger the flow.

      Try removing the Power Automate flow from your canvas app entirely (remove it from the Data Sources area) and adding it back. You may need to save a copy of your Power Fx code in a notepad and delete it for this to fully work. This will resync the flow schema to the canvas app.

      Let me know if that resolves the issue!
      /joy

      Like

      1. Yes it was exactly this! I spent ages trying to sort it and then fixed it at the last minute by removing and re-adding the Flow.
        Its annoying, as I have experienced this issue previously but never thought about it this time.

        Liked by 1 person

  2. Hi Joy,

    What would you suggest for an app where the users need to use it offline and have access to multiple forms (currently 10)? Would it be OK having x amount of forms in one app or is the preference to have multiple apps with a form in each to use offline?

    My app is currently structured with a home/welcome screen with a form chooser on it. Each form is linked to it’s own form (list) screen which shows each of the forms that have been previously submitted. You can click into a form to see it on its own form screen (the same for new form action).

    So essentially there are 10 form list screens and 10 form/edit screens plus the home screen, and a branding screen in the background. So that’s 22 screens in total.

    Offline functionality has come about as a new feature request and we’re happy to rebuild the forms to facilitiate this.

    Last thing, is it better to make a patch statement form rather than using an ‘edit form’ control? I’ve recently used form patching for another app and this gave me greater flexibility for overall design of the form, whereas layout control is limited on an edit form control.

    Looking forward to your reply.

    Thanks,
    Chris

    Liked by 1 person

    1. Hi Chris,
      The guidance here will depend on a couple of things: how many fields per form, how much reference data is required (are any of the form fields combo boxes to lookup up existing data?), and how many existing rows are you trying to display in offline mode?

      All of these could have an impact on the amount of data which must be saved offline, which in turn impacts the use case feasibility. For example, 10 forms x 5 fields each which are all text inputs (requiring no reference data) is probably just fine– whereas 10 forms x 50 fields each with many reference tables for lookups could push you over the device memory storage limit and cause issues.

      I would recommend to scrap the list/gallery feature for each form type. Not only does that double the amount of screens and controls for the app itself (also eating into offline device memory), but all of that data would have to be collected and saved offline, too. If it’s a necessary feature, I would try to 1) rationalize the screens down so you can have a single list/gallery screen capable of displaying any form list and control the filters with variables and 2) restrict the amount of previously submitted forms to a reasonably small number (e.g., 5-10 each).

      Testing max data volumes with an app in offline mode will be critical to your success. Since we currently don’t have great visibility into the device memory, it’s hard to say whether your use case will encounter offline issues until you run water through it and test for holes.

      As for Patch() with input controls vs Form, I can go either way on this! Using a Form has a benefit of picking up data definitions from your data source, auto-formatting the data cards within the Form control, and has screen-reader/accessibility advantages. On the downside it is a click-heavy affair to customize the data cards and the Form control can be limiting on the UI presentation. In either case, you’ll have to use Patch() of either your individual input controls or the Form itself to Patch offline submissions to the Collections, because SubmitForm() will only try to submit back to the online data source.

      I hope this helps! Please reach out if you need additional advice.
      /joy

      Like

      1. Hi Joy,
        I’ve just checked and one of the larger forms has 103 columns so I’m thinking it may be better to have a form per app. Our users would be using the Windows Power Apps app on their corporate laptops.

        Due to the nature of their work, there will likely be a high number of forms stored in the SharePoint list. However the idea is that once the form is completed, it will eventually be saved off to a PDF and stored in a record centre document library.

        Liked by 1 person

      2. Microsoft hasn’t updated their documentation about the available memory for offline data when using Power Apps for Windows. It’s possible that PC memory allocation works differently from mobile and tablet devices, and you may have more available for your use case. In any case, given the high complexity of your SP List forms, I agree that splitting the forms into separate apps seems to be the best way forward. Good luck with it! /joy

        Like

Leave a comment

Blog at WordPress.com.

Up ↑