an honest guide to canvas app offline (part 2)

Before you continue— make sure you read through an honest guide to canvas app offline (part 1) first! There you will find a basic guide to the offline expression pattern, advice on the proper development approach, a step-by-step guide to loading in all your data via Power Automate, and a first set of recommendations on whether or not to built out offline capability.

We’re now ready to dive into the practical application of offline architecture. How do we keep track of data modifications? What about synchronizing changes to the server? How do we efficiently test and debug offline code?

Look out for these indicators and tips:

🛑 High risk or blocking issues for offline architecture

🟢 Best practice tips

⚠ Gotchas and things to look out for

Buckle up! This second part has a lot of ground to cover!

tracking user-modified data

After following Part 1, our theoretical offline app has all of the reference data it needs for the user to enjoy a read-only experience. When the app initializes in an online state, we run PowerFx expressions that retrieve all the requisite data from the server and store them in local Collections, and then save the data for offline use.

But a read-only experience is not going to cut it! In almost all use cases, the requirements will dictate that the user both updates existing data and creates new data while they’re offline. We need to create the infrastructure within the app to do so– and this means saving more data offline!

creating the dirty collection

To borrow from web coding concepts, a “dirty” flag variable is implemented to indicate when a form or webpage has unsaved changes. After tripping the dirty flag to true, web sites and apps will prompt you with an “Are You Sure?” dialog if you try to navigate away, providing you with an option to abandon your changes and proceed, or cancel your navigation and return to the unsaved form. We need a similar concept within our canvas app to monitor when there are unsaved changes and keep track of the rows which need to be synchronized.

these gifs just pick themselves

We start by setting up the Collection structure, and then creating screens that allow the user to edit or create some data.

🟢 Use input controls like Text and Dropdown to allow users to submit changes using Patch().

⚠ You can use the Form control, but not the SubmitForm() expression. SubmitForm() is a declarative-style expression which only synchronizes changes to the server— obviously not an option when creating an offline scenario. This isn’t a blocking issue for me, as I normally find the Form control to be too restrictive from a UI & layout perspective, and use input controls and Patch() as my default.

how to implement

  1. Implement the offline data collection technique described in Part 1.
  2. In App.OnStart, create a new Collection with Collect(). Your Collection needs to have the same schema as your target data, so an easy method is to collect the first record from your data source and then clear it. (Note: this code should only be executed when the app is online, so it’s best to wrap it in an If(Connection.Connected...) formula. Thanks Gil for pointing this out!)
Collect(
    colDirtyAccounts,
    First(Accounts)
);
Clear(colDirtyAccounts);
  1. Create your edit screen. Link it to navigation from the beginning screen, and ensure you send the context of the row to update with a handoff variable (in my example, I used Navigate(scrnEditAccount, Cover, {selectedItem: ThisItem}) to pass the Account context).
  2. Add your input controls to your screen. Set the default values to the data from your selectedItem.
  3. Create a Button or similar trigger to save changes.

At this point, your app might look something like this.

My example will be modifying the Account name, website, email and Primary Contact while offline.

⚠ Depending on your scenario, you may have a great deal more data which can be edited. Always keep in mind the amount of data planned to be saved offline, and streamline the attributes which can be edited to the bare minimum required to enable the business process. “Nice to have” attributes should be saved for when the user is back in the office!

Basic form structure using text and combo box controls
  1. Add a Patch() to the OnSelect property of your Button and save all user changes to the dirty collection.
    • Don’t forget to include the GUID of the record you will update! This is absolutely required when working with updates to existing data to prevent accidental data duplication.
    • My code looks like this.
Patch(
    colDirtyAccounts,
    Defaults(Accounts),
    {
        Account: GUID(selectedItem.accountid),
        'Account Name': txtAccountName.Text,
        Website: txtWebsite.Text,
        Email: txtEmail.Text,
        'Primary Contact': First(cmbPrimaryContact.SelectedItems)
    }
);
  1. Add an additional Patch() to the existing offline collection, too.
    • 🛑 This step may seem unimportant, but if you don’t update the working set of data, your users may falsely conclude that their changes weren’t saved. Updating both collections will prove to the user that their edits exist, and hold onto all new and updated records which still need synchronization to the server.
    • 🟢 Notice that I’ve wrapped everything into an error handling expression (which ensures the user is aware when changes are not saved) plus some Concurrent() expressions for performance. When there are no errors, the final step pushes the updated collections to local memory with SaveData() and navigates back to the gallery screen.
    • ⚠ When you test this code in the canvas app studio, you will get a runtime error because of the SaveData() step. This is normal and can be safely ignored. The browser doesn’t have the same device memory infrastructure as a tablet or mobile phone, so Power Apps throws an internal error.
