Intro to Mobile App Development with NativeScript-Vue

If you're a web developer accustomed to the simplicity of Vue.js, learning a whole new language (or two) to develop native mobile apps for iOS and Android may seem daunting. While you can use React Native to develop for mobile using JavaScript, there will still be a steep learning curve unless you're already familiar with React.

Enter NativeScript: it's another open source framework for native mobile app development using JavaScript (or TypeScript), with support for Vue and Angular syntax. The apps you build with it aren't web-based imitations; NativeScript uses JavaScript Virtual Machines on Android and iOS to run your app, and provides access to native APIs through its core modules, translating your JavaScript code into Java for Android, and Objective-C for iOS.

If you're already familiar with Vue, you'll get to take advantage of your existing Vue knowledge and experience when working with NativeScript-Vue. Your templates will look a little different—there's no div element in NativeScript, for example—but generally, you'll build your app using very familiar Vue syntax, and with plugins like Vuex. It's even possible to share some code between web and mobile platforms in the same codebase.

In this post, we'll configure our environment for NativeScript development on iOS, and create a basic NativeScript-Vue app using Tailwind CSS that can be deployed to iOS and Android devices.

Installing NativeScript

Prerequisites:

  • macOS High Sierra or later
  • Node 10.x
  • Homebrew
  • Latest Xcode + command-line tools, xcodeproj Ruby gem, CocoaPods, and the Python six package

Note that iOS apps can only be built on macOS machines, but if you work on Windows or Linux, NativeScript provides a cloud-based platform called Sidekick that can get you up and running.

To install the necessary dependencies, run the commands below (more detailed instructions are available in the NativeScript macOS setup article):

brew install node@10
sudo gem install xcodeproj
sudo gem install cocoapods
pod setup
sudo easy_install pip
pip install six

Install the NativeScript CLI using NPM:

npm install -g nativescript

