Menu

Making your Website App Aware and Performant

By the end of this section, you’ll be able to:

And you'll be able to:

Why?

Because Astro leverages your existing web content you need to make your website "app-aware". That means, your website needs to detect when it’s running inside Astro. If your website knows that it’s being viewed from inside an Astro app, it can change its look and feel by changing its layout, design or functionality using CSS or JavaScript.

How a website can change it’s look and feel when viewed in an app.

As you’ll see in the next section, there’s more than one way to detect whether your website is running in an app context.

Content Security Policy

Before we go any further it is important to note that a Content Security Policy header can break "app awareness" in a website. See the Supported Platforms article to make sure your site is compatible with Astro.

Now back to making your site "app aware"! 💪

Detecting App Context

In order to detect whether your website is running in an app (so that you can modify its design or behaviour), the first thing you should do is include the astro-client.js file into your website. Ensure that the script gets added to every page so that Astro is readily available. The script should appear in the <head> tag of your website.

<script src="http://assets.mobify.com/astro/astro-client-3.0.0.min.js"></script>

The version number should be updated to the latest version of Astro.

To test that you’ve added the library, run the following JavaScript in Chrome’s console:

Astro.isRunningInApp()

This function should run without issues and return false (alternatively, it should return true when running inside an app).

Checking to see if *Astro* is running on a website.

App-Only JavaScript

Websites that are used by an Astro app will often have code that is only executed when the site is loaded inside the app. For this reason, you want to guard that code and not have it execute when it’s not running inside an Astro app. Running extra code that doesn't do anything hurts performance for non-app web visitors.

As a result you should always wrap any app-specific JavaScript within an isRunningInApp() check.

For example:

if (Astro.isRunningInApp()) {
    // Some app-specific javascript
}

Platform-Specific JavaScript

There may be cases where you only want to run specific JavaScript code if your app is running in an iOS or Android app.

These platform checks are still only for the site running within an Astro app on that platform. This means that you can't use these checks to execute some code for iOS when running outside of an Astro app.

For example:

if (Astro.isRunningInIOSApp()) {
    // Some code for the iOS version of the app
} else if (Astro.isRunningInAndroidApp()) {
    // Some code for the Android version of the app
}

Organizing these blocks of code that are Astro or platform specific is important for readability. A good practice is to make calls out to Astro or platform-specific methods instead of writing the code "inline". The example below shows how you can have iOS and Android specific pieces that don't overly disrupt the readability of the overall function.

function checkout() {
    if (cartIsValid()) {
        // Proceed with checkout
        if (Astro.isRunningInIOSApp()) {
            checkoutForIOSApp()
        } else if (Astro.isRunningInAndroidApp()) {
            checkoutForAndroidApp()
        } else {
            // Proceed with "web" checkout
        }
    }
}

function checkoutForIOSApp() {
    const applePay = await ApplePayPlugin.init()
    applePay.checkout(getCartContents())
}

function checkoutForAndroidApp() {
    const androidPay = await AndroidPayPlugin.init()
    androidPay.checkout(getCartContents())
}

You should avoid platform-specific code (iOS or Android) as much as possible. What's good for one platform is almost always good for the other platform. Reducing the number of "branches" (or subroutines) in your website’s JavaScript also minimizes maintenance and regression testing when anything changes.

Finally, there may be cases where you have code that you don't want running when within an app. You can negate the above checks to guard that code as well for the same reasons you guard app-specific code blocks.

App-Only CSS

When the Astro client loads, it automatically adds two CSS classes to the root <html> tag of the document. The first is a general astro-app class to signal that the page is loaded in an app. The second is a platform-specific class in case you need CSS that is specific to iOS (astro-app-ios) or Android (astro-app-android).

Using these two classes, you can conditionally style your CSS by selecting on one, or both, of those class names. For example, you almost always want to hide the HTML header bar of your website when it’s running inside an app. That might look like:

html.astro-app .headerBar {
    display: none;
}

html.astro-app-ios button {
    background-color: #00ff00;
}

html.astro-app-android button {
}

