How I Built an iOS & Android App in a Week

I have a confession to make: although I spent most of last year speaking about mobile and Cordova development, I didn’t have a single app that appeared in the iOS App Store or Google Play. Yes, I’ve worked on a ton of sample and demo apps, but not having something “real” made me feel like a fraud.

So this past holiday I decided to change that. I tasked myself with writing a “real” app in the course of a week, and I somehow managed to pull it off. Now I didn’t succeed because of some great programming prowess — as you’ll see from this article, it’s quite the opposite — but rather, I succeeded because of a stupidly simple app idea, the help of some smart coworkers, and a few pretty useful tools. To help others that have always wanted to build an app, I thought I’d do a write up on how I built the app, the tools that helped me, and what I learned along the way.

The idea

I like geography, but I’m horrible at it. I look up the location of well-known cities on Google Maps often enough that I’m pretty sure its servers are judging me now. Brisbane? Again? Really?

On one of my daily embarrassing trips to Google Maps I had a thought: what if Google Maps left the labels off so I could take a guess at where a place is first? Such guessing games tend to be the only way I memorize things, so I thought it was a useful concept, even if it’s only useful to me.

The idea kept sounding better and better so I ran with it, and City Search—A Geography Challenge was born, or at least the name was. The concept of the app is super simple: you’re asked where a city is, you guess on a map with no labels, and the game tells you how far off you were. That’s it. These screenshots of the final app should help you get the idea:

app-findapp-results

Maps

I was happy with my idea until I realized I actually had to code the thing, which I wasn’t sure I could do. I decided to start with the maps themselves, since they’re kind of the crux of the game, and I wasn’t going to draw them myself.

I started researching map providers for Cordova apps and found an excellent guide Rob Lauer wrote that compares the big players, including instructions on getting started with each. I briefly tried each out each provider, but it became clear right away that only Google Maps had a comprehensive enough API for me to build what I wanted to build.

I ended up being happy with my choice. The Google Maps JavaScript API is really easy to get started with, amazingly thorough, and comprehensively documented. For instance, creating a basic map is as simple as including the API’s <script> tag and calling new google.maps.Map(). Here’s an example that shows a map of North America:

<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY"></script>
<script>
    var map = new google.maps.Map( document.getElementById( "map" ), {
        zoom: 3,
        center: new google.maps.LatLng( 45, -95 ),
    });
</script>

Which looks a little something like this:

north-america

Simple. It’s amazing how many things you can customize too. For my game I needed to turn the labels on and off — off when you’re guessing where a city is, and on when you’re shown the results. This turned out to be really easy. The code below turns the labels off:

map.setOptions({
    styles: [{
        featureType: "all",
        elementType: "labels",
        stylers: [
            { visibility: "off" }
        ]
    }]
});

And as the image below shows, the labels are gone now:

north-america-no-labels

Turning the labels back on again was as simple as calling setOptions() again with an empty styles array. In the end I spent about two hours verifying that Google Maps could do everything I needed it to do (and it could), and then I had to head back to the Internet to find the next thing I needed: data.

Data

To build a game that hunts for cities you need a big list of cities and their locations. This isn’t something Google Maps provides, and I wasn’t about to compile a big list myself, so I started scouring the web for the data I needed to drive the game. Eventually I stumbled on GeoNames, which provides a Creative Commons licensed database containing a whole bunch of geographical data. For my purposes, I was interested in the text file with a list of all cities that have a population of over 15,000; it was exactly what I needed.

But I soon realized I had a problem if I actually wanted to use this data. The file was tab-delimited data, and I needed data in a format I could consume in my JavaScript app—i.e. JSON. To give you an idea of what I was up against, here’s an excerpt of the data for two cities from the text file:

4684724 Cypress Cypress     29.96911    -95.69717   P   PPL US      TX  201         46025   46  51  America/Chicago 2011-07-28
4684888 Dallas  Dallas      32.78306    -96.80667   P   PPLA2   US      TX  113         1197816 128 139 America/Chicago 2014-10-27

In terms of a relational database, tabs represented columns and each line represented a row. This is fine, I guess, except I wanted the data to look like this:

[
    { "name": "Cypress", "latitude": 29.96911, "longitude": -95.69717 },
    { "name": "Dallas", "latitude": 32.78306, "longitude": -96.80667 }
]

Now, I feel like a more adept programmer would’ve wrote a Perl script that does this in 30 seconds, or a Node script that does it in 2 minutes, and then go back to working on the Linux kernel or something, but that adept programmer isn’t me. After a few minutes of being depressed at my ineptitude, I hacked something together using the only skill set I actually do feel adept at: web technologies. Here’s how I converted this text file into JSON:

<script>
    return $.get( "cities.txt" ).then(function( data ) {
        var rawCitiesData = data.split( "\n" ),
            cities = [];

        for ( var i = 0; i < rawCitiesData.length; i++ ) {
            var rawCityData = rawCitiesData[ i ].split( "\t" ),
                city = {
                    "name":         rawCityData[ 1 ],
                    "latitude":     rawCityData[ 4 ],
                    "longitude":    rawCityData[ 5 ],
                    "country_code": rawCityData[ 8 ],
                    "population":   rawCityData[ 14 ]
                }

            // Only use cities that have at least 200,000 people
            if ( city.population > 200000 ) {
                cities.push( city );
            }
        }

        console.log( cities );
    });
</script>

I saved this this code as an index.html file, opened it in a browser, manually copied and pasted from the log into a JavaScript file, and then included that file in my app.

Is this the best way of solving this problem? No. Am I embarrassed to share this? A little. But did I get the city data I needed to build my app? Yes.

Maybe next year I’ll teach myself enough Node to parse the input file properly, but until then, on to the next problem!

Frameworks

At this point I had my map APIs and I had the data I needed. The last thing I needed was to put it all together into a functioning game. I considered whether I needed a hybrid library like Kendo UI Mobile or Ionic, or an MVC solution like Backbone, Angular or Ember, but at the end of the day I decided I didn’t need anything that complex for such a simple game.

The game has three states—a welcome screen, a search screen, and a results screen—and I decided to manage the visibility of the appropriate DOM elements with some simple CSS class names. I’ll explain with a bit of code. Here’s the basic markup the game uses:

<body class="welcome">
    <div id="map"></div>
    <div class="welcome">...</div>
    <div class="search">...</div>
    <div class="results">...</div>
</div>

The map <div> contains the map, which displays all the time, and the welcome, search, and results <div>s display conditionally depending on the state of the app. I chose to drive the display of these elements from a class name defined on the <body>. Here’s the CSS I used:

body .welcome,
body .search,
body .results {
    display: none;
}
body.welcome .welcome,
body.search .search,
body.results .results {
    display: block;
}

Basically, all non-map children of the<body> are hidden, unless their class name matches the one on the <body>. This class name approach to determining visibility is something I’ve used before and found it works well. Yes, Angular has things like ng-show/ng-hide, Kendo UI has visibility bindings, and I’m sure Ember/Backbone/React/whatever have something similar, but remember this app is all about simplicity, and with this CSS in place I could manage the game state by toggling a few class names in JavaScript. I wrote the following function to handle that:

function setGameState( state ) {
    $( "body" )
        .removeClass( "welcome search results" )
        .addClass( state );
}

Yes I used jQuery, and I didn’t necessarily have to. I could’ve used the classList API; it is really well supported at this point, but I still find jQuery’s syntax easier to use for most tasks. I also didn’t have to worry about weird edge cases—like the fact that IE and Safari still don’t let you add/remove multiple classes with a single call to classList.add() or classList.remove().

Besides jQuery I included two other libraries: FastClick to workaround the 300 ms click delay, and a library that helped me calculate the distance between two latitude/longitude points, because I certainly didn’t want to do the necessary trigonometry myself. It’s a testament to the open source JavaScript community that you can do a Google search for some nuanced trigonometric functionality and find a liberally-licensed library that works great.

Cordova

The app itself ended up being around 150 lines of JavaScript, ~70 lines of CSS, and ~60 lines of HTML; I told you it was simple. I did the actual development using the Telerik AppBuilder CLI, and spent most of my time in the AppBuilder simulator ($ appbuilder simulate).

The AppBuilder simulator automatically watches your project for code updates and refreshes the simulated device on every save, which was super helpful during development—especially when tinkering with map variables (as I show in the gif below).

simulator-and-map

I also found AppBuilder’s mocking of Cordova events and core Cordova plugins useful, as my app uses most of the Cordova events, and a few of the core plugins, specifically StatusBar, Device, and InAppBrowser. To give you an idea of how the mocking works, here’s a gif of how AppBuilder mocks my InAppBrowser plugin call (which my app uses to show Wikipedia content):

in-app-browser-demo

Once I had my app working in the simulator I used AppBuilder to build app packages — .ipa and .apk files — and to push them out to my iOS and Android devices ($ appbuilder deploy android; appbuilder deploy ios). Usually I find a few differences between the simulator and physical devices (a simulator is just a simulator after all), but because this app was so simple I only had to make a few small tweaks to the CSS.

