Flip that App! Hybrid Mobile to JavaScript Native

Many developers jumped onto the hybrid mobile train as the Cordova ecosystem became more and more mature. It seemed, and to many still seems, like such a win! You can use your current skills as a web developer to build what is essentially a small web site that runs in a WebView on a native device such as iOS, Android, or Windows Phone. Using Cordova plugins and some know-how, you can create a near-native user experience using hybrid mobile app development techniques and technologies. For many use cases, a hybrid mobile app works beautifully!

At some point, however, you may decide that you need to diverge from the hybrid mobile ecosystm and try something different. Maybe you are looking to build a more native-like, performant app, moving away from some of the lagginess of the WebView. Ready to jump off the hybrid mobile train?

train-jump-divergent
Make the leap, like Tris and Christina, to a native app! Image courtesy of Lionsgate

The question you may be asking yourself is, how hard would it be to flip your app from a hybrid mobile app to a new technology that offers a more native user experience?

NativeScript offers a truly native mobile app built cross-platform using JavaScript, CSS, and XML. In this article, I’m going to walk through how I converted, or flipped, my School Bell Weather app, a mobile app for iOS and Android that gets weather information from forecast.io and recommends what to wear to the bus stop. Originally a hybrid mobile app, I recently converted it to NativeScript.

sbw_slider
The original design for the hybrid mobile app

sbw_slider_v2

The NativeScript version of School Bell Weather

From Hybrid to Native

Switching from a hybrid mobile mentality to embracing all things native entails a switch in mentality. You’re going to want to pay special attention to how the moving parts fit together.

Take the app flipping exercise as an opportunity to examine any awkwardness that your hybrid mobile app presented, and consider ways to fix it. Identify the elements you want to keep, and plan to reuse what assets you can. In my case, I needed to reuse the “weather cat” images that I created for the hybrid app, as well as the colors and the icon font that contains the weather icons that map to the data returned by forecast.io.

With the app already designed in its first iteration as a hybrid mobile project, it was easy to make decisions about what assets to add and what to remove. I decided that I would keep the basic design and reuse the calls to forecast.io to return that data that I needed, but would rely on my new access to native elements to govern the remaining design decisions.

divergent_operation
A change of mentality. This won’t hurt a bit… Image courtesy of Fanpop

Layouts

In any mobile app, you’re going to need to tackle layouts almost immediately. Crafting layout grids is quite different in hybrid versus a native app. While I used CSS to create a grid layout for the hybrid mobile version of the app, I was able to rely on NativeScript’s native implementation of grid layouts to create a nice scaling layout, removing the need for heavy CSS styling.

So, whereas in the hybrid app I used a combination of the pure.css framework and labeled divs to create a box in the view:

<div class="pure-g" id="conditions">
   <div class="pure-u-1-2 left-panel weather">
      <h2 align="center" class="headinglg">Now</h2>
      <p align="center" class="current"></p>
      <h2 align="center" class="current_temp"></h2>
   </div>
</div>

I used layout tags within XML in the NativeScript version:

<StackLayout class="blue1-container" colSpan="2" row="1">
    <Label text="Now" row="2" colSpan="2" horizontalAlignment="center" verticalAlignment="center" class="large-text top"/>
    <Label row="3" horizontalAlignment="center" colSpan="2" text="{{ nowIcon, nowIcon | toIcon() }}" class="weather-icon large-icon"/>
    <Label text="{{currentTemp || '--°' }}" row="4" colSpan="2" horizontalAlignment="center" class="large-text" />
</StackLayout>

While the XML markup looks more verbose than the plain HTML because of the bound elements in the curly brackets, a quick look at the JavaScript in the code-behind files shows how easy it is to get data from forecast.io using an HTTP call:

http.request({ url: url, method: "GET" }).then(function (response) {
    var obj = response.content.toJSON();
    var tmpCurrTemp = JSON.stringify(obj.currently.temperature).toString().split('.');
    var tmp_split = tmpCurrTemp[0];
    var nowIcon = eval(JSON.stringify(obj.currently.icon));
    ...

…and assign it to the View Model so that it can be bound to the XML:

WeatherModel.set("currentTemp", tmp_split + ' ' + mode);
WeatherModel.set("nowIcon", nowIcon);

The biggest change to your mentality when transitioning to native technologies is that you can no longer rely on DOM manipulation to make your screens react to the users’ actions. In a hybrid mobile app, the same call can be made to forecast.io, but you use DOM manipulation to refresh your screen, for example to assign your icon:

$('.current').addClass('icon-'+current_conditions.icon);
$('.current_temp').html(tmp[0]+'&deg;');

While working with layouts in XML markup can be daunting at first, avoiding DOM manipulation and the differences in WebViews across the fragmented device scenarios that you are supporting is a win.

Navigation

divergent_fighting
Getting a codebase into fighting condition means paring it down. Image courtesy of FanPop

In terms of navigation, I decided to move from a drawer navigation to a tabbed navigation in the NativeScript app, since I felt that, as an app geared towards kids, it would be better to expose the navigation up front and avoid a hidden menu. This decision cut down on the amount of code dramatically.

While I have one HTML and one JavaScript file for each page of the hybrid app, clickable via the hidden left navigation, in the NativeScript app the code is considerably abbreviated. In fac,t the entire front end of the NativeScript app is written in about 400 lines of XML code in one file, whereas the hybrid version is contained in six subdirectories within the /view directory, each file containing a new series of divs.

Using tabbed navigation gave the app a much more native feel, as the user experience of tabs is quite different on iOS vs. Android. Fortunately, NativeScript abstracts away these differences so your XML markup works seamlessly across platforms:

app_settings
Native iOS tabs, switch, and time picker

ss-android-sm
The same code on Android

The left navigation in a hybrid app relies on identifying divs as having various data-roles, and their behavior being governed by a framework (in this case, Kendo UI Mobile):

<header data-role="header">
     <div data-role="navbar">
         <span data-role="view-title"></span>
          <a data-role="button" href="#leftNav" data-rel="drawer" data-align="left">
             <span class="icon-lines"></span>
           </a>
       </div>
</header>

The NativeScript app is laid out as a series of tabbed layout groups:

<TabView id="weatherTabs">
   <TabView.items>
     <TabViewItem title="My Weather" iconSource="res://home">
       <TabViewItem.view>
            <GridLayout columns="*,*,*,*" rows="auto, *, *">
            ...
            </GridLayout>

NativeScript easily handles the creation of these native widgets, speeding up development considerably.

Local Storage to App Settings

One interesting change that I had to make was to transition from using the web technology local storage, to save data locally, to something more native-friendly. NativeScript supports “application settings“, a handy NativeScript module that allows you to store strings, numbers, and booleans to pass around your app.

Local storage is an HTML5 technology that has the limitation of being able to only store strings.

[it] simply provides a key-value mapping, e.g. localStorage["name"] = username;. Unfortunately, present implementations only support string-to-string mappings, so you need to serialise and de-serialise other data structures. You can do so using JSON.stringify() and JSON.parse().

Source: http://www.html5rocks.com/en/features/storage.

The creation of local storage key-value pairs is similar to using application settings in the code. Here’s the local storage version:

localStorage.setItem('transportation','bus');

Versus the app settings version:

appSettings.setString('transportation','bus');

Using application settings to store local data offers a straightforward substitute for local storage.

Image Issues

All apps rely on content scaling to deliver the proper image to the proper device. Currently, while building apps, the developer needs to create 1x, 2x, and 3x images to support all the devices from small phones to iPads with retina displays.

NativeScript apps require your images to be added to the appropriate folder in App_Resources and they need to be sized appropriately. Whereas in a hybrid mobile app you might add your scaled images all together into an img/ folder, if you want content-scaled images in a NativeScript app, you need to resize them three times for iOS and place them in the App_Resources iOS folder. You then need to do the same for App_Resources/Android in the ‘drawable’ folders.

Naming conventions are important as well; you shouldn’t use hyphens in the image names, and you must rename your @2x and @3x images for Android, placing them in the appropriate folder according to the screen resolutions you are targeting. Managing an app with a lot of images is a big pain for all apps that require content scaling, so for this weather app, using icon fonts was particularly attractive.

Since icon fonts are now supported in NativeScript widgets that accept a text element, such as buttons and labels, it was trivial to reuse the icon font I had previously generated on IcoMoon:

icon_fonts

You can use the HTML codes for each icon in the XML markup of the NativeScript app:

<Label text="&#xf129;" col="4" horizontalAlignment="right" verticalAlignment="center" class="top-icon weather-icon small-icon" tap="openInfo"/>

Or you can use the, via binding when an icon needs to be placed dynamically. To make this work, I had to convert the code using a helper file:

//convert the forecast.io icon term
case "partly-cloudy-day":
        return String.fromCharCode(parseInt('e621', 16));
        break;

This strategy contrasts with the hybrid mobile approach of using CSS to render icons:

[class^="icon-"], [class*=" icon-"] {
  font-family: 'icomoon';
  speak: none;
  font-style: normal;
  color:white;
  font-size:500%;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  margin:0 0 10px 0;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.icon-clear-day:before {
  content: "\e601";
}

As is normal, hybrid mobile apps rely on web technologies like HTML and CSS, but transitioning code to JavaScript and XML is not too painful.

Plugins vs. Modules

The best thing about building a hybrid mobile app, in my opinion, is its maturity as a technology. In particular, the plugin ecosystem is really great. You can find Cordova plugins to cover almost every use case. Telerik even offers a curated plugins marketplace that containing managed plugins for free.

On the other hand, the plugins for these new native-style cross platform apps are just starting to develop, but soon I expect that there will be just as many options. In the meantime, I must admit that getting a specialized plugin such as location services to work in a hybrid app is considerably easier than getting it to work in a NativeScript app.

To enable location services in a hybrid mobile app, you simply need to ensure that the core location plugin is installed, and then use a standard call to Cordova’s navigation API to check whether the location was picked up and store the latitude and longitude in local storage:

if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(
              function (position) {
                  localStorage.setItem('lat',position.coords.latitude);
                  localStorage.setItem('long',position.coords.longitude);

Currently we need to use the NativeScript module for location which requires changes to the AndroidManifest.xml file, the Info.plist for iOS, and a certain amount of native code to trap the user’s input to the standard popup requesting location access on iOS.

Documentation on managing location in NativeScript apps can be found here but this was the hardest thing to handle when flipping the app. Fortunately, you can test location changes on your native emulator, which speeds up debugging time.

Change is Good

divergent_fear
Don’t be afraid of change! Image courtesy of FanPop

Iterating on an existing app allows you to make meaningful changes to improve it. While working on the first iteration of School Bell Weather, I got feedback from a friend that the app icon needed work, so I changed it. More importantly, I got a wonderful email from a Canadian mom who needed weather forecasts in Celsius:

Hi there,

Could you please advise how to change your school weather app to Celsius? We live in Canada and my children do not know the imperial weather scale in terms of temperature. So in their mind, when I say it’s 53 out they are wanting to go to school 3/4 naked & think I am quite daft when I suggest a sweater. Thank you for the help 😀

It’s always a great idea to listen to your customers, especially to the awesome Canadian moms! Using native techniques to create a switch was very easy and saving the switch value (‘C’ or ‘F’) in application settings was straightforward:

<SegmentedBar id="sBar" selectedIndex = "{{ selectedIndex }}" style="color: white" selectedBackgroundColor="white">
    <SegmentedBar.items>
          <SegmentedBarItem title="Celsius" />
          <SegmentedBarItem title="Fahrenheit" />
    </SegmentedBar.items>
</SegmentedBar>

In the JavaScript, I set up a listener to trap the switch changes and save the selected value to application settings:

sBar.addEventListener(observable.Observable.propertyChangeEvent, function (e) {
    if(e.value == 1){
       appSettings.setString("mode","F");
    }
    else{
       appSettings.setString("mode","C");
    }
    vmModule.mainViewModel.getLocation();
});

Come on board!

Are you on the fence about whether you want to change your current app stack from hybrid mobile to something with a more native feel? I recommend asking yourself the following questions:

  • Are you dissatisfied with the performance of your hybrid mobile app?
  • Does truly native development (Objective-c or Swift and Java) with its requirement of maintaining two separate codebases seem daunting?
  • Are you looking to continue to be able to use JavaScript to build mobile apps?
  • Are you open to trying a new technology?

If the answer to these questions is “Yes! Yes! A hundred times YES!” – or if you simply are in agreement with one of them – I recommend ‘flipping’ an app to see where the pain points might lie in such a project. It’s a great way to learn a new technology and decide what’s right for you. Good luck!

divergent_still
Take the plunge to native tech! Image courtesy of FanPop

Header image courtesy of Jared Tarbell

Comments

  • Adil Mourahi

    It’s a real story. Evolution for the better. Thank you so much

    • Jen Looper

      Thanks, Adil! 🙂