CSS can affect the rendering performance of a page. It is important to be aware of how styling rules for app-specific features affect the overall rendering performance of a page. This helps the web rendering engine to ignore app-specific rules by only checking the <html> element (i.e. it doesn't have to traverse the DOM at all if the <html> element is missing the astro-app class).

Always prefix your app-specific CSS rules with .astro-app so that the renderer can ignore app-specific rules if not in an app and to ensure that app- specific rules aren't applied when not running within an app.

Detecting App Version (and Server-Side Detection)

Whenever an Astro app requests a page, Astro modifies the User Agent and adds Astro App/1.0 to the request (1.0 is replaced with version number of the native app hosting Astro). The application version can be retrieved from any Astro JavaScript code by accessing the property Astro.applicationVersion.

This can be useful in a number of ways, both on the client and server side. If your backend follows RESS (Responsive Elements and Server Side Scripting), you can detect the user agent and serve an entirely different template for the app. How you detect the user agent and serve a different template will vary depending on your own backend architecture. On the client side, you can detect the version number and either hide unsupported features or present the user (of the app) with a prompt to upgrade their app if they are running an older version of the app.

Web Performance Best Practices

Astro is a Software Development Kit (SDK) for building great iOS and Android apps... so why are we talking about web performance? Glad you asked! Web performance is a critical component to making a great Astro app. Astro enables you to make pragmatic decisions about which pieces to build native or web. Often, you already have great web content that needs some minor changes to be ready to be used in your app. Because Astro apps mix native and web, the performance of the web content you integrate can make or break the experience for users of your app.

Nobody likes to wait and this is especially true for users of mobile apps. If your app is slow to respond or takes a long time for content to appear, you will lose the attention of your users. They may not uninstall your app, but will most likely never come back to it, leaving your app icon scuttled in some forgotten folder on the device.

When users take some action in an app, they expect the app to react and provide feedback quickly. Of course, there are limits to how fast you can load the remote web content in the app, but you can take steps to let the user know that something is happening and be responsive.

The iOS and Android tools provide great support for analyzing and diagnosing web performance issues in your Astro app. Both platforms provide a similar approach in allowing you to attach a web browser's developer tools to any web view running in your Astro app. Once the dev tools are connected to the web view you can use any of the web development tools your browser provides to figure out where the web content isn't performant.

You can find out how to connect to web content hosted in an Astro app in the lesson Debugging and Getting Unstuck.

Some common tasks to complete when you diagnose web content issues in an Astro app are:

  1. Review the network activity to pinpoint resources that load slowly or are not being cached appropriately.
  2. Execute timeline traces to discover CSS reflows, render blocking JavaScript, etc.
  3. Profile JavaScript code to help you find poorly performing pieces of code.

We've included some great articles from Google to help you make web content provide the best app experience for your user base. Some of these may be Chrome specific, but keep in mind that analyzing the web content can be done in Chrome even if you’re building for iOS:

Adaptive/Astro Best Practices

Astro is designed to work well with websites that use Mobify's Adaptive.js framework. Adaptive.js is an earlier product that Mobify sold that turned desktop sites into mobile sites. Mobify no longer sells Adaptive.js since the product has been replaced by Mobify’s Progressive Mobile Web product, but it is still widely used by many customers. This sub-section is to help those developers that encounter an Adaptive website being used inside of an Astro app.

Making the astro-client.js available in the Adaptive.js Project

The first thing you need to do is make the astro-client.js file available to the Adaptive project. The simplest way to do this is to just download the Astro client using the following link (and modifying it to match the version of Astro you're using in your app project):

http://assets.mobify.com/astro/astro-client-3.0.0.min.js

3.0.0 the version part of the URL that changes.

Place the astro-client.js file in your Adaptive project in the app/vendor/ directory.

Open app/config/adaptation.js and add the Astro client to it by adding an entry to the paths key as follows (bolded code):

require.config({
    ...,
    'paths': {
        .,
**        'astro-client': 'vendor/astro-client'**

You have now made the Astro client available to the Adaptive code. The next step is to use it in the project.

Include the Astro Client in Your Base View

Adaptive provides an easy way to include code in every page through the baseView.js file.

Open app/global/baseView.js and add astro-client to the define at the top of the file (leave the existing code in place, just add the astro-client to the end of the define list along with Astro at the end of the function(...) argument).

import Astro from './astro-client'

Detecting App Context

Now that you have access to the Astro client in baseView.js you can make the Adaptive project aware of the fact that it’s running inside an app. As you learned above, there are two things that you'll want to do:

  1. Add a CSS class to every page to classify a page as running in an "astro-app".
  2. Scope your CSS design changes to the "astro-app" class.

Add the following code to the postProcess function in baseView.js:

if (Astro.isRunningInApp()) {
    const el = document.documentElement
    const className = 'astro-app'
    el.classList.add(className)
}

Now you can scope any CSS changes you need for the app easily by prefixing any "app only" rules with html.astro-app. For example:

html.astro-app .headerBar {
    display: none;
}

Add Astro Keys to the Adaptive Context

Although it is possible to include the Astro client in any page you need it, it's good practice to simply do your app detection in baseView.js and then push the results in the evaluated context. That way you can just check the evaluated context in any page or UI script that you need.

Assign the following keys to the context object at the bottom of the baseView.js file:

.,
context: {
    ...,
    runningInApp:        Astro.isRunningInApp(),
    runningInAndroidApp: Astro.isRunningInAndroidApp(),
    runningInIOSApp:     Astro.isRunningInIOSApp(),
    ...
}

You can now easily check if you are running in an app (or specifically iOS or Android) from any view or UI script by checking the new keys on the context. For example, in a dust template you could do this to use different dust templates depending on if you are in an app or not:

{?runningInApp}
    {>"templates/components/benefits-app"/}
{:else}
    {>"templates/components/benefits-web"/}
{/runningInApp}

Or you can do a similar JavaScript check in your ui.js Adaptive code for a specific page:

    ...
    const _updateWelcomeBackText = () => {
        if (Adaptive.evaluatedContext.runningInApp) {
            // Set up a custom banner for app users
            $('t-home welcome-banner').text('Welcome back to app user')
        }
    }

    const homeUI = () => {
        _updateWelcomeBackText()
    }

    return homeUI
})

Signaling Astro that the Adaptive Rendering is Complete

Astro apps come with a built-in loading indicator (in the form of a spinning loader icon) for all web views. The WebViewPlugin automatically detects when it starts navigating and displays the loader. When the navigation completes (specifically, when DOMContentLoaded has fired) the loader is dismissed. This is not always acceptable because DOMContentLoaded is triggered in Adaptive apps only once "capturing" completes, but in that case the screen is still just a blank page. This results in what we call a “white flash” since the screen is white (blank) while the Adaptive code is executed and results in a poor user experience since a user does not like looking at an empty white screen.

Astro provides a way to control when the loader is dismissed so that you can have the Adaptive rendering complete and then dismiss the loader. This is done in two steps:

Step 1. Set Which Page URLs You Want to Show Manually

The first step is to tell each web view that you will tell it when to dismiss the loader. This is done using the manuallyShowPageForHosts([host, …]) method. You can pass a list of hostnames that you will control the loader for.

webView.manuallyShowPageForHosts(['www.example.com'])

Step 2. Manually Show the Page

Now you can add some code right at the end of the app/global/ui.js file that will notify Astro that the page has loaded.

    ...
    const globalUI = () => {
        Astro.trigger('adaptive:page-loaded')
    };
    return globalUI
}) // End of app/global/ui.js

Finally, in the app.js you can handle the event on your webview and show the page.

webView.on('adaptive:page-loaded', () => {
    webView.showPage()
})

Summary

In this section you learned how to make your site "app aware". You started out by linking in the Astro client js file and detecting the app context. From there you learned how to begin customizing your site UI when your site is surfaced inside an Astro app by adding a CSS class and using the isRunningInApp() helper method. Finally, you saw how to apply this learning when working within an Adaptive.js project.