IfError(
    Concurrent(
        Patch(
            colDirtyAccounts,
            Defaults(Accounts),
            {
                Account: GUID(selectedItem.accountid),
                'Account Name': txtAccountName.Text,
                Website: txtWebsite.Text,
                Email: txtEmail.Text,
                'Primary Contact': First(cmbPrimaryContact.SelectedItems)
            }
        ),
        Patch(
            colOfflineData,
            selectedItem,
            {
                name: txtAccountName.Text,
                websiteurl: txtWebsite.Text,
                emailaddress1: txtEmail.Text,
                _primarycontactid_value: First(cmbPrimaryContact.SelectedItems).contactid
            }
        )
    ),
    Notify(
        "Data could not be saved",
        NotificationType.Error
    ),
    Concurrent(
        SaveData(
            colDirtyAccounts,
            "Offline Accounts"
        ),
        SaveData(
            colOfflineData,
            "Offline Data"
        )
    );
    Navigate(
        scrnAccountGallery,
        Cover
    )
);

collecting new versus updating existing

Implementation of the dirty collection for new data entry is actually simpler, since you don’t need to maintain the context of the selected record or store the existing GUID. It’s up to you if you want to add new data into your existing offline collection (colOfflineData) as well as the dirty collection. Generally, I would recommend to keep it separate until the changes have been fully synchronized to the server, but your use case requirements should make the final determination here.

viewing pending changes

When we built out our real offline use case, we had a requirement to preview the offline changes to the user so they knew how many records they had created or modified, and how many records were pending synchronization to the server. We ended up using filtered Galleries to expose the dirty collection to the user, plus some helpful counters in the header to show the user how many records were sitting waiting to be synced.

While it will depend on what you’re building, I generally find this helpful to keep the end user informed and make sure they are alerted of pending changes before they close out of the app. If a counter doesn’t make sense, even just a text or icon indicator for pending changes is a good idea. Closing the app and dismissing it from active use can result in data loss since all saved offline data is stored in app memory. Keep your user informed to avoid tragic data loss situations!

managing a hybrid app

I imagine there’s a wide range of applications for offline functionality in canvas apps. Not all apps are going to be used in exclusively offline scenarios, or not all users may be affected by a patchy cell signal while out in the field. My first inclination when building an offline app was to attempt bifurcation of the code depending on the app connectivity status. For example, if the app is online, Patch directly to the data source, else if the app is offline, Patch to the dirty collection.

🛑 Don’t attempt this. Write your app architecture in such a way that all changes hit your dirty collection, and are subsequently integrated back to the cloud asynchronously. There are a number of reasons why, but here are the main ones:

  • Any code which relies on an API call would attempt to evaluate, even if not in the If() branch that’s executing (see the section on option sets / choices below)
  • Every data entry/update feature would require double the amount of PowerFx code to write and maintain
  • You can write your synchronization code to achieve near real-time performance even when using the dirty collection data architecture, so there’s no real downside to keeping it simple

synchronization

There are two methods of synchronization to consider for your offline app: scheduled and on-demand. You can use both of them in the same app to provide a multitude of choices for your users.

scheduled synchronization

This method uses the Timer control to periodically ping the device’s connectivity status. Set the Duration to an appropriate interval for your use case (somewhere between 5-15 minutes seems reasonable), and then start your OnTimerEnd code with If(Connection.Connected…).

In my real-life scenario, users anticipated being offline the majority of the time. At first, we started with a hard-coded interval of 15 minutes, but quickly realized that with a Dataverse back-end, it was easy to create an even better experience by letting the managers set the default interval for their location on the table, so they could increase or decrease the synchronization interval based on what made sense for the size of their location.

how to implement

  1. Add a Timer control to your app on the main screen.
  2. Set the Duration value (start with 60000 for a 1 minute interval, 300000 for a 5 minute interval, or set this to a variable so it can be changed dynamically).
  3. Set the OnTimerEnd property to check the connection status of the device: If(Connection.Connected, )
  4. Write your synchronization code to Patch() the dirty collection back to the data source, leveraging ForAll() to perform the operation in bulk.
  5. Remove all rows from the dirty collection with Clear().
  6. Refresh your data set from the data source, re-collecting it into your offline collection. Make sure you terminate with a final SaveData() step so the user can take the refreshed data offline right away if their connection is spotty.

