Before you continue reading the article, I would like to point out that it already assumes certain knowledge of Power Apps concepts.

There are currently several options in Canvas Apps to trigger certain logic when the application starts. We have App.OnStart, App.StartScreen and Screen.OnVisible. Beyond that, there are a few settings associated with these formulas. How to make sense of all this? 🤯 And how should the data be loaded when the app starts? Let’s take a look.

Imagine a simple request approval application. We have two user roles - approver and requester. Both roles need common data to be loaded when they run the application – a list of request types, application styling and navigation menu items. In addition, each role needs to be redirected to a different initial screen - the approver wants to see a list-view of their approvals and the requester wants to see a list-view of their requests. Related to this is the need for each to load different data when the application starts - the approver will load the requests that are pending his approval and the requester will load the requests he has created.

First, let’s look at the simplest part. Where to load screen-related data? The first option is App.OnStart. In order to load the data only if the user has a certain role, a condition similar to this would have to be added.

// App.OnStart =
Switch(
    varCurrentUser.role,
    "approver",
    Set(varApprovals, Filter(Approvals, Approver = varCurrentUser.Email),
    "requester",
    Set(varRequests, Filter(Approvals, Requester = varCurrentUser.Email)
);

It will work, of course, but once there are more roles in the application and more requests to load screen-related data, App.OnStart will be very messy. Besides, data that is only related to one screen is stored in a global variable, that’s not right either. Let’s try it another way. Let the data be loaded only in the Screen.OnVisible formula for a specific screen. And instead of a global variable, let’s use a context variable so that the data is only available in the required scope.

// scr_Approvals.OnVisible = 
UpdateContext({
    ctxApprovals: Filter(Approvals, Approver = varCurrentUser.Email)
});

// scr_Requests.OnVisible = 
UpdateContext({
    ctxApprovals: Filter(Approvals, Requester = varCurrentUser.Email)
});

Next, we will look at where to decide what screen the user should be redirected to. Previously, the Navigate function in App.OnStart was used. This is now impossible due to the ‘Enable Navigate function in App.OnStart’ setting, which is disabled as recommended. Instead, the App.StartScreen property is available in which the following evaluation can be performed.

// App.StartScreen =
Switch(
    LookUp(Users, Email = User().Email).Role,
    "approver",
    scr_Approvals,
    "requester",
    scr_Requests
)

But here comes another problem, what if we want to store the user role in a variable for later use. This is not possible, because App.StartScreen is not a behavioral formula, so we cannot use the Set function. Neither can we set a variable in App.OnStart and use it in App.StartScreen, because App.StartScreen doesn’t support global variables. Therefore, it is necessary to perform LookUp twice, once to evaluate App.StartScreen and once to store it in a variable. That doesn’t sound too good, does it? 🤨

Before I present the solution for the variables and App.StartScreen, let’s take a look at how to retrieve data common to the entire application. Of course, this data should be stored in a global variable and we can use the App.OnStart. Which would look like this.

// App.OnStart =
Set(
    varApprovalType,
    Table(...)
);
Set(
    varNavigationMenu,
    Table(...)
);

It looks good, but if you need to load large amounts of data, there will be a problem. It will take users quite a long time to start the application (see ‘Time to first screen’ in the application analytics report). However, this can be solved by enabling the ‘Use non-blocking OnStart rule’ setting. What this does is that it will run App.OnStart and Screen.OnVisible formulas in parallel when the app starts. If the setting is turned off, it always waits for App.OnStart to finish before it starts evaluating Screen.OnVisible - this is when the ‘Time to first screen’ is increased.

It almost looks like the ‘Use non-blocking OnStart rule’ will save us, but we might run into another problem. Let’s go back to our approval app and extend it with user groups. We want the approver to be able to see requests from other approvers in their group (e.g. HR). The user group should be stored in a global variable, so it can be used in other places in the application. Let’s do the following.

// App.OnStart =
Set(
    varCurrentUser,
    LookUp(Users, Email = User().Email)
);

// scr_Approvals.OnVisible = 
UpdateContext({
    ctxApprovals: Filter(Approvals, Approver.Group = varCurrentUser.Group)
});

Do you see the problem? 🤔 scr_Approvals.OnVisible depends on the varCurrentUser variable that is set in App.OnStart. But! scr_Approvals.OnVisible and App.OnStart are evaluated in parallel, so we may have a situation where scr_Approvals.OnVisible starts evaluating and varCurrentUser doesn’t contain data yet.

To give you an idea, I am attaching my little experiment 🧪

[8:02:38] App.OnStart:      Entering
[8:02:38] App.OnStart:      Fetching data
[8:02:38] Screen.OnVisible: Entering        // Screen.OnVisible is being evaluated before App.OnStart has finished
[8:02:38] Screen.OnVisible: Fetching data
[8:02:40] App.OnStart:      Data fetched    // continue to evaluate App.OnStart
[8:02:40] App.OnStart:      Leaving
[8:02:40] Screen.OnVisible: Data fetched
[8:02:40] Screen.OnVisible: Leaving

Finally, we come to the StartUp screen pattern that aims to solve most of the mentioned problems. This pattern is based on omitting the App.OnStart formula, thus reducing the ‘Time to first screen’ to a minimum. Instead, a new screen scr_StartUp will be created, which will be set as App.StartScreen. All common variables will be set in the scr_StartUp.OnVisible formula. The advantage is that at this point the app is already loaded and even though the data is still loading, you can already show some part of the interface to the user. Once everything in scr_StartUp.OnVisible has been processed user must be redirected to the actual application screen. Unfortunately, you cannot use the Navigate function in Screen.OnVisible either. So you have to do something else. Fortunately, members of the PowerApps community 💜 have already figured this out - just use Timer named tim_StartUp, which has following configuration.

Also I would like to remind you that you can disable App.OnStart property in the settings 💡

// scr_StartUp.OnVisible (last line!)
UpdateContext({
    ctxStartUpTimer: true,
    ctxStartScreen: scr_Approvals
});

// tim_StartUp.Start & tim_StartUp.AutoStart
ctxStartUpTimer

// tim_StartUp.Repeat & tim_StartUp.Reset & tim_StartUp.Reset
false

// tim_StartUp.OnTimerStart
UpdateContext({
    ctxStartUpTimer: false
});
Navigate(ctxStartScreen);

To make this pattern clearer I am attaching a diagram.

StartUp screen pattern diagram

To make it easier for the user to work with the application, don’t forget to add indicators on scr_StartUp that the data is still being loaded. Such as progress overlay.

Let’s quickly return to the problem with App.StartScreen. This can be solved very easily using the StartUp screen pattern. Just modify the above code to the following. This allows us to use global variables when selecting the intial screen.

// scr_StartUp.OnVisible (last line!)
UpdateContext({
    ctxStartUpTimer: true,
    ctxStartScreen: Switch(
        varCurrentUser.Role,
        "approver",
        scr_Approvals,
        "requester",
        scr_Requests
    )
});

This design pattern therefore helps to solve the problem of loading data when the application starts. Let’s summarize its advantages:

  • Application starts very quickly (minimizing the ‘Time to first screen’ )
  • Better code layout - we no longer have App.OnStart, App.StartScreen and scr_Initial.OnVisible, but only scr_StartUp.OnVisible and scr_Initial.OnVisible
  • Cleaner code - setting the global variables in scr_StartUp.OnVisible and everything else only on the specific screens

Finally, I would like to recommend that while creating a new application, start by implementing the StartUp screen pattern. This will make the work that would be required for later modifications easier. The exceptions, of course, are applications with only one screen.

A template using the StartUp screen pattern is available on this link.