Node v6
Starting with v0.19 Astro must be used with Node v6+!
Menu

Build Your First Simple App

What you’ll learn and how you can apply it

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

And you'll be able to:

Why?

In this section you will build a simple mobile shopping app for iOS called Velo. By the end you will have added a few features and have a better understanding of how Astro works. This tutorial is a great way to get your hands dirty and get exposed to the core concepts of Astro without getting going too deep on any of the subjects. We will dig into each topic in more detail in later sections.

If you get stuck or just want to follow along, you can find the full solution at the end of this section.

Figure 1. Example of the app we’re building.

Install and Run the Sample Site

Before you can run the Astro app, you need to get the tutorial site running that the app uses. Once this step is done you'll be able to run the tutorial app and see the web content inside of it.

Run the following commands:

$ git clone --branch 1.0.0 https://github.com/mobify/astro-tutorial.git
$ cd astro-tutorial
$ npm run deps
$ cd site
$ npm install

Great, you should now have everything ready to get started. We wrote a sample website for you to experiment with. To run the server, run:

$ node index.js

Go to the demo site at http://localhost:5000 to check it out. Please leave Node running - you'll need the site running in the steps ahead.

Open and Run the App

You are now ready to run the app. In this section we'll open up the project in Xcode and run the app in the simulator for the first time!

First, open up the iOS project in Xcode and leave that in the background.

$ cd ..
$ open ios/velo.xcworkspace