I thought I was nearly done; little did I know I wasn’t close.

Icons and splash screens

Developing a mobile app requires a startling amount of work that isn’t related to coding at all. I spent more time tinkering with configuration files, certificates, and screenshots than I did coding — by a lot. I got through it, so I thought I’d share some tools that helped me out.

My first task was to create a set of icons and splash screens for my app. Since I have the design talent of your average four-year-old with a box of crayons (and I have twin four-year-olds, so I would know), I had no idea where to start.

Luckily my colleague Jen Looper pointed me at VectorStock, which provides a number of appropriately licensed icon and graphic sets for a buck or two apiece (although they do make you buy $25 worth of credits up front). I found an image set that had the two images below, which were perfect for my icon and splash screen requirements.

icon
splash-screen

From previous experience I knew that iOS and Android each have crazy naming schemes and dimension requirements for these files, but I also knew that the AppBuilder browser client has an uploader that can handle all of this nonsense for me.

So I headed to the Properties section in the AppBuilder browser client (which you can read about how to get to here), took the highest quality square icon I had (500×500), uploaded it for each and every iOS and Android icon, and let AppBuilder take care of the naming and resizing.

ios-icons
iOS and Android require a ton of icons but they’re all squares. I used the same icon for each and let AppBuilder handle the resizing and naming conventions for me.

Splash screens are harder, as iOS requires orientation-specific files (landscape and portrait), so it’s not something that you can easily generate. I ended up creating two images manually, one landscape and one portrait, which took far more time than I care to admit here. I then took those two files and went back to AppBuilder.

I uploaded my portrait image for any slot where the height was bigger than the width, and my landscape image for the others. Again I let AppBuilder handle the naming conventions and resizing to the appropriate dimensions. Since the height to width ratio isn’t the same for all the required images I had a few splash screens that were a bit stretched, but they didn’t look that bad to me.

ios-splash-screens

With icons and splash screens behind me I thought I was finally ready to deploy this thing. I performed a release build of my iOS and Android apps ($ appbuilder build ios --release; appbuilder build android --release). I tested them out. I got my hopes up…and then I met iTunes Connect.

App Store deployment

iTunes Connect is evil. Ok maybe not evil, but it does make you learn a bunch of things you have no interest in learning just to submit an app. After you perform the initial registration of your app, you’re greeted with an overwhelming set of forms, menus and options, the first of which is a section called “App Video Preview and Screenshots”, shown below:

itunes-connect

See the 4.7-inch, 5.5-inch, 4-inch, 3.5-inch, and iPad buttons? Those sections are for uploading appropriately sized screenshots for each of those devices. And they’re all required, meaning you have to submit at least one, and up to five, screenshots for each screen size. Plus, there are required (yet seemingly undocumented?) dimensions, just to add to the fun.

I got some more coffee, then I set out to create these things. I found a helpful blog post that lists the exact dimensions each device size requires. Next I again consulted my colleague Jen.

Per her advice, I took five screenshots of my app using my personal iPhone and iPad. I then used Preview on OS X create copies of my iPhone screenshots that were the appropriate, Apple-appeasing dimensions. (Here’s a guide on how to resize images with Preview in case you were curious.) The resized images weren’t perfect, as I had to upscale images from my iPhone 5 to work for the iPhone 6 and 6 Plus screenshots, but I felt they looked fine for my purposes.

For those of you without iDevices, another popular strategy is to use the iOS simulator to take the screenshots you need. The AppBuilder Windows Client also has a way to take screenshots for those of you on Windows.

You don’t have to use exact screenshots for your apps, and in fact many “professional” app developers use the screenshots to market their app and attract downloads. Jen maintains a repo with the templates she uses to generate her’s, which in my opinion turn out really nice:

giftler

For the really brave, Jen also has an excellent article on how to create the app preview videos that iTunes Connect now lets you add. Note that you’ll need OS X Yosemite, Safari, and more patience and determination than I have.

The rest of my iOS experiences were tedious, but significantly less soul crushing than dealing with screenshots. I used AppBuilder to upload my app (the actual .ipa) to iTunes Connect so that I could avoid using OS X’s Application Loader. I specifically used the $ appbuilder appstore upload command, for those of you that are curious. I submitted my app for review, and ten (yes ten) days later my app was live! Here’s the requisite logo if you want to check out the app for yourself:


ios-app-store

One platform down and one to go.

Google Play Deployment