For other scenarios, where offline architecture is required just in case of poor connection, you might want to consider a fancier approach and use PowerFx code plus variables to set up a polling interval! By tuning the polling interval settings, you can achieve near real-time data synchronization when the device is continually connected, while preserving app performance by backing down the polling interval if the device is offline for an extended period of time.

Here’s what the below code snippet will do. If the device is connected, the dirty collection data is synchronized to the data source and the polling interval will remain at 1 minute. Otherwise if the device is offline, it will increase the polling interval by 5 minutes with each subsequent run up to a maximum of 30 minutes. If, after the extended polling interval, the device goes back online, the dirty collection will synchronize and reset to a 1 minute interval.

If(
    Connection.Connected,
    //code to synchronize dirty collection to cloud data source;
    Set(
        PollingInterval,
        60000
    ),
    If(
        PollingInterval < 1800000,
        Set(
            PollingInterval,
            PollingInterval + 300000
        ),
        Set(
            PollingInterval,
            1800000
        )
    )
)

Pair this code with setting the Timer.Duration to the variable PollingInterval, and make sure your App.OnStart sets the PollingInterval start value. I recommend to start with 1 minute (60000 ms), but theoretically you can set it as low as 50 ms. Chose intervals that make sense based on the general use case, and don’t be afraid to tweak it later based on user feedback.

manual synchronization

Just as it sounds, the manual synchronization option is an on-demand push of pending changes in the dirty collection up to the server. Simply tie the same code as described above into an interactive element like a Button, Icon or Image, and allow users to override the default interval to synchronize when they please.

Of course, you should always wrap that in an If(Connection.Connected, ) expression, because otherwise your users will get a nasty runtime error trying to synchronize if they’re actually still offline. Disabling the button while the app is offline is likewise a good protective measure.

If you implemented a polling interval, be sure to include some code to reset the interval to the baseline if a user successfully performs a manual override.

downsides

🟢 Will your app be used to capture net-new data only, with no feature created to allow users to modify existing records? This section will not apply to those scenarios, and there are no additional actions to take when implementing your canvas app offline architecture.

⚠ Will your app be used to capture new data and modify only data owned by the app user? This section will likely not apply, but proceed with caution.

🛑 Will your app be used to modify any existing data, regardless of ownership? Stop and carefully read this section. If you allow unfettered access to edit any existing data by anyone, you now have to implement a conflict resolution process for each editable data set you add to the app.

Without additional PowerFx code, none of the above synchronization methods will prevent data loss due to multi-user edit scenarios. Multi-user edit scenarios are common when users are working broadly across an application, especially if there is little security overhead keeping users from touching each other’s records (like a locked-down Dataverse with strict Business Unit Hierarchy and Security Roles).

When multiple users have access to edit data when either online or offline, every time your canvas app synchronizes the dirty collection to the cloud database, your PowerFx code must check for any difference in the last modified dates and make a decision whether to synchronize that row or not.

I won’t write you a code snippet for this, because I would honestly recommend against it. There is a lot of complexity to consider when there is a conflict, such as:

  • How do you decide who wins?
  • Are the affected user’s changes discarded completely?
  • Will you give the affected user the choice to force a data override?
  • Do online user changes have priority over offline user changes? Or vice versa?
  • What about changes performed via automation, API calls or bulk jobs?
listen to Johnny

In my real-life scenario, we mitigated this risk of a multi-user data overwrite scenario by: limiting the editable data to just one set (everything else was just read-only reference data); filtering the data we brought into the canvas app to only rows assigned to the user (preventing them from accidentally updating someone else’s record); and not including any user scenarios where someone “back in the office” would be creating or updating new data while people were out in the field. I felt comfortable with directly synchronizing the dirty collection to the cloud database without a conflict resolution implementation because we had constrained the business process and security roles to avoid the situation altogether.

If your use case is more open-ended, proceed with caution, and if you have to, put in the extra work of a conflict management feature.

option sets / choices

Before we’re done talking about this hefty topic of offline, a quick note regarding everyone’s favorite Dataverse object type: the picklist slash option set slash choices. For the sake of sanity and grammar, I’ll continue to refer to them as Option Sets in this post.

If you’re not aware, Option Sets are a special data type in Dataverse and have been around at least since CRM 2011. Option Sets are two-pronged: first, the set of choices are defined as a solution-aware object; and second, the set of choices are bound to a column on a table. One Option Set can be bound to multiple columns across multiple tables, and the set of choices are centrally defined on the Option Set object. Cool.

🛑 How many Option Sets are in your data set(s), really?

