Mobile App Development - Crossing the Platforms, Dotting the Notation

2 March 2020

2 Mar 2020

2/3/20

Dave Cumming

16 MIN READ

16 MIN READ

Apps, Apps, Apps

Foreword: This post is based on a talk I gave at DDD Perth 2019 and I’ve included the original slides at the end of this blog. 

The talk was restricted to 20 minutes and as such is a fairly light and high level look at this topic as it was aimed at a wide spectrum of technical and non technical people. If you’re expecting a deep dive into this topic, this possibly isn’t for you. But if you want a broad brush strokes overview then it might well be so venture forth! 

We’re here today to attempt to answer 3 questions :

What is Cross Platform Development?

Is it useful?

Which solution is best for me?

Apps, Apps, Apps

In the modern world a huge percentage of people use their mobile phone every day, and in turn they use Apps. Facebook, Netflix, Angry Birds, Dropbox, Spotify, the list is endless. Apps are great, they are fast, slick and specific, but building them can be very expensive.

Why is this and what’s this got to do with Cross Platform Development?

First a brief history lesson

Prior to 2007 phones were ugly and clunky with hardware keyboards and/or stylus. Some had touch-screens but they were slow and unresponsive. We had a variety of operating systems, all different and most closed to developers. Then in January that year this happened.

Steve Jobs walked on stage at WWDC and announced the iPhone. It had no stylus or hardware keyboard. It came with a bright, clear screen, sleek design, shiny shiny case, snappy responsive UI and, combined with Apple’s now legendary marketing machine, forced the world to sit up and take notice.

At this stage it only supported the apps it came with, Calendar, Safari, Contacts, Notes and so on, run of the mill day to day apps users were already familiar with. It also had no Software Development Kit (SDK) for developers to build their own apps. 

But Apple had a trick up their sleeves. iOS was using Objective-C, which they were already utilising for their Mac desktop apps and developed using their proprietary IDE, Xcode.

In March 2008 Apple released a new version of Xcode with an iOS SDK which, along with the launch of their “app store”, birthed what we now recognise as mobile app development. By the close of 2009 there were already 50,000 apps in the store, today there are more than 2.2 million iOS apps available.

Late 2008 Google came to the party with the release of Android, which is based on the portable Java language. They also launched an app store, which became known later as Play.

Today there are more than 2.6 million Android apps in the Play store alone.

Other players came and went — such as Blackberry and the ill-fated Windows phone — but Apple and Google soon came to rule the roost, and have been battling it out ever since.

Why does any of this matter?

We’ve ended up with two very different platforms which dominate the marketplace and you usually want to support both if you want to reach all your potential users. Clients frequently ask us for a price for one platform, then for two and are shocked to learn that it’s more than likely double the original cost. 

“Why can’t you re-use some of what you’ve already done?” they cry. The answer is that iOS and Android use completely different languages, two different development platforms, and completely different ways of doing near enough everything, including UX layouts.

This means companies have to hire two teams of devs or cross train their existing teams to do both. Either way it’s expensive and this is a cost that’s usually passed on to the client.

Cross platform development attempts to resolve this by providing unified platforms or tools which allow you to build the same app for multiple platforms using a single common language, development kit and UX stylings.

Let’s take a look at a few of the possible options for Cross Platform Development.

Apache Cordova

Cordova started life as PhoneGap and was one of the earliest cross platform solutions. It is a Hybrid HTML5, CSS and Javascript platform which renders applications using the device browser engine. 

Cordova comes with its own web-to-native layer that allows access to many hardware functions, such as the camera, local storage, and accelerometer.

Later releases such as Ionic leverage Cordova or Capacitor (Ionic’s own native-bridge platform) to allow access to popular front end web frameworks such as React and Angular.

The main strengths of this approach are that you are developing in widely used web technologies familiar to many, and these also offer features such as hot re-loading of code changes which can allow for rapid development and prototyping.

The downside of this browser style solution is in performance and usage. Apps may “look” native but their performance and how they react to user input will never feel truly native, a fact which Adobe themselves acknowledge.

Partial Solutions

Here at Adapptor some of our early projects used various tools to achieve partial savings in development cost by building sections of applications “cross platform”. (And indeed most of our newer React Native apps continue to use Swagger CodeGen.)

Swagger : CodeGen : API

Swagger provides a format for server developers to define the shape of their API and generate web pages to test and document that API.

Swagger Codegen takes this definition and generates code for it in a multitude of languages, in our case Java which allows its use in native Android projects.

This gives us not only the data model as POJOs but also the code required to communicate with the web API.

J2ObjC : Java to Objective-C