Next, open app/app.js in your favourite text editor (Xcode isn't great at formatting JavaScript). We're going to point the app to the sample site you have running. Change the baseUrl to localhost.

// Enter your site url here
- const baseUrl = 'http://<Your IP Address>:5000/'; // change this
+ const baseUrl = 'http://localhost:5000/';         // to this

Now build and run the app in Xcode by clicking on the ► icon in the toolbar and check it out (running it in the simulator is fine). You should see your website running in the app. You can scale the simulator to fit in your screen by selecting Window > Scale > 75% (or smaller until it fits).

Pro Tip We usually use the iPhone 5s simulator when testing unless we need to test specifically on larger resolutions. This is because the iPhone 6 and 6 Plus simulators will run slower due to the number of pixels being rendered (even if you scale down the window). You can change which device the simulator will run with by selecting Hardware > Device > iOS 9.3 > iPhone 5s (or using the dropdown in the toolbar as shown in Figure 2)

Figure 2. Device selection dropdown

NOTE: Your app may be slow to build for the first time. This is normal because it has to compile the entire app. Afterwards, it will only compile the parts that have changed.

Figure 3. Your website running in an app.

Pro Tip! Keyboard shortcuts reduce the amount of time it takes to perform common operations. Use Command-R (⌘R) to run the app in Xcode and Command-1, Command-2, … to scale the Simulator window smaller and larger.

Add the Header Bar

Now that we’ve got the website running inside the app, the next thing we want to do is add a native header bar. This is often one of the first native elements added to an app because it makes the transitions feel native, it adds context to the content below (by showing a title), and it gives us a place to build our top-level navigation.

To add a native header bar to your app, in app.js, add the HeaderBarPlugin.

import HeaderBarPlugin from 'astro/plugins/headerBarPlugin';

Now you're ready to use the HeaderBarPlugin. Your next step is to initialize the plugin. Initializing a plugin returns a promise.

// Initialize plugins
...
const headerPromise = HeaderBarPlugin.init();

Put the following code snippet under the promise block that calls layout.setContentView. This will create a basic (but blank) header bar.

// Add the header bar once `layoutPromise` and `headerPromise` are fulfilled.
Promise.join(layoutPromise, headerPromise, function(layout, headerBar) {
    // Set up the header bar
    headerBar.setBackgroundColor('#FFFFFF');
    headerBar.setTextColor('#333333');
    layout.addTopView(headerBar);
});

What we've done is add a basic header bar to the layout of our app. Because the header bar and main navigation view depend on each other for various things (such as updating the title, or synchronizing header bar animations with navigation animations), we need to do a little more work to hook the two together. Below, you'll see that we're able to assign icons to the header bar when we navigate in our main view. We also ensure that when the back button is pressed we navigate the main view backwards.

// Remove this code
mainNavigationPromise.then(function(mainNavigationView) {
    mainNavigationView.navigateToUrl(baseUrl)
});

// Add this code in
Promise.join(
    mainNavigationPromise,
    headerPromise,
    function(mainNavigationView, headerBar) {
        // Now, let's hook up the main navigation to the header bar
        mainNavigationView.setHeaderBar(headerBar);
        // Navigate to the base url and set the icons
        mainNavigationView.navigateToUrl(baseUrl, {
            header: {
                leftIcon: {
                    id: 'account',
                    imageUrl: 'file:///account.png'
                },
                centerIcon: {
                    id: 'logo',
                    imageUrl: 'file:///velo.png'
                },
                rightIcon: {
                    id: 'cart',
                    imageUrl: 'file:///cart.png'
                }
            }
        });

        // When the back button is pressed navigate back
        headerBar.on('click:back', function() {
            mainNavigationView.back();
        });

        // When the (Android hardware) back button is pressed navigate backwards
        Application.on('backButtonPressed', function() {
            mainNavigationView.back();
        });
    }
);

Now re-run the app in Xcode to check out your new header bar. You should be able to navigate to an item and then tap the back button to go back to the beginning.

Figure 4. Your app with a header bar and a back button

Add a Flyout Drawer

Drawers are flyout views in our app (either on the left or right-hand side). Drawers are a great way to show persistent content such as a shopping cart. They are also a UX convention you can use in both iOS and Android. Using flyout drawers often makes code more maintainable. This is because there is only one main view at all times (compared to multiple open views when using the tab bars UX pattern). Again, in app.js, let's include the DrawerPlugin.

import DrawerPlugin from 'astro/plugins/drawerPlugin';

Now that you have the DrawerPlugin, let's initialize a drawer object we can use to change the app’s layout structure.

// Initialize plugins
...
const headerPromise = HeaderBarPlugin.init();
const drawerPromise = DrawerPlugin.init();

Under the promise statement that adds the header bar to the layout, set the main content area of our DrawerPlugin to the AnchoredLayoutPlugin instance with the following code snippet:

// Set the drawer's content area once `drawerPromise` and `layoutPromise` are
// fulfilled.
Promise.join(drawerPromise, layoutPromise, function(drawer, layout) {
    drawer.setContentView(layout);
});

At the bottom of the file, delete the promise which sets the layout as the application's main view. In its place, tell the Application to use the DrawerPlugin instance as its main view with the following code snippet:

// Remove this code
layoutPromise.then(function(layout) {
    Application.setMainViewPlugin(layout);
});

// Add this code in
drawerPromise.then(function(drawer) {
    Application.setMainViewPlugin(drawer);
});

Now you have a DrawerPlugin** instance, but it does not yet have any drawers. The DrawerPlugin plugin exposes three content areas:

  1. Left drawer
  2. Right drawer
  3. Main content

Let's create a WebViewPlugin that we'll add to one of the drawer's content areas. First, import WebViewPlugin at the top of app.js:

import WebViewPlugin from 'astro/plugins/webViewPlugin';

Initialize the WebViewPlugin below where we initialized the DrawerPlugin:

// Initialize plugins
...
const headerPromise = HeaderBarPlugin.init();
const drawerPromise = DrawerPlugin.init();
const cartWebViewPromise = WebViewPlugin.init();

At the bottom of the file, underneath the last promise to set the DrawerPlugin instance to the main view, add code to navigate the webview to the cart and assign the view to the right drawer:

// Navigate the cart webview to the right url
cartWebViewPromise.then(function(webView) {
    webView.navigate(`${baseUrl}/cart`);
    webView.setBackgroundColor('#FFFFFF');
    // Don't navigate when links are pressed
    webView.disableDefaultNavigationHandler();
});

// Set the right drawer view to the cart web view instance once the promises
// have been fulfilled.
Promise.join(
    cartWebViewPromise,
    drawerPromise,
    function(cartWebView, drawer) {
        const rightDrawer = drawer.initRightMenu(cartWebView);
        // We want the right drawer later, so we will return it
        return rightDrawer;
    }
);

Now if you run the app in Xcode you'll have a right drawer! To check out your right drawer, swipe from the right edge to expose the right drawer. Not ideal, but don't worry, next we're going to show the right drawer by tapping on the 'cart' icon.

Figure 5. The app’s flyout drawer.

Show and Hide the Drawer

While you can swipe from the edges of your screen to reveal the drawer, most users are not familiar with this gesture. Instead, you want to programmatically show or hide the drawer when a user taps an icon from the header bar (such as the cart icon to reveal the cart drawer) or when a user does something in the app (such as adding an item to their shopping cart). Now, let's hook up the 'cart' button in the header bar to open and close the right drawer.

The HeaderBarPlugin instance emits events when buttons are pressed. The event name has a format of click:. If you remember from when we created the HeaderBar we assigned ids to the buttons. We gave the cart an id of cart, so we have to listen for the click:cart event.

We'll need a refererence to the right drawer from above so let's capture that in a variable.

Change the above code that we added:

Promise.join(cartWebViewPromise, drawerPromise, function(cartWebView, drawer) {
    const rightDrawer = drawer.initRightMenu(cartWebView);
    // We want the right drawer later, so we will return it
    return rightDrawer;
});

to

const rightDrawerPromise = Promise.join(cartWebViewPromise, drawerPromise, function(cartWebView, drawer) {
    const rightDrawer = drawer.initRightMenu(cartWebView);
    // We want the right drawer later, so we will return it
    return rightDrawer;
});

Then, add this code at the bottom:

// Bind the `click:cart` event once the promises have been fulfilled.
Promise.join(rightDrawerPromise, headerPromise, function(rightDrawer, header) {
    header.on('click:cart', function() {
        rightDrawer.toggle();
    });
});

Save the app.js file and re-run the app in Xcode. Tap the bag icon in the header bar to open and close the right drawer.

Add Cart Interaction

In this section we're going to make the right drawer open when a user adds an item to their cart. This is a nice user experience because a shopper can see their item was added to the cart and they can see the updated price. By implementing this interaction, you will learn one of the ways that hosted web content can communicate with Astro (open drawer) from a webpage. First, include the Astro JavaScript library in the demo site.

In your editor of choice, open the HandleBars partial site/views/partials/scripts.hbs. This file gets included on every page of the sample site. Include the script:

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

You can now access the Astro JavaScript API on the window.Astro object. Next you will add some code to the site/public/js/cart-add.js file that will trigger an event when a user adds an item to their cart. Attach a handler to the cart button click event with the code below:

$(".js-add").click(function() {
    Astro.trigger('addToCartClicked');
});

This handler triggers a custom addToCartClicked event through Astro. By binding addToCartClicked through Astro, we are now able to listen for this event in our app.js file. At the bottom of the app.js file after the last promise to toggle the right drawer, listen for the addToCartClicked event and open the drawer with the code below:

// Add a handler on the main web view to open drawer when 'addToCartClicked'
// event happens.
Promise.join(
    mainNavigationPromise,
    rightDrawerPromise,
    cartWebViewPromise,
    function(mainNavigationView, rightDrawer, cartWebView) {
        mainNavigationView.on('addToCartClicked', function() {
            // Open the right drawer
            rightDrawer.open();
            // Let the cart view know that something has been added
            cartWebView.trigger('cartUpdated');
        });
    }
);

Save the app.js file and re-run the app in Xcode.

Congrats you are finished the tutorial!

You can checkout the final tutorial code to see how your code compares.

Full Solution #

If you get stuck, or just want to jump to the full solution, you can view the app.js file on the Astro reference docs website (it works on both iOS and Android).

Summary

So you’ve built your first Astro app? Great job! This section covers most of the main development concepts of Astro at a very high-level: header bar, flyout drawers, anchored layout, and native/web interaction. Chapter 2 will dive deeper into some of these concepts.

We encourage you to play around with the app you built. Try to customize the app by pointing the main web view to your own website and change the header bar’s color and logo to match your website’s brand. Once you’ve finished making a few changes you’ll have a better idea of where everything is located in the project and we’re sure you’ll have plenty of questions that we’ll answer in the upcoming chapters.