⚠ Do you anticipate needing to change Option Set values on your data? Reference Option Set values in filters and conditions?

I’ll save you all the hair-tearing frustration we encountered and recommend you think very thoroughly about your data model before you decide to proceed with the offline functionality. When working with Dataverse Option Sets in canvas apps, each time you reference an Option Set value in a table, it will make an API call to Dataverse to check the global Option Set list.

When you are offline, this poses a huge problem. Our real-life use case burned way too many cycles trying to sort this one out. We had several boolean/Two Option fields on our table which were used in an If() condition during the synchronization Patch(). The app was working perfectly online and offline, but as soon as we tried to synchronize our dirty collection to Dataverse, the app would spin for 1 minute and ultimately time out with a runtime error.

Through some testing and troubleshooting, we saw the API calls attempting to contact Dataverse. Each call had a 20 second timeout and 2 retries… for a total of 60 seconds. 💡 Lightbulbs went off. This was happening even though the reference to the Option Set was not in the If() branch condition which was executing. It looked something like this:

If(Connection.Connected,
Patch( {OptionSetField: OptionSetObject.OptionSetValue}
),
Patch( {StringField: StringValue} )
)

The entire If() statement was being evaluated as a block, so the Option Sets needed to be evaluated, so the API calls were triggered. This changed our entire approach. In this case, our requirements were simple enough that we swapped out Option Set fields for Text strings and moved on with our lives, frustrated but wiser.

how to safely implement option sets in offline

There is a way to bring Option Sets into your offline data, but it is a workaround. Many thanks to my colleague James Battams for sharing his wisdom from his own real-life implementation of this method.

  1. In App.OnStart, declare a global variable for each Option Set you will work with.
  2. Create an array with each choice value.
example of the variable code using the Preferred Method of Contact field on the Contact table
  1. Leverage the variable(s) wherever you would normally reference the Option Set. For example, set it to the values of a combobox or dropdown input; or reference it with Filter() or LookUp() conditions in your PowerFx code.

10/13/2021 Update: This paragraph has been updated to amend an error about saving your Option Set Variables offline. Thank you to John at Strategy365 for pointing this out! Since storing the Option Set values within a Variable does result in a getOptionSetItems API call, you do need to save the variables offline. Otherwise, when your app is started when offline, they will try to call Dataverse for the Option Set object definition and result in the timeouts I described above. You will need to implement this method for each and every Option Set you want to use throughout your offline app. Good luck!

testing best practices

Last but not least, testing the offline capabilities of your canvas app can prove to be challenging. I’ve got a few best practice tips to help you be successful.

  • When writing the app, start with a placeholder variable instead of Connection.Connected. The browser does not recognize this expression and will always evaluate to true, so sub in a variable which you can control to test your offline code.
  • Ignore SaveData() and LoadData() runtime errors. There’s nothing we can do here: it’s the same kind of error as trying to test the barcode scanner in the browser.
    • (Note: there is a currently-experimental feature as of 12/15/22 named “Enabled SaveData, LoadData, ClearData on web playerwhich will prevent these errors. Try it out!)
  • Use Monitor to see what’s happening behind the scenes. Here you can see the response performance and size in kb of the data returned by API calls. (This is where we found the Option Set API calls!)
  • Approximate an offline scenario by setting your network throttling to 0kb / Offline.
    • This varies by browser, but in Google Chrome you can find it under F12 > Network > Throttling.
  • New! Try using Power Apps for Windows to test the app while your device is in Airplane Mode. This can be a bit faster than cycling through airplane mode on a mobile device.
  • Test on a real mobile device with airplane mode. Nothing compares to the real thing– make sure you test like a real user on the device which will be used. If you plan to let users bring their own consumer devices, then test on as many different device types as you can.
    • It’s a good idea to issue minimum requirements to run your offline app to your users. You don’t want to be responsible for supporting very old devices!
  • On a real mobile device, test to see if your offline architecture is impacted by actions like switching between apps, switching between different Power Apps, dismissing the app from memory and other likely actions your users could take during operation. Be sure to issue training guides and job aids if any of these actions could have a negative impact on their offline experience.

re-assess feasibility