J2Objc takes simple Java and converts it into Objective-C. It can handle most plain Java as long as it contains no Android SDK code, which means as well as converting the CodeGen code we can write business layer logic in Java and convert it to Objective-C using this tool. 

Squidb: CRUD Database

Squidb is a cross platform SQLite wrapper written by Yahoo. It allows us to easily store the model objects generated by Swagger in a device’s database for long term storage without the need to write complicated schemas.

These 3 tools, when used in conjunction, give us the ability to access a web API, download data from it, transform that data and apply business logic to it, store it in a database and perform the entire flow in reverse sending data back up the wire all in a cross platform manner.

Flutter

Flutter is Google’s answer to React Native (which we’ll come to). Rather than using an existing language, Google decided to implement their own custom language in Dart, a fast, predictable native language.

Flutter development takes its own path where rather than using a web view or a bridge it renders its own UI widgets to a canvas. This allows a fast, fluid UI but its controls are only “native like” since they are replicas of the real thing rather than the product of actual native APIs.

Although a descendant of Javascript, Dart itself is more like Java in that it has true concurrency as it runs within its own virtual machine.

As with React Native, Flutter is open source.

Flutter can be great for highly styled apps, i.e., those which drift from the expected native stylings, but if users are expecting a fully native experience it may not be ideal.

It is also an extremely young platform and language, meaning that its support forums aren’t as extensive as those for more mature platforms.

Progressive Web Apps

Progressive Web Apps are an extension of standard web development techniques which attempt to mimic and replace natively built applications. Some commentators believe that PWAs may one day replace native apps and their app stores, but the evidence so far doesn’t seem to support this.

Simply put, a PWA is a website on steroids. It is not as such a framework, more a set of techniques and standards. They are designed to work on every browser and in turn every device that runs a browser.

In order to be classed as a PWA, a website must pass a set of requirements. Although there appears to be debate about which of these are requirements and which are recommendations, the standard procedure is to run your website through the Lighthouse tests.

PWAs are expected to:

  • Be responsive and work on every browser

  • Be safe (use https)

  • Be network independent (run offline)

  • Have a standard manifest file which declares the contents of the site

PWAs can access any device features available to HTML5 websites, which includes push notifications, bluetooth and GPS, but cannot access contacts, NFC or SMS.

They have some advantages over native apps in terms of size, not requiring an app store (you just to go the website and install it directly) and they are relatively cheap in that if you already have a website its some tweaks to that rather than building something new.

The downsides, however, are huge. A PWA is still a website and will behave as such, no native controls or native performance, some device features are still not supported (although that is slowly changing) and you also lose out on being on each platforms app store which can be a massive issue as that’s where many people will automatically look for your app. Some claim that PWAs will replace the need for app stores but thats a long way off even if it is true.

React Native

React Native is a Javascript framework based on the popular React Web platform. It was created by Facebook who had previously been entirely web focussed and wanted to make inroads into the native world. It powers apps such as Skype, Instagram and Uber Eats.

React Native, like Cordova, is Javascript based, but rather than rendering to a web view it uses the native APIs to render true native objects. Let’s take a look at how RN does this.

React Native Bridge

iOS

Android

Images borrowed from this article which is also well worth a read: https://acodingdance.io/understanding-the-react-native-bridge-concept/

React Native runs Javascript on a background thread which generates a DOM layout. Rather than this being rendered by the browser, differences in the DOM are submitted to the React Native bridge on the main UI thread. 

These changes can be batched which allows for efficient updates. The bridge then uses native interfaces to communicate with the native code, which then runs as if it had been written in the native language.

This bridge is written in C++ which makes it portable and extremely performant.

Other features of React Native include: 

Yoga: A custom layout engine that implements a flavour of Flexbox, which will be familiar to many web developers. This allows a much simpler layout syntax than those used in either iOS or Android.

JSX: Screen elements are defined using JSX markup inline with code rather than in separate storyboards or XML files. This allows you to group your UX logic together as you see fit rather than as dictated by the framework.

True Native Controls: All native controls are actually that, not something styled to look like a native control, which means that users see what they are used to seeing and the controls behave as you’d expect. 

Cat Fetcher

In order to attempt to demonstrate the advantages of cross platform tech, here’s a quick demo from my talk. It should be stressed that this is certainly not the cleanest or best way of writing however it is fairly representative of what can be achieved.

Cat authors own: Leia… . as in Princess

My Cat Fetcher app performs a set of basic functions which many apps are expected to implement.

  • A UX control in the shape of a button

  • A network request to an external web resource

  • Parsing of the response from said network call (JSON response)

  • Fetching and rendering an image from the internet