Google Play wasn’t nearly as bad, although I again had to deal with screenshot hell. Like Apple, Google requires screenshots for a variety of device types, but Google, of course, has different devices it requires images for — specifically a “Phone”, a “7-inch tablet”, a “10-inch tablet”, and of all things, a “TV”. Here’s what their upload screen looks like:

google-play-screenshots

At this point my patience had reached critically low levels. I had just dealt with iTunes Connect, so my grip on reality was already slipping; therefore I decided to take the path of least resistance.

I took my iPhone and iPad screenshots from before, edited them in Microsoft Paint (yes, Microsoft Paint) to remove the iOS status bar—aka the only thing in the app that gives away its running on iOS—and uploaded them. My iPhone was a “Phone” as well as a “7-inch tablet”, and my iPad was a “10-inch tablet” as well as a “TV,” because why not. For whatever reason Google accepted these screenshots choices so I didn’t ask any questions; I was ready to be done. Then I scrolled down.

google-play-graphics

Seriously? A Hi-res icon, featured graphic, promo graphic, TV banner, and promo video? Who has the time to create all of these things?

But before I panicked I noticed that the Promo Graphic, TV Banner, and Promo Video were optional, so I ignored those, and the other two slots turned out to be easy. I had a 512×512 icon from VectorStock, and I was able to resize my landscape splash screen to work as a feature graphic. Not bad.

And the rest of the Google Play processes weren’t bad either. More forms and configuration, but nothing too horrible. I uploaded my AppBuilder-generated .apk, hit submit, and a few hours later my app was in Google Play:


google-play

Wrapping up

If you’re curious, my app’s full source code is available on GitHub for perusing, forking, or whatever. If you’d like to run it in AppBuilder all you need to do is:

$ git clone https://github.com/tjvantoll/city-search-challenge.git
$ cd city-search-challenge/app
$ appbuilder simulate

In the end I had thrown together a simple, but functional app, and deployed it to two app stores in the matter of a few days. Is the app spectacular? No. Does anyone actually use it? No. (I’m using the Telerik Analytics plugin, so I know for sure.) But it’s still really cool to have an app in the stores, and I’m glad I did it, if only for the learning experience.

Although I joke about how absurd the iOS and Android deployment processes are — and they are — at the end of the day deploying an app is just another skill set to learn. Once you’ve gone through the processes one time, they don’t seem nearly as bad.

I’m a Telerik employee and obviously biased, but I found AppBuilder invaluable for dealing with a bunch of things that I didn’t want to: icons, splash screens, configuration files, plugins, and such. I never had to touch a manifest.xml or Info.plist file, or even care that they exist. The Telerik Platform made it easy to add analytics to my app, and I plan on giving the new Device Cloud a shot later this week for some automated testing.

If there’s a moral to this article, it’s that anyone with a little web development experience can throw a Cordova app together. I stumbled through the process with some sloppy jQuery code, Microsoft Paint, and sheer determination. If you’ve always wanted to build the next great mobile app, or just build an app to impress your friends, now is a great time to give it a shot.

Comments

  • Marcos Alejandro Gallardo

    Hi TJ,

    You could used “google.maps.geometry.spherical” for calculate distance between two LatLngs instead.
    To get access to this API you only need to add one additional param to Google Map API’s script:

    Regards!

    • Thanks Marcos!

      I had no idea that existed and it cleaned up my code substantially https://github.com/tjvantoll/city-search-challenge/commit/5890f023f323dbe39116c0628ff8aa5c4a838915.

    • I often don’t want to include a whole library just for one function and also often don’t care about a 0.5% loss in accuracy, so I consider the Earth a sphere and use the Haversine formula:

      var distanceOnSphere = function (lat1, lon1, lat2, lon2) {
      “use strict”;

      var R = 6371,
      p1 = lat1.toRadians(),
      p2 = lat2.toRadians(),
      dp = (lat2 – lat1).toRadians(),
      dl = (lon2 – lon1).toRadians(),
      a = Math.sin(dp / 2) * Math.sin(dp / 2) +
      Math.cos(p1) * Math.cos(p2) *
      Math.sin(dl / 2) * Math.sin(dl / 2),
      c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 – a));

      return R * c;
      };

      distanceOnSphere(59.4372, 24.7453, 58.3833, 26.7167);

      • Marcos Alejandro Gallardo

        You have a pro and a con in your approach, I think that depends on what we will want to sacrifice.
        I always tend to use 3rd party libs and for me when they are less, better.

  • Pingback: Dew Drop – January 29, 2015 (#1942) | Morning Dew()

  • Pingback: Adding Telerik Analytics to Cordova Apps -Telerik Developer Network()