Phew, that was a lot. Let’s revisit the feasibility checklist to ask yourself if an offline use case for your canvas app is a good idea. We covered the first four questions in Part 1:

  • Is your app already built? Adding offline features after-the-fact could be a huge lift.
  • Will your app be used on employee consumer devices? The end-user’s device can have a big impact on the user experience.
  • Will your app require large amounts and/or variable amounts of data? Data has to be stored in app memory, giving us only 30-70MB of storage to work with.
  • Will your app be complex, with many kinds of tasks or functionality included? The amount and complexity of components in your app can reduce the offline app memory available (and, of course, complex apps usually mean complex data sets!)
  • Do your users require many canvas apps with offline capability? Running multiple canvas apps while offline may negatively impact the amount of available app memory.

We are left with a few final questions to understand:

  • Will your app’s users be able to modify any data, regardless of ownership? Synchronization from offline to the server can introduce data loss when multiple users could be editing the same data at the same time. If you don’t take care with the data flow design, you’d need to build in conflict resolution capabilities for each data set a user could modify.
  • Does your app’s Dataverse model have way too many Option Sets / Picklists / Choices? These data types are a real pain to work with when you go offline, and you’ll need to manually re-create them within global variables to ensure your app will work offline.
  • Is the app’s offline capability a nice-to-have? Offline capability requires a lot of extra work in design, development and test. If offline isn’t core to your use case, then my recommendation would be to look at model-driven app for occasional offline use, and leave the pixel-perfect apps for your connected users.

closing thoughts

I had a lot of fun figuring out the architecture of an offline canvas app. In reflecting back throughout the writing process, it’s clear that today’s canvas app offline capability is for narrow use cases only. It leaves the architecture 100% up to the app maker, with little guardrails to prevent unwary makers from stumbling into difficulties and dead ends.

It is my hope that this blog provides insight and guidance on whether and how to move forward with your offline dreams. Microsoft’s canvas app offline roadmap looks extremely promising, and I expect we’ll see some marked improvements to the functionality in the next few release waves. Until then, take these lessons and avoid some of the pitfalls we ran into!

Happy app building!

26 thoughts on “an honest guide to canvas app offline (part 2)