First came iOS written in Swift along with a storyboard (for the UX layout).

This came out at about 51 lines of code for the main controller and the bulk of the hand-written code, plus the storyboard file.

//
//  ViewController.swift
//  catfetcher
//
//  Created by David Cumming on 7/7/19.
//  Copyright © 2019 davecumming. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var label: UILabel!
    
    struct Cat: Decodable {
        let url: String
        let breeds : [String]
        let width : Int
        let height : Int
    }
    
    @IBAction func buttonPressed(_ sender: Any) {
        guard let url = URL(string:"https://api.thecatapi.com/v1/images/search")
            else {
                return
        }

        let session = URLSession.shared
        let urlRequest = URLRequest(url: url)

        let task = session.dataTask(with: urlRequest, completionHandler: {
            (data, response, error) in

            guard let responseData = data else {
                print("Error: did not receive data")
                return
            }
            if let cats = try? JSONDecoder().decode([Cat].self, from: responseData){
                let url = URL(string: cats[0].url)
                let data = try? Data(contentsOf: url!)
                DispatchQueue.main.async{
                    self.label.text = cats[0].url;
                    self.imageView.image = UIImage(data:data!)
                }
            }
        })
        task.resume()

    }
}

Next, I converted this to an Android app written in Java.

This came out at about 107 lines of code plus the XML file for the layout.

package au.com.davecumming.myapplication;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.android.volley.Cache;
import com.android.volley.Network;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.JsonArrayRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

public class MainActivity extends AppCompatActivity {

    private class SampleAsync extends AsyncTask<String, Void, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... params) {
            String catUrl = params[0];
            URL imageURL = null;
            try {
                imageURL = new URL(catUrl);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }

            Bitmap bmp = null;
            try {
                bmp = BitmapFactory.decodeStream(imageURL.openConnection().getInputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }

            return bmp;
        }

        @Override
        protected void onPostExecute(Bitmap bmp) {
            super.onPostExecute(bmp);
            imageView.setImageBitmap(bmp);
        }
    }

    private void sendRequest(){
        String url = "https://api.thecatapi.com/v1/images/search";

        JsonArrayRequest jsonObjectRequest = new JsonArrayRequest
                (Request.Method.GET, url, null, new Response.Listener<JSONArray>(){

                    @Override
                    public void onResponse(JSONArray response) {
                        try {
                            JSONObject cat = response.getJSONObject(0);
                            String catURL = cat.getString("url");
                            textView.setText(catURL);
                            new SampleAsync().execute(catURL);
                        } catch (JSONException e) {
                            e.printStackTrace();}
                        catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                    }
                });
        requestQueue.add(jsonObjectRequest);
    }
    RequestQueue requestQueue;
    ImageView imageView;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getSupportActionBar().hide();



        Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap
        Network network = new BasicNetwork(new HurlStack());
        requestQueue = new RequestQueue(cache, network);
        requestQueue.start();
        setContentView(R.layout.activity_main);
        final Button buttonView = findViewById(R.id.button);
        imageView = findViewById(R.id.image);
        textView = findViewById(R.id.label);
        buttonView.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                sendRequest();
            }
        });
    }

}

Last, I built the same app in React Native, and here’s what that looks like.

import React, {Fragment} from 'react';
import {
  SafeAreaView,
  View,
  Text,
  Image,
  StatusBar,
  Button,
  
} from 'react-native';

export default class App extends React.Component {

  _fetchCat = () => {
    return  fetch ("https://api.thecatapi.com/v1/images/search")
    .then(response => response.json())
    .then((responseJson) => {
      this.setState({
        catUrl : responseJson[0].url
      })
      return responseJson.cat
    })
    .catch((error) => {
    });

  }

  constructor(props){
    super(props)

    this.state = {
      catUrl: ""
    }
  }

  render() {
    const catImage = this.state.catUrl !== "" ? 
    <Image style={{width: 300, height: 300, marginTop:30}}
     source={{uri: this.state.catUrl}}/>: <View/>

    return (<Fragment>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={{justifyContent:"center", alignItems:"center"}}>
          <Button  onPress={this._fetchCat}
          title="Fetch cat"
          />
          {catImage}
          <Text >{this.state.catUrl}</Text>
      </SafeAreaView>
    </Fragment>
    )
}};

This comes in at 52 lines of code, which includes the layout. That represents a saving of over 70% which in anyone’s books is a great improvement.

Running the RN implementation for the two platforms involves nothing more than a different command line parameter. There’s no custom code or conditional code (although you could use platform-specific logic if you wanted to), beyond the standard project setup required for each platform (app ids, certs, etc.), which are unavoidable no matter how you’re building your app.