Now you can run the system configuration tool, using the command tns doctor, to verify your setup and identify any problems that need to be fixed. (tns stands for "Telerik NativeScript"; Telerik is the company that supports NativeScript's development.) When prompted to configure cloud and/or local builds, select "Skip this step and configure manually".

tns doctor

If there are any errors related to NativeScript or iOS development, follow the provided instructions to fix them. Ignore any Android errors; we won't need Android virtual devices just yet.

Creating a new NativeScript-Vue project

NativeScript-Vue projects can be created using Vue CLI, just like regular Vue apps (see: NativeScript-Vue Quick Start). Install Vue CLI and initialize your new project:

npm install -g @vue/cli @vue/cli-init
vue init nativescript-vue/vue-cli-template hello-world

The init command will prompt you for some details about your project. You can customize these options or just accept the defaults; everything here can be changed later. When it's finished, cd into the project folder and install NPM dependencies:

cd hello-world
npm install

Installing Tailwind CSS

NativeScript supports using CSS in its mobile apps—but not all CSS properties and values are supported, and most values use different units of measurement. This means we can't just import Tailwind the usual way, but there are two options: either create a NativeScript-compatible Tailwind config file overriding any unsupported properties & units, or—better yet—use the NativeScript Tailwind plugin which ships with a NativeScript-compatible default config file.

Install NativeScript Tailwind as a dev dependency:

npm install -D nativescript-tailwind

Import the pre-built Tailwind CSS file in app/app.scss, which NativeScript loads on all pages by default:

@import "nativescript-tailwind/dist/tailwind";

Note: To keep this tutorial simple we're using the default build, but you can also generate custom builds using npx nativescript-tailwind tailwind.config.js. See my config file PR for more information.

Running the app

We're ready to build our app! NativeScript will make use of Xcode's iOS Simulator feature to run our app on a simulated iOS device:

tns run ios

This command is similar to npm run watch; you can leave it running in a terminal window during development and it will rebuild the app automatically when you save a file. Hot module reloading is also enabled by default, so most changes will appear instantly without needing to re-compile the app.

Screenshot of an iPhone simulator running the default "Hello World" NativeScript-Vue app.

Note: You can choose which iOS device to simulate in the Simulator app menu, under Hardware > Device.

Customizing the landing page

Next, open app/components/App.vue. This is your root component, and the landing page of your mobile app.

You'll notice this component uses <Page> as its outer element—your app runs in a <Frame> element (open app/main.js to see it in action), which can contain <Page> elements to denote individual pages. In NativeScript, navigating to a new <Page> is like navigating to a new URL.

Let's add a basic, table-like layout to the landing page using NativeScript's <GridLayout> element. We'll start with one column sized to fill all the available space (columns="*"), and three rows with the middle one auto-sized to its content (rows="*, auto, *"):

<template>
  <Page class="bg-blue-100">
    <ActionBar class="bg-blue-900 text-white" title="Welcome to NativeScript-Vue!"/>

    <GridLayout columns="*" rows="*, auto, *" class="text-center mx-8">
      <Label col="0" row="0" class="font-bold text-blue-800 text-xl" :text="msg"/>

      <Button col="0" row="1" class="bg-blue-200 mx-8 py-3 rounded" text="Click Me!" @tap="msg = 'Hi Mom'"/>
    </GridLayout>
  </Page>
</template>

<script>
  export default {
    data() {
      return {
        msg: 'Hello World',
      }
    }
  }
</script>

Assuming you left the command tns run ios running, you should see "Hello World" and a "Click Me!" button appear on your homescreen, styled with Tailwind classes.

Screenshot of an iPhone simulator showing our custom landing page with "Hello World" text and a "Click Me" button.

NativeScript provides several other types of layouts to arrange content sections, including <FlexboxLayout> which behaves similarly to CSS's Flexbox. For example, if we wanted to mimic the behavior of Tailwind's <div class="flex justify-around"> we could use the justifyContent attribute:

    <GridLayout columns="*" rows="*, auto, *">
      <FlexboxLayout col="0" row="1" justifyContent="space-around">
        <Label class="font-bold text-blue-800 text-xl" :text="msg"/>

        <Button class="bg-blue-200 px-4 py-2 rounded" text="Click Me!" @tap="msg = 'Hi Mom'"/>
      </FlexboxLayout>
    </GridLayout>

Note: Each nested layout generates a new view "layer", so complex layouts can be costly to render. Keep your layouts simple for optimal performance.

Navigating to another page

Now that we're familiar with NativeScript layouts, let's add a second page to our app. Create a new file app/components/Page2.vue and put a basic layout in it:

<template>
  <Page class="bg-green-600">
    <ActionBar class="bg-blue-900 text-white" title="Page Two"/>
    <GridLayout columns="*" rows="*">
      <Label col="0" row="0" class="text-center text-white" text="Now we're on page 2"/>
    </GridLayout>
  </Page>
</template>

To route to this new page, we'll need to use the built-in method $navigateTo—Vue Router isn't compatible with NativeScript's Frame/Page navigation.

Back in App.vue, import the new component and set it to a data property, then navigate to it using the button's @tap event:

<template>
  <Page class="bg-blue-100">
    <ActionBar class="bg-blue-900 text-white" title="Welcome to NativeScript-Vue!"/>

    <GridLayout columns="*" rows="*, auto, *" class="text-center mx-8">
      <Label col="0" row="0" class="font-bold text-blue-800 text-xl" :text="msg"/>

      <Button
        col="0"
        row="1"
        class="bg-blue-200 mx-8 py-2 rounded"
        text="Click Me!"
        @tap="$navigateTo(page2)"
      />
    </GridLayout>
  </Page>
</template>

<script>
  import Page2 from './Page2'

  export default {
    data() {
      return {
        msg: 'Hello World',
        page2: Page2,
      }
    }
  }
</script>

Now clicking the button takes us to the Page2 component.

Screenshot of an iPhone simulator with the Page2 Vue component visible.

Congratulations! You've built your first mobile app with NativeScript-Vue. Though we haven't tried it on an Android virtual device yet (they're slightly tougher to configure than iOS devices on a Mac), your NativeScript app is ready to be compiled for both platforms, and includes tooling to assist in publishing to the App Store and Google Play Store.