1. Code
  2. Mobile Development

Creating Your First App With Fuse

Scroll to top

Now that you've learned the basic concepts of Fuse, it's time to put things into practice and build an app. In this tutorial, you'll learn how to develop an app using the Fuse framework. Specifically, you're going to learn the following:

  • How to code using UX Markup.
  • How to use the Observable, Timer, and Geolocation APIs.
  • How to preview an app using desktop preview and custom preview.

If you need a refresher on Fuse, check out my previous post in this series: Introducing Fuse for Cross-Platform App Development.

Prerequisites

To start working with Fuse, go to the downloads page and sign up for an account. You can also log in to an existing account if you have one. 

Fuse is available for both Windows and macOS. Download and install the correct installer for your platform. On the downloads page, they also point out the Fuse plugins available for various text editors. Install the one for your text editor. The Fuse plugins include code completion, goto definition, and viewing of logs generated from the app, all of which makes developing apps more convenient.

We'll also cover how to preview the app using custom preview. This requires Android Studio or Xcode to be installed on your computer.  

A basic understanding of web technologies such as HTML, CSS, and JavaScript is helpful but not required.

What You'll Be Creating

You'll be creating a stopwatch app which also measures the distance covered. The distance is measured using geolocation. The user can also create laps, and the individual distance and time for each lap will be displayed on the screen.

Here's what the app will look like:

Final Output HIIT StopwatchFinal Output HIIT StopwatchFinal Output HIIT Stopwatch

You can view the complete source code in the tutorial GitHub repo.

Creating a New Fuse Project

Once you have installed Fuse Studio, you should now be able to create a new Fuse project. Just open Fuse Studio and click on the New Fuse Project button. Enter the name of the project, and click Create:

Create a new Fuse projectCreate a new Fuse projectCreate a new Fuse project

This will create a new folder in the selected directory. Open that folder and open the MainView.ux file. By default, it will only have the <App> markup. Update it to include a <Text>, and then save the file:

1
<App>
2
    <Text FontSize="25">Hello World!</Text>
3
</App>

The preview should now be updated with the text you specified:

Hello world outputHello world outputHello world output

That's the main development workflow in Fuse. Just save the changes to any of the files in the project directory, and they will automatically get reflected in the desktop preview. 

You can also see the logs in the bottom panel. You can trigger your own by using console.log(), like in the browser. The only difference is that you have to JSON.stringify() objects in order to see their value, since the console.log() implementation in Fuse can only output strings. 

UX Markup

Now we're ready to build the app. Open the MainView.ux file and remove the <Text> element from earlier. That way, we can start with a blank slate:

1
<App>
2
3
</App>

Including Fonts

Just like in an HTML document, the standard is to include the assets—things like fonts, stylesheets, and scripts—before the actual markup of the page. So add the following inside the <App> element:

1
<Font File="assets/fonts/roboto/Roboto-Thin.ttf" ux:Global="Thin" />

This imports the font specified in the File attribute and gives it the name Thin. Note that this doesn't make it the default font for the whole page. If you want to use this font, you have to use its name (Thin) on the specific text you want to apply it to. 

You can download the font from the tutorial GitHub repo. After that, create an assets/fonts/robot folder inside the root project directory and put the .ttf file in it.

If you want to use another font, you can download it from dafont.com. That's where I downloaded the font for this app.

Next, we want to use icons inside the app. Fuse doesn't really have built-in elements and icon sets which allow you to do that. What it offers is a way to include existing icon fonts in your app. Since icon fonts are essentially fonts, we can use the same method for including fonts:

1
<Font File="assets/fonts/icons/fa-solid-900.ttf" ux:Global="FontAwesome" />

You can download the icon font from the GitHub repo or download it directly from fontawesome.com. Note that not all icons on fontawesome are free, so it's best to check the actual icon page before using it. If you see a "pro" label next to the icon, then you can't simply use it in your project without paying. 

Including JavaScript

Next, we need to include the JavaScript file for this page. We can do that using the <JavaScript> element:

1
<JavaScript File="scripts/MainView.js"/>

Don't forget to create the scripts/MainView.js file at the root of the project directory. 

Creating New Components

To maximize code reuse, Fuse allows us to create custom components from existing ones. In the code below, we're using a <Panel> to create a custom button. Think of it like a div which acts as a container for other elements. In this case, we're using it as a reusable component for creating a button. 

Fuse comes with many elements. There are elements for laying out content such as the <Panel>, elements for showing user controls, pages and navigation, scripting and data, and primitives for building the UI. Each one has its own set of properties, allowing you to modify the data, presentation, and behavior.

