Blog

Apps - Migrating OnStart formula to use App.StartScreen/ Fixing existing apps that implement deep linking

A recent update to Power Apps now prohibits the use of Navigate in App.OnStart. Existing apps that call Navigate in App.OnStart will show the error 'Navigate is not permitted in OnStart. Use the StartScreen property instead.' How do we fix this problem? The post describes some of the possible solutions.

Last week, Microsoft applied updates to improve the performance of apps. This update discourages the
use of App.OnStart and deprecates the use of the Navigate function in App.OnStart.

The reason for this change is to prevent complex, long-running formulas in App.OnStart which can significantly slow the start up time of an app. Long running code in App.OnStart blocks the display of the first screen in the app which makes an app appear noticeably slower to a user.

Whilst the intention of this change is very beneficial, it causes difficulty for app builders with complex code on App.OnStart, typically, apps that implement some sort of deep linking functionality. This post describes the typical challenges and describes the possible migration techniques.

Why does deprecating the use of Navigate in App.OnStart cause a problem? 

To demonstrate a common scenario that relates to "deep linking", let's take the example of an app that stores issues/support tickets. When the status of a record changes, the process sends an email with a link that directly opens the specified record in Power Apps. The web link contains the unique identifier (IssueID) as a parameter value, like so:
 

https://apps.Power Apps.com/play/c4c89866-5b53-4668-9a67-bd9e06ee56a0?IssueID=12

To adapt an auto-generated app to support this deep linking functionality, a fairly typical approach is to add the following formula to the OnStart property of the app. 

If(Not(IsBlank(Param("IssueID"))),
          Navigate(DetailScreen1,ScreenTransition.Fade),
          Set(RecordSeen,true)
)
When a user starts the app by clicking a hyperlink that includes an IssueID parameter, this formula navigates the user to DetailScreen1 - a screen with a form control that displays the record that matches the IssueID.

The Item property of this display form would be set to the following formula.

If(RecordSeen=true, 
   BrowseGallery1.Selected, 
   LookUp(Issue, IssueID=Param("IssueID"))
)

To configure the form to subsequently display the selected record in the gallery afterwards, we set the OnHidden property on the screen to the following formula.

Set(RecordSeen,false)


This overall technique will no longer work because it's not possible to call Navigate from App.OnStart. We now receive the error - 'Navigate is not permitted in OnStart. Use the StartScreen property instead.'


How exactly do we use the StartScreen property instead? Let's take a look at the possible migration methods.

Method 1 - Set the StartScreen property of the app

The preferred method is to use the StartScreen property to specify the screen that we want to navigate to. Traditionally, the initial screen that appears when an app loads is the screen that appears at the top of the Screens section of the Tree view. The StartScreen property now controls the initial screen that appears.

Taking our example, we would set the StartScreen property to the following formula:

If(IsBlank(Param("IssueID")),
BrowseScreen1, DetailScreen1
)


This configures the app to display DetailScreen1 if the web address includes an IssueID value. If not, the app displays BrowseScreen1.

The rest of the app remains the same, and the app continues to function as it did previously. This example highlights an example of a simple migration.

Method 2 - Set the StartScreen and disable the 'Use non-blocking OnStart rule'

With Method 1, it might also be necessary to switch off the 'Use non-blocking OnStart rule' setting.

As an example, here's a variation of a deep linking technique that relies on the following App.OnStart formula:
If(Not(IsBlank(Param("IssueID"))),
    Set(varSelectedRecord, 
LookUp(Issue, IssueID=Param("IssueID"))
)
;

Navigate(DetailScreen1)
)


To highlight the necessity of why it's necessary to disable the 'Use non-blocking OnStart rule', let's assume there's a text input control with the text property set to varSelectedRecord.IssueDescription on DetailScreen1. This mimics the typical example of a screen that displays data without the use of a form control.

To modify the app to use the StartScreen method, we remove the call to Navigate in App.OnStart. The updated formula in App.OnStart now looks like this:

If(Not(IsBlank(Param("IssueID"))),
    Set(varSelectedRecord, 
LookUp(Issue, IssueID=Param("IssueID"))
)
;

)

Next, we set the start screen property to the following formula:
If(IsBlank(Param("IssueID")),
BrowseScreen1, DetailScreen1
)

Whilst this fixes the 'Navigate is not permitted in OnStart' error, there is a problem with this technique. That is - when the app starts, Power Apps executes the App.OnStart formula and displays the start screen simultaneously/asynchronously.

Therefore, it's possible for the app to display the start screen and to display the value varSelectedRecord.IssueDescription in the text input control before it successfully loads the target record in App.OnStart. In this case, the text input control will be empty, which is incorrect behaviour.

One way to fix this problem is to disable the 'Use non-blocking OnStart rule' setting. With this setting disabled, Power Apps will complete the call to LookUp in App.OnStart before it displays the start screen. This resolves this timing problem but unfortunately, it also negates the performance benefit that this change intends to introduce (ie - to speed up the display of the initial screen).


Method 3 - Use a timer control to run the startup formula

The third method is to create a custom start screen, add a timer control, and to add the existing OnStart formula to the timer.

To give a use case scenario, here's another variation of deep linking that relies on a screen/context variable called locSelectedRecord. DetailScreen1 would contain a form with the Item property set to this variable. The App.OnStart formula for this example looks like this:

If(Not(IsBlank(Param("IssueID"))),     
     Navigate(DetailScreen1,
ScreenTransition.Fade,
{locSelectedRecord:

LookUp(Issue, IssueID=Param("IssueID"))
}
)


Because the App.StartScreen property provides no way to pass context variables, the timer method offers a viable workaround. A second alternative would be to rewrite the app to use global variables rather than context variables, however, this could involve a significant rewrite of an app depending on the number of places where we set/use the context variable.

To implement the timer method, we would add a new screen to the app (let's call this StartScreen). We set the App.StartScreen property to StartScreen.

From the StartScreen, we add a timer control. We set the Duration property to 1, the AutoStart property to true, and we can also hide the timer by setting the Visible property to false. We can also customise the look of this screen so that it looks like an interstitial, or "splash screen" - that is, a screen that contains some visual indication that the app is loading.

We then cut and paste the existing App.OnStart formula into the OnTimerEnd property.


At startup, the app immediately shows StartScreen and milliseconds later when the timer triggers, the app will
execute the startup formula.

The advantage of this method is that it offers a relatively simple solution that involves minimal refactoring/rewriting of an app. This can be appropriate for apps with very complex OnStart logic because splitting the logic between App.OnStart and  App.StartScreen may not be trivial and can be exacerbated by existing formula in the OnVisible property of the target start screen.  Orchestrating these 3 threads to minimise dependencies and to ensure all the formulas run in the expected sequence can be difficult.

One disadvantage of this technique is that it adds additional time to the startup of an app, which can be noticeable.

Note that on the topic of context variable support, I've added an idea here to add the ability to pass context variables to App.StartScreen.

Method 4 - Re-enable Navigate in App.OnStart through the settings

The final technique is to re-enable Navigate in App.OnStart through the settings.


This is by far the easiest solution to the immediate problem. However, this switch will be available for a limited time only, and at some point in the future, we'll need to modify our app to properly remove the call the Navigate in App.OnStart.

Conclusion

An update to Power Apps now makes it impossible to call the Navigate function from App.OnStart. This has a big impact on apps that implement deep linking functionality, and this post describes four techniques to resolve this issue.