You could argue the above examples could probably have been made more slimline with say Kotlin, for the Android case, or 3rd party libraries, but the basic principle stands. Even if you cut both the native apps down to 50 lines each they are still double the RN code, much of it duplicated which doubles the cost of maintenance and change.

So what does the React Native version look like? Surely it can’t be the same right?

First the Android version…

Aside from minor layout differences, which are simply down to me rushing to meet the talk deadline, it looks near enough the same as its native counterpart.

And then the iOS version….

Same story, same controls, same behaviour, exactly as a user would expect. All this down to the fact, as explained earlier, that it’s rendering native objects.

Now obviously this is an extremely simple example, doing very standard things (in fact, I didn’t have to add any 3rd party libraries at all). Your percentage gain will vary depending on your layout and requirements, but the important part of this is that the React Native code runs on both platforms without platform specific code (outside setting up the base project ids, etc.) 

It’s less than half the code, and I only had to write it once, not figure out how to do the same thing in a completely different way using a completely different toolset.

As well as a time and cost saving, avoiding reimplementing the code twice means there is no chance of variances in business logic and when you need to change things later you only change it once. 

One Codebase ✔️

No Duplication ✔️

Single Language and IDE ✔️

Happy Client ✔️

Answering the questions

Back at the start I said I’d attempt to answer 3 questions so here goes…

What is Cross Platform Development?

Cross Platform Development uses tools or frameworks that reduce the overhead of writing for multiple platforms.

Is it useful?

It can be, if it delivers what you need in terms of look and feel, technologies and performance and in the end provides a time/cost saving. If you require the latest greatest tech, then it may not be right for you, but many day-to-day apps can benefit greatly.

Which solution is for me?

Pure native:

If your app is complex, or requires blistering performance and the latest technology — think games or machine learning — then sticking with native might well be the only route as you may well not get what you need from other solutions.

Partial solutions:

These can obviously give some time/cost saving and have the benefit of retaining access to all the strengths of native development, but you won’t see the savings you can gain from some of the modern frameworks.

PWAs:

If you already have a team of web developers and an existing website, this might be the way to go, but keep in mind that you probably won’t get an app that’s as slick or performant as native apps. You can also lose discoverability and access to the latest hardware innovations.

Leading edge:

Want to stay ahead of the curve? Flutter might well be the way to go. It’s Google’s long term plan for Android and iOS support will continue to grow, but as it stands it’s still a slight risk in that it is very new so lacks the support of some of the more mature frameworks. It also requires learning a new language, which may well be attractive to some developers but for many could be off putting.

Javascript based frameworks:

Another route which can appeal to both existing devs and web developers alike are platforms such as React Native and Cordova. Cordova can be great for anyone familiar with Angular and/or JS in general, whereas React Native is React based with JS. React Native’s standout advantage is that it renders truly native components, unlike Cordova. This is one of the reasons we here at Adapptor favour React Native. We’ve used it on a variety of projects now with great savings in time and cost, and minimal issues with compatibility and performance. That said, it hasn’t been without its growing pains, and when estimating you should allow for unforeseen issues which may not have been there if you’re used to working with native.

Footnote

After my talk a few people stated that I didn’t really commit to one solution, and I stand by that. Although Adapptor widely uses React Native we consider each project individually to decide the best fit, and there are 100% situations where I wouldn’t use RN. Software development isn’t a one size fits all thing and it really depends on your situation: what you’re trying to deliver, your current skill set, and your personal goals. It’d be crazy for me to recommend React Native to a new developer who simply wants to knock up their first application for fun — trust me, figuring out Redux is NOT fun. RN can be a complicated platform to wrap your head around and isn’t without its problems. By the same token, for large scale team development of a cross platform app it can be incredibly useful and quick to develop with.

The one recommendation I will make is do your homework and think about the ramp-up time to learn your chosen platform, any pitfalls that might arise (does it support the tech you need, will it give you the performance you want, etc.) and if you aren’t sure if something will work, prototype it up front. Don’t just dive in making wild claims to your client about cost savings if you aren’t confident you can deliver on it, and that only comes from experience and experimentation.

Whichever solution you go for, good luck and be sure to look out for other blog posts by our awesome team of devs in the future.

Full code for my cat fetcher in all forms can be found here: 
https://bitbucket.org/davecumminghome/

Presentation Slides: 
https://www.dropbox.com/s/ndj5fxrncc3h384/DDD%20NEW%20NO%20NOTES.pdf?dl=0