Add yours

  1. I loved this offline mode article as much as the first one. PowerApps needs more opinionated coverage like this.

    Your coverage of offline mode was quite exhaustive and I could relate to all of it. However, there was one area I’ve spent a lot of time thinking about that I did not see: creating new records with relationships. GUIDs can’t be created on the user device so you have to retrieve them from the server and then update any related local records on the local device before writing them too.

    But then… what if you drop a connection while writing a record? You have to find some way to track the errors and make sure they are resolved with the new GUID.

    Crazy, ain’t it? I feel like we could easily fill an hour talking about our experiences.

    Liked by 1 person

    1. You can do relationships to existing records (all that data would be stored in your offline collections), but you are absolutely spot on that creating a parent record with one or more child records all while offline poses another giant challenge that my guide does not cover. Are we creating local primary & foreign key sets and then trying to convert to GUIDs during synchronization? Or creating nested Collections with multiple loops of synchronization? (What a headache.) Another reason to rethink offline if that’s in your requirements!

      Thanks for your insightful comment!

      Like

      1. Yes, I am working in Dataverse. Local primary & foreign key sets are created as GUIDs. During synchronization I retrieve newly created GUIDs from the server and replace primary & foreign key sets on the local device. There are no nested collections involved.
        I would love to be able to create a GUID on the local device and writing it to Dataverse. Swapping GUIDs makes like difficult. I know a way to circumvent this and directly write the GUID but alas, per Microsoft, this is not supported…

        Like

      2. Directing writing the GUID on-create is supported via bulk import and the Dataverse API, so I’m not sure why canvas apps setting the GUID via Patch() would work any differently. Theoretically, all the aforementioned methods are using the same API definitions.

        Like

  2. (tried to continue the thread but the comments section did not allow it).

    Wow, I had no idea directly writing the GUID on create is support via bulk import and the Dataverse API. Do you have a link to the documentation you could forward to me. I would be highly interested in reading it 🙂

    Here’s a link to my own work-around for directly writing the GUID. You can bulk import a collection in Power Apps using Patch. It will be treated as an upsert (update if the record exists, create a new record if none exists).

    Link: https://github.com/MicrosoftDocs/powerapps-docs/issues/2300

    I tried to send you an email message with some other interesting tidbits on offline mode but joy@joyofpowerapps.com did not work. If you’d like to hear them I’d be happy to re-send to another address.

    Like

    1. In my days as a migration analyst, I used both ETL tools and the Bulk Import feature to import new data into the CRM database including the GUID. Was a useful technique when someone accidentally deleted master data, or when I needed GUIDs of master data to match between environments. There’s not much in the way of official documentation, but there’s a large set of blogs out there detailing the process. (Check out this one for example: https://ricktowns.com/post/2020/06/23/importing-data-with-guids-into-dynamics-365) Since the database itself can handle an insert with a GUID via these two methods, I don’t see why it would be unsupported by Microsoft or why we wouldn’t be able to do a similar action through the Dataverse connector in either canvas apps or Power Automate.

      It’s risky, of course, because if you’re generating your own GUID outside of Dataverse, you’re introducing the possibility of accidentally generating one that’s a duplicate of an existing GUID. The possibility is 1 in 2¹²⁸, but it’s still something to think about. (https://powerobjects.com/dynamics-365/guids-dynamics-365/)

      Email forwarding is now fixed! Please send again. 😊

      Like

      1. Hi guys, i’m working in a project with an offline app and I was in the same scenarios. That’s incredible, because it’s a very common case, but is so hard to find some content in the internet to help us.
        I was reading your talking and feeling all challenges that you had. I created one method to help me in this case, too.
        Thinking in 2 collections who need to have relationships, I created a GUID for the first collection and saved this GUID in a column of my second collection.
        When the app is online, the user click in a button to save in Dataverse the first collection using ForAll() and inside my ForAll, I have the Patch() function inside a Collection() function, to return the GUID created in Dataverse.
        With this, I update the column lookup of my second collection using the GUID returned and the GUID created to reference.

        Like

  3. I’m a little confused by “There is not a need to save the variables offline, since they are being hardcoded in the OnStart code (and not dependent upon an API call).” In the example you’ve given, wouldn’t it need to make an initial call to Dataverse to query the various options on a Contact? I was hoping that maybe it was just pushing the underlying values in to the variable and just displaying the friendly name, but when I try to implement this, when the app is offline, it seems to be constantly trying to make the query live rather than just using the set values.

    Liked by 1 person

    1. Hi John, you are right about this. I ran a few tests on my side with Monitor open to identify the API calls. When you collect your Option Set values into a variable, you are directly referencing the Option Set object in Dataverse so that will generate the getOptionSetItems API call. For offline capability, you do need to save the Variable in your offline data set to prevent your app from trying to call Dataverse upon App.OnStart when it’s in offline mode. I will update the post. Thank you for calling it out!

      Like

      1. Thanks for looking into it and replying… glad I wasn’t going nuts! The app I’d been working on was still trying to make calls online while offline and I figured it had to be these option sets. I’d switched to collecting them and storing them offline and everything then behaved as expected.

        Some other info that might be useful too: you mention that you can’t use the SubmitForm expression, but if you build the form as though it’s an online form and then switch the data source to the collection afterwards (assuming the schemas are the same), it will submit to the collection instead. For my other apps, I’d done exactly what you’d done with just patching to the collection, but for my current app, there were a lot of fields, so I wanted to use the column structuring of the form builder. Everything seemed to work fine (it was nice to be able to just have one line of code to submit the data for a change instead of a lengthy patch!) although doing this kind of switch does leave some traces of online calls for things like option sets and lookups which need to be fixed manually.

        Anyway, enough rambling – thank you for the blog post as it steered me in the right direction for handling the option sets. 🙂

        Liked by 1 person

      2. Good note about using a Form for a Collection! I believe you can also Patch() an entire form instead of using the SubmitForm expression to keep everything offline. Depending on the size of your data sets, these are good tips for making the code more manageable.

        Thank you so much for the feedback. I’m glad that the post was helpful to you and you got everything working!
        /joy

        (I had to reply to my own post since WordPress doesn’t allow for long comment chains. 🙄)

        Like

  4. Thanks Joy for this wonderful post. A couple of questions if I may,

    1. You mentioned “Microsoft’s canvas app offline roadmap” and how it looks promising. I’ve had a look for this on the web and cannot seem to find anything very descriptive. Would you be able to share more details on this? In particular, what Microsoft are doing (if anything) about the limitation of using device memory for saving offline data.

    2. In quite a few instances you recommend that we rethink whether we want to forge ahead with developing an offline-capable canvas app. If we decided that this was too hard, what would be your recommended platform.

    Thanks in advance.

    Liked by 1 person

    1. Hi Steve,

      Thanks for your kind message! I hope the post was helpful in your canvas-making endeavors.

      1. Microsoft’s roadmap is not always publicly available in their blogs or documentation– often, these nuggets are shared in conversations and presentations which are covered under NDA for their partners and customers. I’m not able to share any actual details for this reason! I recommend to register for the upcoming Microsoft Ignite conference Nov 2-4 (https://myignite.microsoft.com/home) as I am sure there will be a lot of exciting announcements about the Power Apps roadmap forthcoming.

      2. If your use case is too complex for an offline canvas app, there are several paths to explore. First, I would look to see if the use case could be simplified or if the offline experience could be limited to only the core business processes. If this is possible, you could restrict navigation when offline to the offline-capable Screens and limit the amount of offline code you need to write. If this is not feasible, I would look to moving the business process into a model-driven app. The model-driven offline capabilities are currently much more extensive than canvas app because the UI is a known quantity. It’s not ideal, since you lose the control over the UI, but it can make sure your users have access to all the data they need offline. Third, if neither of the above options are palatable, consider waiting out the roadmap. We’ll see what gets announced at Ignite and what the roadmap timelines are for improving this experience– who knows, maybe my whole honest guide to canvas app offline posts will be obsolete before the year is out!

      Like

      1. Great plug for Ignite! Yes, as far as “waiting out the roadmap” goes, I was trying to find out how long I may need to wait. Let’s see what Ignite brings.

        Like

  5. Hello, Thanks for this awesome article! I have a question though as I am spinning my wheel trying to load data for lookup fields. In your example, you passed the Account context as a SelectedItem variable into your controls. I am finding that it works when online but it is empty when offline. In your case it is the PrimaryContact lookup field. In my case, it is the bookable resoure look up on the Time Entry table in Dataverse. Do anything else special for this to work?

    on my combobox Items property i have this:

    Choices(SelectedItem.’Bookable Resource’)

    ..and selected the Name field to be displayed.

    It is populated when Online but not when offline.

    Thanks in advance!

    Like

    1. Hi Kim,
      My code snippet examples are a bit simplified, which is likely why you’re running into issues. I did not show saving the Contact table offline in my example, but that’s what I would need to do in order for my example to properly work offline.

      By nature, LookUp() and Choices() commands are API calls to a data source. Those commands instruct the canvas app to call the data source and return information (for LookUps, that’s a reference to a specific record; for Choices, it’s an array of records defined by the originating data field). The challenge here is that all reference data tables must also be available offline for this to work, and is one of the greatest downsides when trying to create offline data in canvas apps.

      In your scenario, you would need to ClearCollect() the entire Bookable Resource reference table and save it offline. Your LookUps or Choices commands would then need to reference the locally-cached Collection (instead of the Bookable Resource table in D365 FS) to prevent your controls from trying to call the Dataverse API.

      I hope this helps! Please don’t hesitate to reach out if you have further questions.
      /joy

      Like

  6. Hi Joy, and thank you for a very complementary and wonderful article on this topic!

    The pitfalls you mention, I’m sure many will come across, and it’s good to be able to compare my own experiences with your experiences and recommendations.

    The reason I get in touch is to hear if you have any experience with what lies beyond actually building the application, in the Power App-mobile application specifically. I experience that solutions and logics that are built work great, but that end users often struggle to find / open the applications.

    My experience is that you naturally have to open an app in the Power App application for it to be cached. But very often I see that if you go offline / in flight mode you still can not open the app. Do you know where the app itself caches (memory?), And how long it is valid for? I often experience error messages from users that they are trying to open applications and get “Something went wrong. There seems to be a problem with the internet connection” which I think indicates a lack of caching, but that the user claims to have opened the application with internet not long ago.

    Also experience that the Power Apps application says that there are no applications on the device when you do not have internet, even though you have just opened an application with the internet and that you can open an application from a shortcut stored on the Home screen of the device.

    These things seem to me like bugs in the Power Apps application itself, but would have been fun to hear your views and experiences.

    Thank you for sharing!
    Sondre, Norway

    Liked by 1 person

    1. Hi Sondre, thanks for your great question!

      Admittedly, I have not tested the Power Apps Mobile application for its offline capabilities thoroughly. So, this topic would benefit from more thorough analysis and rigorous testing to produce some guidelines. However, we can make some inferences based on our observations of the Power Apps Mobile app behavior to come up with a quick response for your question.

      Power Apps Mobile does have a local cache. If you open up the settings menu, you will see an option of “Clear cache”. Clicking this option will provide a message warning that all Power Apps data (including offline data) will be wiped. This indicates to me that the data is cached in likely a mix of app memory (offline data) and device storage (metadata, authentication, app copy, recently used apps list, favorite apps list).

      Despite the local cache, I think there are a number of areas where you could go wrong when trying to start an app in offline mode. There’s an authentication loop when you first open Power Apps Mobile that needs to contact the Azure AD server to verify if your credentials are still valid. There’s going to be a ping to the server to ask for any new canvas apps which have been created/shared with you since the last session. Once you open an app, then there’s the app version check to compare the local version with the cloud version, and prompt you to update to get the latest version.

      All of these activities will require connectivity to execute, and so it makes sense to me that your users are sometimes encountering error messages when they are not able to execute successfully. It’s important to remember that Power Apps Mobile for canvas apps itself does not have an offline mode, so even if you’ve put in the effort to make your app offline capable, the Mobile framework isn’t considering the device connectivity status during start up. For example, if you check out the documentation for frequently encountered issues with Power Apps Mobile, the “App list is empty” one that you mentioned has the following advice:

      “This can happen in any of the following scenarios:
      […]
      –When you come back online after a period of being offline. The app list is automatically refreshed.
      To resolve connection related issues, ensure you remain connected to the internet while the app list is fully downloaded.”

      I’m not sure that I would characterize these issues as bugs, but rather Microsoft has not created robust offline support for canvas apps to date. As we see Microsoft’s offline story unfold, I anticipate we will see a migration from app memory to device storage for offline data as well as an offline mode for Power Apps Mobile to handle the issues your users are seeing. Until then, my best recommendation is to encourage users to sign into the mobile app, download their app list, open their target application and update to the latest version (if applicable) before going into a low or no connectivity situation.

      I hope this helps!
      /joy

      Like

  7. Hi Joy,

    Your article is superb and has helped a lot.

    I do have one slight issue, when configuring the timer control to handle the synchronization i noticed that when navigating to another screen the timer stops.

    Is this something you encountered and if so were you able to find a workaround?

    Thanks
    Jason

    Liked by 1 person

    1. Hi Jason,
      Thanks for the feedback! You are correct, the Timer control will only operate on the currently active screen. It will not continue to run in the background while the user is completing operations on a different screen. This is another example of the complications of trying to implement offline scenarios in the current state of canvas apps– we are linking controls together to approximate the data synchronization with our custom Power Fx code, but what we truly need is a robust set of features to manage this for us intentionally!

      In my scenario, we had a home screen designed where the majority of the user’s work would take place in a very narrow use case. We used modal overlays instead of navigating away to a new screen, so we could rely on the fact that users would spend 90% of their time on the home screen where the Timer control lived.

      If your use case is broader, you’ll have to come up with a modified design on this architecture which manages a Timer control on each screen where the user will end up spending the majority of their time in the app.

      Hope that helps,
      /joy

      Like

  8. Hi Joy, and thank you for such a great, eye-opening article.
    I am collecting my data from a SharePoint list through a flow. The SharePoint list has a date field, which is turned into a string because of the flow’s Response action. I am then working with the collection in the app with no problem, but when it is time to patch the collection back to SharePoint (once online) – I get an error in design time, because the collection has a text field instead of a date field (which SharePoint expects.
    Any idea how can this be solved?

    Liked by 1 person

    1. Hi Gil,
      Thanks for the feedback and I am glad you found the guide helpful.

      Sounds like you need to cast your text value as a Date or DateTime data type as you are patching back to your SP List. Have you worked with these formula commands before? https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-datevalue-timevalue

      For example, if it’s a date only field like “11/14/2022” you can write your statement like Patch(SPList, Record, {DateField: DateValue(DateTextString)}). That should convert the date appropriately when sending the data back to SharePoint. (There’s also formula reference for more complex date & time fields at that same link depending on your needs).

      /joy

      Like

      1. Hi Joy, thanks for taking the time to help me!
        I worked a lot with the casting formulas to change from text to date etc. but in this case, it was more complicated since I could not reference the original column in the collection in the same line where I’m patching it to SharePoint. I will not waste more of your time on explaining that, because I have used another one of your recommendations as an alternative: Instead of patching a filtered version (updates only) of the original collection that was collected from the flow’s response, and ending up with the problem described before, I am using a separate dirty collection, which as you suggested I’m collecting with one record of the entity, and then clearing it, so the collection has the correct structure, and then I’m just patching the collection. This method works great, with only one fix to your code snippet: Initially collecting the dirty collection from the datasource should be done only when Connection.connected, and of should be loaded from memory if it is not empty (meaning previous updated are still pending).

        Lastly, I think it is worth updating that one of the preview features of Power Apps enables SaveData and LoadData on the web player:
        https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-savedata-loaddata (it is even not experimental anymore).

        Thanks again,
        Gil.

        Liked by 1 person

Leave a comment

Blog at WordPress.com.

Up ↑