To create a reusable component, add a ux:Class property to a presentation element that you'd like to use as a base. In this case, we're using a <Panel> as the base. You can then add some default styling. This is similar to how styling is done in CSS. Margin adds space outside of the container. Here we've only specified a single value, so this margin is applied on all sides of the panel. Color adds a background color to the element:

1
<Panel ux:Class="ToggleBtn" Margin="4" Color="#007bff">
2
    
3
</Panel>

Inside the <Panel>, we want to show the button text. We want to make this into a reusable component, so we need a way to pass in properties for when we use this component later on. This allows us to achieve different results by only changing the properties. 

Inside the <Panel>, use the data type of the value you want to pass in as the name of the element, and then add the name of the property using ux:Property. You can then show the value supplied to the property by using {ReadProperty PropertyName}, where PropertyName is the value you supplied to ux:Property. This will allow you to supply a Text property whenever you're using the <ToggleBtn> component.

1
<string ux:Property="Text" />
2
<Text Value="{ReadProperty Text}" Color="#fff" FontSize="18" Alignment="Center" Margin="20,15" />

Next, we want to offer the user some sort of feedback while the button is being pressed. We can do that via triggers and animators. Triggers are basically the event listeners—in this case, <WhilePressed>. And animators are the animations or effects you want to perform while the trigger is active. The code below will make the button 10% bigger than its original size and change its color. Duration and DurationBack allow you to specify how long it takes for the animation to reach its peak and reach its end.

1
<WhilePressed>
2
    <Scale Factor="1.1" />
3
    <Change this.Color="#636363" Duration="0.03" DurationBack=".03" />
4
</WhilePressed>

Next, we create the <IconBtn> component. As the name suggests, this is a button which only shows an icon as its content. This works the same way as the previous component, though there are a few new things we've done here. 

First is the ux:Name property. This allows us to give a name to a specific element so we can refer to it later. In this case, we're using it to change its Color property while the button is being pressed. 

We've also used a conditional element called <WhileTrue>. This allows us to disable the <WhilePressed> trigger when the value for is_running is a falsy one. We'll supply the value for this variable once we get to the JavaScript part. For now, know that this variable indicates whether the timer is currently running or not.

1
<Panel ux:Class="IconBtn">
2
    <string ux:Property="Text" />
3
    <Text Font="FontAwesome" Color="#333" ux:Name="LapText" FontSize="40" Alignment="Center">{ReadProperty Text}</Text>
4
    <WhileTrue Value="{is_running}"> 
5
        <WhilePressed>
6
            <Change LapText.Color="#ccc" Duration="0.09" /> <!-- change text color -->
7
            <Rotate Degrees="90" Duration="0.02"/> <!-- rotate the button by 90 degrees -->
8
        </WhilePressed>
9
    </WhileTrue>
10
</Panel>

Main Content

We can now proceed with the main content. First, we wrap everything in a <StackPanel>. As the name suggests, this allows us to "stack" its children either vertically or horizontally. By default, it uses vertical orientation so we don't need to explicitly specify it: 

1
<StackPanel  Margin="0,25,0,0" Padding="20">    
2
    
3
</StackPanel>

In the code above, we used four values for the Margin. Unlike CSS, the value distribution is left, top, right, bottom. If only two values are specified, it's left-right, top-bottom. You can use the selection tool in Fuse Studio to visualize the margins applied.

Next, we add a background image for the page. This accepts the file path to the background image you want to use. A StretchMode of Fill makes the background stretch itself to fill the entire screen:

1
<ImageFill File="assets/images/seigaiha.png" StretchMode="Fill" />

You can download the background image I've used from the tutorial GitHub repo. Or you can find similar patterns on the Toptal website

Next, show the name of the app. Below it is the time-elapsed text field. This text needs to be updated frequently, so we need to turn it into a variable which can be updated via JavaScript. To output some text initialized in this page's JavaScript file, you need to wrap the variable name in curly braces. Later on, you'll see how the value for this variable is supplied from the JavaScript file:

1
<Text Value="HIIT Stopwatch" Color="#333" FontSize="18" Alignment="Center" Margin="0,0,0,10" />
2
<Text FontSize="65" Font="Thin" TextAlignment="Center" Margin="0,0,0,20">{time_elapsed}</Text>

Next, we use the <IconBtn> component that we created earlier—not unlike in a web environment where we use the ID of the font. In Fuse, you have to use the Unicode assigned to the icon font you want to use. You also need to use &#x as a prefix. When this button is pressed (called Clicked), the addLap() function declared in the JavaScript file is executed:

1
<IconBtn Text="&#xf2f1;" Clicked="{addLap}" />

In Font Awesome, you can find the unicode of the icon font on its own page.

Right below the button for adding a new lap is some text which indicates that the button above is for adding new laps:

1
<Text Value="Lap" Color="#333" FontSize="15" Alignment="Center" Margin="0,5,0,20" />

Next, show the button for starting and stopping the timer. This also executes a function which we will declare later:

1
<ToggleBtn Text="{toggle_btn_text}"  Clicked="{toggle}" />

Next, we need to output the laps added by the user. This includes the lap number, distance covered, and time spent. The <Each> element allows us to iterate through a collection of objects and display the individual properties for each object:

1
<StackPanel Margin="20,40">
2
    <Each Items="{laps}">
3
        <DockPanel Margin="0,0,0,15">
4
            <Text Alignment="Left" FontSize="18" Color="#333" Value="{title}" />
5
            <Text Alignment="Center" FontSize="18" Color="#333" Value="{distance}" />
6
            <Text Alignment="Right" FontSize="18" Color="#333" Value="{time}" />
7
        </DockPanel>
8
    </Each>
9
</StackPanel>

In the code above, we're using the <DockPanel> to wrap the contents for each item. This type of panel allows us to "dock" its children on different sides (top, left, right, and bottom) of the available space. By default, this positions its children directly on top of each other. To evenly space them out, you need to add the Alignment property. 

JavaScript Code

Now we're ready to add the JavaScript code. In Fuse, JavaScript is mainly used for the business logic and working with the device's native functionality. Effects, transitions, and animations for interacting with the UI are already handled by the UX Markup.

Start by importing all the APIs that we need. This includes Observable, which is mainly used for assigning variables in the UI. These variables can then be updated using JavaScript. Timer is the equivalent of the setTimeout and setInterval functions in the web version of JavaScript. GeoLocation allows us to get the user's current location:

1
var Observable = require("FuseJS/Observable");
2
var Timer = require("FuseJS/Timer");
3
var GeoLocation = require("FuseJS/GeoLocation");

Next, we initialize all the observable values that we'll be using. These are the variables that you have seen in the UX markup earlier. The values for these variables are updated throughout the lifetime of the app, so we make them an observable variable. This effectively allows us to update the contents of the UI whenever any of these values change:

1
var time_elapsed = Observable(); // the timer text

2
var toggle_btn_text = Observable(); // the text for the button for starting and stopping the timer

3
var is_running = Observable(); // whether the timer is currently running or not

4
var laps = Observable(); // the laps added by the user

After that, we can now set the initial values for the toggle button and timer text:

1
toggle_btn_text.value = 'Start'; // toggle button default text

2
time_elapsed.value = "00:00:00"; // timer default text

That's how you change the value of an observable variable. Since these are not inside any function, this should update the UI immediately when the app is launched.

Set the initial values for the timer, lap time, and location for each lap:

1
var time = 0; // timer

2
var lap_time = 0; // time for each lap

3
var locations = []; // location of the user for each lap

The toggle() function is used for starting and stopping the timer. When the timer is currently stopped and the user taps on the toggle button, that's the only time we reset the values for the timer and laps. This is because we want the user to see these values even after they stopped the timer. 

After that, get the user's current location and push it on the locations array. This allows us to compare it to the next location later, once the user adds a lap. Then, create a timer which executes every 10 milliseconds. We increment both the overall time and the lap_time for every execution. Then update the UI with the formatted value using the formatTimer() function:

1
function toggle(){
2
3
    if(toggle_btn_text.value == 'Start'){ // the timer is currently stopped (alternatively, use is_running)

4
        laps.clear(); // observable values has a clear() method for resetting its value

5
        time_elapsed.value = formatTimer(time);
6
        is_running.value = true;
7
8
        locations.push(GeoLocation.location); // get initial location

9
10
        timer_id = Timer.create(function() {
11
            time += 1; // incremented every 10 milliseconds

12
            lap_time += 1; // current lap time

13
            
14
            time_elapsed.value = formatTimer(time); // update the UI with the formatted time elapsed string

15
        }, 10, true);
16
    }else{
17
        // next: add code for when the user stops the timer

18
    }
19
20
    toggle_btn_text.value = (toggle_btn_text.value == 'Start') ? 'Stop' : 'Start';
21
}

When the user stops the timer, we delete it using the delete() method in the timer. This requires the timer_id that was returned when the timer was created:

1
Timer.delete(timer_id); // delete the running timer

2
// reset the rest of the values

3
time = 0;
4
lap_time = 0;
5
is_running.value = false; 

Next is the function for formatting the timer. This works by converting the milliseconds into seconds and into minutes. We already know that this function is executed every 10 milliseconds. And the time is incremented by 1 every time it executes. So to get the milliseconds, we simply multiply the time by 10. From there, we just calculate the seconds and minutes based on the equivalent value for each unit of measurement:

1
function formatTimer(time) {
2
    function pad(d) {
3
        return (d < 10) ? '0' + d.toString() : d.toString();
4
    }
5
6
    var millis = time * 10;
7
    var seconds = millis / 1000;
8
9
    mins = Math.floor(seconds / 60);
10
    secs = Math.floor(seconds) % 60,
11
    hundredths = Math.floor((millis % 1000) / 10);
12
    return pad(mins) + ":" + pad(secs) + ":" + pad(hundredths);
13
}

Every time the user taps on the refresh button, the addLap() function is executed. This adds a new entry on top of the laps observable: 

1
function addLap() {
2
    if(time > 0){ // only execute when the timer is running

3
4
        lap_time_value = formatTimer(lap_time); // format the current lap time

5
        lap_time = 0; // reset the lap time

6
7
        var start_loc = locations[laps.length]; // get the previous location

8
        var end_loc = GeoLocation.location; // get the current location

9
        locations.push(end_loc); // add the current location

10
11
        var distance = getDistanceFromLatLonInMeters(start_loc.latitude, start_loc.longitude, end_loc.latitude, end_loc.longitude); 
12
        
13
        // add the new item on top

14
        laps.insertAt(0, {
15
            title: ("Lap " + (laps.length + 1)),
16
            time: lap_time_value,
17
            distance: distance.toString() + " m."
18
        });
19
        
20
    }
21
}

Here's the function for getting the distance covered in meters. This uses the Haversine formula:

1
function getDistanceFromLatLonInMeters(lat1, lon1, lat2, lon2) {
2
    function deg2rad(deg) {
3
      return deg * (Math.PI/180)
4
    }
5
6
    var R = 6371; // radius of the earth in km

7
    var dLat = deg2rad(lat2 - lat1);  
8
    var dLon = deg2rad(lon2 - lon1); 
9
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
10
        Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); 
11
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 
12
    var d = (R * c) * 1000; // Distance in m

13
    return d;
14
}

Don't forget to export all the observable values:

1
module.exports = {
2
    toggle: toggle,
3
    toggle_btn_text: toggle_btn_text,
4
    is_running: is_running,
5
    time_elapsed: time_elapsed,
6
    laps: laps,
7
    addLap: addLap
8
}

Geolocation Package

To keep things lightweight, Fuse doesn't really include all the packages that it supports by default. For things like geolocation and local notifications, you need to tell Fuse to include them when building the app. Open StopWatch.unoproj at the root of your project directory and include Fuse.GeoLocation under the Packages array:

1
"Packages": [
2
    "Fuse",
3
    "FuseJS",
4
    "Fuse.GeoLocation" // add this
5
],

This should instruct Fuse to include the Geolocation package whenever building the app for custom preview or for generating an installer. 

Setting Up for Custom Preview 

Before you can run the app on your iOS device, you need to add a bundle identifier to the app first. Open the StopWatch.unoproj file and add the following under iOS. This will be the unique identification for the app when it's submitted to the app store:

1
"Packages": [
2
    // ...
3
],
4
"iOS": {
5
    "BundleIdentifier": "com.yourname.stopwatch",
6
    "PreviewBundleIdentifier": "com.yourname.stopwatch.preview"
7
}

Next, on Xcode, log in with your Apple developer account. If you don't already have one, you can go to the Apple developer website and create one. It's actually free to develop and test apps on your iOS device. However, there are some limitations if you're not part of the developer program.

Once your account is created, go to Xcode preferences and add your Apple account. Then click on Manage Certificates and add a new certificate for iOS development. This certificate is used to ensure that the app is from a known source.

Once that's done, you should now be able to run the app on your device. Click on Preview > Preview on iOS in Fuse Studio and wait for it to launch Xcode. Once Xcode is open, select your device and click the play button. This will build the app and install it on your device. If there's a build error, it's most likely that the preview bundle identifier is not unique:

change the bundle IDchange the bundle IDchange the bundle ID

Changing the Bundle Identifier to something unique should solve the issue. Once the error under the signing section disappears, click on the play button again to rebuild the app. This should install the app on your device. 

However, you won't be able to open the app until you approve it. You can do that on your iOS device by going to Settings General Device Management and selecting the email associated with your Apple Developer account. Approve it, and that should unlock the app.

For Android, you should be able to preview the app without any additional steps.

Conclusion

That's it! In this tutorial, you've learned the basics of creating an app using the Fuse framework. Specifically, you've created a stopwatch app. By creating this app, you've learned how to work with Fuse's UX Markup and a few of Fuse's JavaScript APIs. You also learned how to use Fuse Studio to preview the app on your computer and your phone while developing it.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.