Toggle:



Why and how I use PhoneGap



Soy Pamela Fox. Hace un año, yo me fui de Google, donde trabajaba en Developer Relations. Quisé desarrollar una aplicación completa por mi misma, para descubrir como es ser un desarollador, para conocer lo bueno y lo malo de la profesión. Y si aprendí mucho.

I'm Pamela Fox. A year ago, I left Google, where I was working in Developer Relations. I set a goal for myself to develop a complete app by myself - partly to discover what it's like to be a developer in the wild, to find out about the good and the bad parts.

The web app

Decidí programar una aplicación para el seguimiento de la nutrición, porque necesitaba cambiar mi nutrición después de tantos años en Google, comiendo todo lo que me daban sin pensar. Al principio, la aplicación estuvo solamente disponible en la web. Los usuarios se registraban en la web, y cada noche, llenaban un formulario para informar sobre lo que comían.

I decided to make an app for nutrition tracking, because I needed to change my own nutrition after so many years at Google, eating whatever they gave me without questioning. At the beginning, the app was web only - users signed up online and each night, filled out a form to report how they ate.

The mobile app?

Me dí cuenta que sería mejor si los usuarios pudieran usar el móvil para sacar fotos de sus comidas. Pero nunca había desarrollado una aplicación móvil, y cuando hago algo por primera vez, yo siempre investigo las opciónes. Lo que descubrí es que hay muchas posibilidades y tenía que decidir entre todas ellas. Hoy, voy a compartir esas opciónes con ustedes y cuál fue mi decisión.

I realized that it would be better if users could use their phone to take photos of their meals. But I'd never made a mobile app before, and whenever I do something for the first time, I always investigate the options. What I discovered is that there are many options, and I had to decide between them. I'm going to share those options and my decisions in this talk, to help you in your own decision making process.

How to decide?



Your app:

  • How many platforms do you want to support?
  • How important is performance?
  • How important is the user experience?
  • Which hardware APIs do you need access to?

Your team:

  • How many codebases do you want to have?
  • What languages/technologies do you know?
  • How much money do you want to spend?

Antes de considerar las opciónes, éstas son las preguntas que deben hacerse - preguntas sobre su aplicación y preguntas sobre su equipo.

Before considering the options for making a mobile app, these are the questions I think you should ask yourself - both about your app and your team.

How to decide?

My app:

  • How many platforms do you want to support? 2
  • How important is performance? Eh
  • How important is the user experience? Sort of
  • Which hardware APIs do you need access to? Camera

My team: (me)

  • How many codebases do you want to have? 1
  • What languages/technologies do you know? Web/Python
  • How much money do you want to spend? $0

La primera es: para cuantas plataformas quieren desarrollar? Quería desarrollar para dos plataformas, Android y iPhone, porque tengo usuarios en las dos. Y más importante, yo uso un Android mientras que mi novio usa un iPhone, y quería programar una aplicación que nos funcionase a los dos. Mi aplicación tiene un aspecto social - se puede compartir sus registros diarios - y cuando una aplicación es social, es importante que funcione en múltiples plataformas. Existen muchas parejas “cross-plataformas”, como mi novio y yo.

Luego: Es el rendimiento importante para su aplicación? Algunas aplicaciónes necesitan usar mucho procesamiento del móvil, como un juego de tres dimensiones. Mi aplicación es simple: el usuario lo usa para escribir texto y subir fotos. Así que el rendimiento no era un aspecto tan importante para mi aplicación.

Es importante una buena experiencia con el usuario? Para algunas aplicaciónes, la experiencia del usuario y el diseño es lo más importante, y eso es el factor diferenciador. Para mi aplicación, quería una buena experencia, pero me falta un diseñador y sabia que el diseño no sería lo mejor del mundo. Al menos, quería una aplicación usable.

Que APIs de hardware necesita la aplicación? Los móviles ofrecen varias APIs de hardware, como el accelerometer, la ubicacion, la brujula, la cámara, las notificaciones, y tambien ofrecen APIs para acceder a datos como los contactos y el sistema de archivos. Mi aplicación solo necesitaba acceso a la API de la cámara.

Ahora que saben los requisitos de su aplicación, tienen que hacerse preguntas sobre su equipo.

Cuantas bases de código quieren? Por cada base de código, se necesita alguien que puede comprenderla, mantenerla, y añadir mas funciones. Ya que mi equipo era solo yo, quería tener solamente una base de código.

Qué tecnologias conocen o quieren saber? Se necesita tiempo para aprender nuevas tecnologias, y tal vez no tienen ese tiempo. Para mi, ya sabía Python, HTML y JavaScript, ya estaba trabajando con esas tecnologias, y no quería pasar tiempo en aprender tecnologias nuevas, si no era necessario.

How many platforms do you want to develop for? I mostly wanted to target two platforms, Android and iPhone, because I have users on both. And more importantly, I use Android while my boyfriend uses iPhone, and I wanted something that would work for both of us. My app has a social aspect to it - you can share updates with friends - and whenever an app is social, its important that it works in multiple platforms, because there are so many "cross-platform" relationships.

Is performance important for your app? Some apps need to use a lot of mobile processing power, like a 3d game. My app idea was simple: the user enters text and uploads photos. So performance was not a big consideration for me.

Is user experience important? For some apps, user experience and UI design are the most important - they're the differentiating factor for an app. For my app, I wanted a good experience, but I didn't have a designer and I knew whatever I came up with wouldn't be the most amazing in the world, so I would settle for something usable.

What hardware APIs does the app need? Mobil phones offer various hardware APIs, like the accelerometer, location, camera, notifications, and they also offer APIs to access data like contacts and the file system. My app only needed access to the camera API.

How many code bases do you want? For every code base, someone has to understand it, maintain it, and add more features to it. Since my team was only me, I only wanted to worry about one codebase.

What technologies do you know or want to know? It takes time to learn new technologies, and sometimes you don't have that time. For me, I already knew and was working with Python, HTML, and JavaScript, and I didn't want to spend time learning new technologies if I didn't have to.

Native app

Platform Language IDE
iOS Objective C XCode
Android Java Eclipse
Windows .NET Visual Studio
BlackberryJava BB JDE
hpWebOS C/C++/HTML5 ?

Okay, ahora que sabemos las respuestas a esas preguntas, podemos explorar las posibilidades. Para empezar, la opción mas obvia es desarrollar una aplicación nativa, usando el SDK y el idioma de la plataforma. Para iOS, es una combinación de Objective-C, iOS SDK y XCode. La sintaxis de Objective-C me parece un poco loca y le tengo miedo. Para Android, es una combinación de Java, el Android SDK, y Eclipse. Java es el idioma que aprendí cuando era niña, porque le encantaba a mi padre, pero me rebelé contra él y hoy en dia, casi nunca uso Java. Al desarrollar una aplicación nativa, hay que usar diferentes tecnologías y bases de código para cada plataforma - pero la aplicación tiene el mejor rendimiento, el potencial de la mejor experiencia del usuario, y acceso a todas las APIs.

Now that we know the answers to those questions, we can explore the options. To start, there's the obvious option: make a native app, using the SDK and the language of the platform. For iOS, it's a combination of Objective-C, the iOS SDK, and XCode. For Android, it's a combination of Java, the Android SDK, and usually Eclipse. When you make a native app, you have to use different tech and have different code bases for every platform - but on the plus side, the app has the best performance, greatest potential UX, and access to all the APIs.

Not for me

Yo, decidí no programar una aplicación nativa, porque no quería aprender tantas nuevas tecnologías y preocuparme en mantener una base de código para cada plataforma.

I know C/C++ from college, but I'd never worked with Objective-C and it has a rather intimidating syntax to newbies. I know Java from when I was a kid and my dad guilted me into programming with it, but then I rebeled against him and generally avoid it these days. So, yes, I considered learning those platforms, but then I decided that I wasn't interested in becoming an expert in so many new technologies, and more importantly, I didn't want to worry about maintaining a code base for each platform.

Cross-Compiled code

iOS Android WP7 BB WebOS
Titanium JavaScript 0-$500-$$/Apache
ParticleCode Java, AS3 Free
XMLVM Java, .NET Free/LGPL
Monotouch C#, .NET $400-$3000/OS

Otra opción es usar un SDK de abstracción que se compila en una aplicación nativa por cada plataforma. Por ejemplo, Titanium es un JavaScript SDK que se compila en código nativo para iOS y Android. Con esta opción, uno puede tener solamente una base de código para todas las plataformas y tambien puede tener buen rendimiento y experiencia de usuario.

Another option is to use an abstracted SDK that cross-compiles into native code for each platform. For example, Titanium is a popular JavaScript SDK that compiles into native code for iOS and Android. With this option, you can have just one code base for all the platforms, and you can also have great performance and a native user experience.

Not for me

Pero no era la opción para mi. Traté de usar Titanium, y me gustó usar un idioma que ya sabía, pero encontré muy dificil diseñar la interfaz usuario. Estoy acostumbrada a diseñar las interfaces con HTML y CSS, y me tomó demasiado tiempo aprender una nueva manera.

But this wasn't the option for me. I tried using Titanium once, and I appreciated that I could use a language that I already knew, but I found it difficult to design the user interface. I'm accustomed to making interfaces with HTML and CSS, and it was taking too much time to learn a new way. Plus, I wouldn't be able to re-use as much code from my existing web app if I went the Titanium route.

Hybrid app

Finalmente, la opción que yo escogí: aplicaciónes hibridas. Hoy en dia, la mayoria de los telefonos tienen navegadores de web, y las aplicaciónes nativas siempre tienen una forma de incrustar un navegador al dentro de la aplicación. Entonces, es posible desarollar una “aplicación hibrida” con una combinación de una pagina de web, envuelto en una aplicación nativa delgada, y la pagina de web puede comunicar con la aplicación nativa sobre un puente de JavaScript y código nativo. Con esta estrategia, se puede programar una aplicación que funciona en multiples plataformas, siempre que exista un puente para la plataforma. El rendimiento y experiencia del usuario depende del rendimiento y experiencia del navegador, y a menudo, no es tan buena como una aplicación nativa.

Finally, the option I chose: hybrid apps. These days, most smart phones come with web browsers, and their native apps have ways of embedding web browsers inside of them. So, that makes it possible to develop a "hybrid app", a webpage that's wrapped in a thin native app, and that communicates with native code via a JavaScript bridge. With this option, you can make an app that works on multiple platforms with mostly the same codebase, as long as there's a bridge for that platform. However, the performance and user experience are dependent on the performance and user experience of the embedded web browser, and often, that means its not as good as of a native app.

Bridge SDK

iOS Android WP7 BB WebOS
(Adobe) PhoneGap Free/Apache
appMobi Free/MIT
trigger.io $600-$3000
<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
    <script type="text/javascript" charset="utf-8">
    function capturePhoto() {
      navigator.camera.getPicture(onPhotoDataSuccess);
    }

    function onPhotoDataSuccess(imageData) {
      var smallImage = document.getElementById('smallImage');
      smallImage.src = "data:image/jpeg;base64," + imageData;
    }
    </script>
  </head>
  <body>
    <button onclick="capturePhoto();">Capture Photo</button> <br>
  </body>
</html>
  

Hay algunos SDKs que se pueden usar para programar aplicaciónes hibridas. Adobe PhoneGap y AppMobi son dos SDKs de código abierto, y trigger.io es un startup. En cuanto a su funcionalidad, son muy similares. Este ejemplo demuestra el uso de PhoneGap. La pagina incluye un script tag para el puente JavaScript, y cuando el usuario hace click en el botón, la pagina hace una llamada al puente JavaScript para sacar una foto, y procesa el resultado.

There are a few SDKs that make it easy to write hybrid apps. Adobe PhoneGap and AppMobi are two open-source SDKs, and trigger.io is a startup. In terms of their functionality, they're quite similar. This example shows PhoneGap. The webpage includes the script tag for the JS bridge, and when the user clicks the button, the page makes a call the JS bridge to take a photo and then processes the result.

Works for me!

Escogí desarollar mi aplicación hibrida con PhoneGap, porque me permite re-usar la tecnologia que ya uso (HTML, CSS, y JavaScript) y tambien me permite re-usar el código de mi aplicación de web.

I chose to develop a hybrid app, and I went with the PhoneGap SDK. It seemed like the right option for this job, as it let me re-use the web technology stack I already use and it lets me re-use my web app code.

Web App

Pero el proceso de decisión no era completo. Una aplicación hibrida contiene una aplicación de web dentro de ella, y tenía que decidir como desarollar esa aplicación de web. Una aplicación se compone de la interfaz, la interactividad, y los datos, y hay muchas maneras de componerlos, con frameworks or bibliotecas.

But the decision making process wasn't complete. A hybrid app contains a webapp inside of it, and I had to decide how to make that web app (and make it mobile-friendly). A webapp consists of the interface, interactivity, and data, and there are many ways of putting those together, often using frameworks or libraries.

Web Frameworks

Hay dos tipos de framework: programatico, donde el desarollador escribe JavaScript, y el framework lo convierte a CSS y HTML para programar la interfaz interactiva, y declarativo, donde el desarollador escribe el HTML y el framework lo combina con CSS y JS para programar la interfaz interactiva.

There are two types of frameworks (the way I see it): programmatic, where the developer writes JS and the framework converts that to CSS and HTML to make the interactive interface, and declarative, where the developer writes HTML and the frameworks combines it with CSS and JS to make the interactive interface.

Programmatic Frameworks

Aqui hay algunos frameworks programaticos populares que están especializados en la interfaz movil: Sencha, Jo, GWTMobile.

Here are some popular programmatic frameworks that are specially designed for mobile interfaces.

Sencha Example

<html>
  <head>
    <script src="lib/touch/sencha-touch.js"></script>
    <link href="lib/touch/resources/css/sencha-touch.css" rel="stylesheet" type="text/css" />
    <script>
new Ext.Application({
  launch: function() {
    new Ext.Panel({
      fullscreen: true,
      dockedItems: [{xtype:'toolbar', title:'My First App'}]
    });
  }
});
var formBase = {
    url   : 'postUser.php',
    items: [{
            xtype: 'fieldset',
            title: 'Personal Info',
            instructions: 'Please enter the information above.',
            items: [
            {
                xtype: 'textfield',
                name : 'name',
                label: 'Name',
            }, {
                xtype: 'passwordfield',
                name : 'password',
                label: 'Password',
                useClearIcon: false
            }
    ]
};
var form = new Ext.form.FormPanel(formBase);
form.show();
    </script>
  </head>
</html>

Este ejemplo demuestra el uso de Sencha. La pagina incluye el JS y CSS para Sencha, y hace llamadas a la API de Sencha para desarrollar una aplicación con un Formulario. No hay ningun HTML en la pagina - ni siquiera una etiqueta body!

This example shows Sencha. The page includes the JS and CSS for Sencha and makes calls to the Sencha API to make an Application with a Form. Notice there's no HTML in the page - not even a body tag!

Not for me

No me gusta usar un framework programatico porque prefiero escribir HTML y despues decorar con JavaScript. Es mas flexible, no tengo que preocuparme si siempre tendré que usar el mismo framework.

I'm not a big fan of programmatic frameworks for web apps - I think it's generally better to write HTML and then afterwards, decorate it with JavaScript. It's more flexible in that it's easier to change frameworks later (if I decided to change from Sencha to another framework, I'd be rewriting the code from scratch) and it's more the way the web was designed to work.

Declarative Frameworks

Aqui hay algunos frameworks declarativos populares que están especializados en la interfaz movil: jQueryMobile, KendoUI, y Wink.

Here are some popular declarative frameworks that are specially designed for mobile interfaces.

jQueryMobile Example

<html> 
<head> 
 <link href="jquery.mobile/jquery.mobile.min.css" rel="stylesheet" type="text/css" /> 
 <script src="jquery.mobile/jquery-1.6.4.min.js"></script> 
 <script src="jquery.mobile/jquery.mobile.min.js"></script> 
</head> 
<body>
<div data-role="page">
  <div data-role="header" data-theme="a"> 
    <h1>SGCE</h1> 
  </div>

  <div data-role="content"> 
    <p>La mejor conferencia en México.</p> 
    <button>Registrarse ahora</button>
  </div>
</div>
</body> 
</html>
    

Este ejemplo demuestra el uso de jQueryMobile. La pagina incluye el JS y CSS para jQuery Mobile, y en el body, incluye HTML tags con atributos de dato. Estos atributos de dato dan información a jQuery mobile - ese div es una pagina, ese div es el header, etc.

This example shows jQueryMobile. The page includes the JS and CSS for jQueryMobile, and in the body, it includes HTML tags with data attributes. These data attributes give information to jQueryMobile, like which div is the a page and which is the header.

Not for me

Para la primera version de mi aplicación movil, yo use jQuery Mobile. Funciono suficientemente bien para una primera version, pero encontré demasiado dificil customizarlo. Se puede leer mas en este blog post.

For the first version of my mobile app, I used jQueryMobile. It worked well enough for a first version, but once I wanted to do more, I found it hard to customize.

CSS Frameworks

Si no quieren usar un framework grande como esos, pueden combinar frameworks de CSS con bibliotecas de JS. Aqui pueden ver algunos frameworks de CSS populares. Los dos son originalmente para aplicaciónes de web, pero contienen media queries para que adapten a una aplicación movil.

Besides using an all-inclusive framework like those, you can also combine CSS frameworks with JS libraries. Here are two popular CSS frameworks. Both were originally designed for desktop web, but they have some mobile-friendly adaptations as well.

Bootstrap Example

<html>
 <head>
  <link href="bootstrap.css" rel="stylesheet" type="text/css">
  <link href="bootstrap-responsive.css" rel="stylesheet" type="text/css">
 </head>
<body>
  <div class="navbar">
    <div class="navbar-inner">
      <div class="container">
        <a class="brand" href="#">SGCEt</a>
      </div>
    </div>
  </div>
  <div class="container-fluid">
    <div class="row-fluid">

      <p>La mejor conferencia en México.</p>
      <button class="btn">Registrarse ahora</button>

    </div>
  </div>
</body>
  

Este ejemplo demuestra el uso de Twitter Bootstrap. La pagina incluye el CSS para Bootstrap - la segunda CSS incluye reglas especialmente para pantallas moviles. En el body, incluye etiquetas normales con nombres de clase de Bootstrap.

This example shows Twitter Bootstrap. The page includes two script tags for Bootstrap - the second file are rules specifically for smaller mobile/tablet screens. The body includes standard HTML tags with Bootstrap class names on them.

JS Libraries

DOM Data AJAX Touch Scroll Animation
Zepto
jQTouch
iScroll
Backbone

Cuando estamos desarrollando una aplicación de web, usamos las bibliotecas de JavaScript para que no tengamos que preocuparnos sobre las diferencias entre los navegadores - como la gran diferencia entre IE seis y todos los otros. Pero cuando estamos desarrollando una aplicación movil, no hay tantas diferencias entre los navegadores - ambos Android y iOS usan una version de Webkit. Sin embargo, todavia hay buenas razones para usar bibliotecas de JavaScript en movil - por ejemplo, para ayudar en responder a los eventos de toque o scroll. Hay algunas bibliotecas que cubren todos los aspectos de la aplicación, y hay muchas “micro-bibliotecas” que tratan de solamente un aspecto. Pueden encontrarlos en microjs.com.

When we're making a desktop web app, we use JS libraries so that we don't have to worry about the differences between browsers - like the big difference between IE6 and all the others. But when we're making a mobile web app, there aren't as many browsers differences - both Android and iOS use a version of WebKit. However, there are still good reasons for using JS libraries in mobile web apps, like to help in responding to touch events or improve scrolling. There are some libraries that cover all aspects of making an app, and there are also many "micro-libraries" that cover just one aspect.

Zepto Example

var activate = ('createTouch' in document) ? 'touchstart' : 'click';

$("body > section").first().addClass("current")

$("a.back").live(activate, function(event) {
	var current = $(this).attr("href");
	$(".current").removeClass("current").addClass("reverse");
	$(current).addClass("current");
});

$(".menu a[href]").live(activate, function(event) {
	var link = $(this), section = link.closest('section'),
	  prev_element = "#"+(section.removeClass("current").addClass("reverse").attr("id"));
	$(link.attr("href")).addClass("current");
	$(".current .back").remove();
	$(".current .toolbar").prepend("Back");
});

Zepto es una biblioteca popular que hace mucho, y tiene la misma interfaz que jQuery, a proposito. Si ya sabe como utilizar jQuery, entonces encontrará facil usar Zepto en su lugar. Zepto es mucho mas pequeno que jQuery porque no tiene correciones para los navegadores viejos como IE-seis - entonces es perfecto para una aplicación movil. Este ejemplo demuestra algunos metodos del Zepto - parece casi el mismo que jQuery.

Zepto is a library that does a lot and has the same interface as jQuery - on purpose. If you already know how to use jQuery, then you should find it easy to use Zepto in its place. Zepto is much smaller than jQuery because it doesn't contain browser hacks for older browsers like IE6, which makes it perfect for mobile apps. This code shows a few Zepto methods - it looks nearly the same as jQuery.

My app stack

Para la segunda version de mi aplicación movil - la version corriente - uso una combinación de Twitter Bootstrap, ZeptoJS, y micro-bibliotecas. Tuve que trabajar mas que antes para programar una interfaz movil, pero encontré mucho mas facil customizarla.

For the second and current version of my app, I use a combination of Bootstrap, Zepto, and micro-libraries. I had to work harder to make a mobile-friendly user interface than when I was using jQueryMobile, but I find it much easier to customize.

PhoneGap: Architecture

Aqui esta la arquitectura de mi aplicación de PhoneGap. Hay solamente un archivo de HTML. Ademas, hay unos archivos de CSS - bootstrap y mis customizaciones, y los archivos de JavaScript. Todos de estos archivos se almacenan en un directorio en el proyecto de Android o iOS, y son incluídos cuando se compila un Android JAR o un iOS app.

Here's the architecture of my PhoneGap app. There's just one HTML file, and besides that, there are a few CSS files (Bootstrap and custom CSS), and the JS files. All of these files go in a folder in the Android or iOS project, so that they're included in the compiled Android JAR or iOS app.

PhoneGap: index.html

<!doctype html>
<html>
 <head>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
  <link rel="stylesheet" href="css/all-phonegap-min.css"/>
 </head>
 <body>

  <div id="mobile-header" class="actionsbar">
   <img src="img/logo_mobile.png">
  </div>

  <div id="mobile-footer" class="navbar">
    <ul class="nav"> 
      <li class="active"><a href="#mobile-log-page">Log</a></li>
      <li><a href="#mobile-stream-page">Stream</a></li>
      <li><a id="logout-button">Logout</a></li>
    </ul>
   </div>

  <!-- Pages -->
  <div id="mobile-pages" class="container-fluid">

    <div id="mobile-splash-page" class="mobile-page">
        Loading...
    </div>

    <div id="mobile-login-page" class="mobile-page">
      <p>Welcome! Please login with either your Facebook account or email.</p>

        <form id="login-facebook-form">
         <button class="btn primary" id="login-facebook-button" type="submit" disabled>Login with Facebook</button>
         <img id="login-facebook-loading" src="img/loading_big.gif" style="display:none;">
        </form>

        <form id="login-form">
          <label for="email">Email</label>
          <input id="email" name="email" type="email" value="" tabindex="1">
          <label style="display:block" for="password">Password <a tabindex="0" style="font-size:10px" href="/forgot-password">(Forgot?)</a></label>
          <input d="password" name="password" type="password" value="" tabindex="2">
          <button class="btn primary" id="login-button" type="submit" disabled>Login</button>
          <img id="login-loading" src="img/loading.gif" style="display:none">
        </form>

    </div>

  </div>

<script src="js/libs/zepto-min.js"></script>
<script src="js/all-phonegap-min.js"></script>
<script>
$(document).ready(function() {
  ED.mobile.setupMobile();
});
</script>
<script src="js/libs/phonegap-1.2.0.js"></script>
<script src="js/libs/phonegap-fb.js"></script>
{% if native %}
<script>
document.addEventListener('deviceready', function() {
  ED.mobile.setupPhonegap();
}, false);
</script>
{% else %}
<script src="js/phonegap-mock.js"></script>
<script>
window.setTimeout(function() {
  ED.mobile.setupData();
}, 300);
</script>
{% endif %}
</body>
</html>
   

Este código es una version simplificada del archivo de HTML. Hay divs para el header y footer, que son fijos y siempre visibles. Despues, hay un div para cada “pagina”. El javascript controla la visibilidad de las paginas, para que solo una pagina sea visible en cada momento. Despues del cuerpo, el javascript escucha eventos para inicializar la interfaz y obtener los datos.

This code is a simplified version of my HTML file. There are divs for the header and footer, which are fixes and always visible, and after that, there's a div for every "page". The JS controls the visibility of the pages to make sure only one's visible at a time. At the bottom, the JS listens to several events so it knows when to initialize the UI and fetch data.

PhoneGap: Workflow

Aqui esta mi flujo de trabajo. Mi estrategia general es desarollar en Chrome durante el tiempo que pueda, y evitar desarollar en el telefono hasta que sea absolutamente necesario. Porque? Es mucho mas facil desarollar en Chrome, porque allí puedo utilizar el Chrome Developer Tools, con inspección de DOM, breakpoints en el código, el registro de errores, etc. En el movil, no hay tantas herramientas de desarollo. Para comenzar, edito los archivos en mi computadora, usando un IDE como Sublime Text o Coda. Además, tengo un servidor local para mi aplicación de web, y tengo un URL en mi aplicación de web que apunta al archivo movil. Despues de probarlo en Chrome en mi laptop, tengo un comando en mi Makefile que concatena, compila, y copia todos los archivos a los directorios de Android o iOS, compila una aplicación nativa, y la manda a mi telefono o emulador.

Here's my workflow. My general strategy is to develop in Chrome for as long as possible, and avoid debugging on the phone until absolutely necessary. That's because it's much easier to develop in Chrome, where I can use the Chrome Developer Tools with DOM inspection, error logging, and code breakpoints. In the mobile browsers, the tooling sitution is really not so great. To start off with, I edit the files in an IDE, usually Sublime Text or Coda. Then, I run a local server for my web app, and I have a URL in my web app that loads in the mobile file. I test in Chrome using that URL. If it seems to be working, I run a command in my Makefile that concatenates, compiles, and copies all of the files to the Android or iOS project, compiles the native app, and sends it to my phone or an emulator.

PhoneGap: Makefile

mobile-jscss-cat:
    cat \
        $(STATICDIR)/js/util.js \
        $(STATICDIR)/js/data.js \
        $(STATICDIR)/js/models.js \
        $(STATICDIR)/js/shared.js \
        $(STATICDIR)/js/mobile.js \
        $(STATICDIR)/js/libs/date.format.js \
        $(STATICDIR)/js/libs/jsrender.js \
        $(STATICDIR)/js/libs/colorslider.js \
        $(STATICDIR)/js/libs/bootstrap.datepicker.js \
        $(STATICDIR)/js/libs/timeago.js \
        $(STATICDIR)/js/libs/lscache.js \
        $(STATICDIR)/js/libs/personalize.js \
        > $(STATICDIR)/js/all-phonegap.js;

    cat \
        $(STATICDIR)/js/libs/pg-plugin-fb-connect.js \
        $(STATICDIR)/js/libs/facebook_js_sdk.js \
        > $(STATICDIR)/js/phonegap-fb-ios.js;

    cat \
        $(STATICDIR)/js/libs/facebook.js \
        > $(STATICDIR)/js/phonegap-fb-android.js;

    cat \
        $(STATICDIR)/css/colorslider.css \
        $(STATICDIR)/css/dateinput.css \
        $(STATICDIR)/css/bootstrap.css \
        $(STATICDIR)/css/bootstrap-responsive.css \
        $(STATICDIR)/css/phonegap.css \
        > $(STATICDIR)/css/all-phonegap.css;

mobile-jscss-compress:
    java -jar $(CLOSUREFILE) --js_output_file=$(STATICDIR)/js/all-phonegap-min.js --js=$(STATICDIR)/js/all-phonegap.js;
    java -jar $(CLOSUREFILE) --js_output_file=$(STATICDIR)/js/libs/zepto-min.js --js=$(STATICDIR)/js/libs/zepto.js;
    java -jar $(YUIFILE) $(STATICDIR)/css/all-phonegap.css -o $(STATICDIR)/css/all-phonegap-min.css;

android-jscss:
    $(MAKE) jscss-clean;
    $(MAKE) jscss-jshint;
    $(MAKE) mobile-jscss-cat;
    $(MAKE) mobile-jscss-compress;
    python android_index.py > $(ANDROIDDIR)/index.html
    cp $(STATICDIR)/js/all-phonegap-min.js $(ANDROIDDIR)/js/all-phonegap-min-`date +%m%d%H%M`.js;
    cp $(STATICDIR)/css/all-phonegap-min.css $(ANDROIDDIR)/css/all-phonegap-min-`date +%m%d%H%M`.css;
    sed -i '' "s/all-phonegap-min-[0-9]*\.js/all-phonegap-min-`date +%m%d%H%M`\.js/" $(ANDROIDDIR)/index.html
    sed -i '' "s/all-phonegap-min-[0-9]*\.css/all-phonegap-min-`date +%m%d%H%M`\.css/" $(ANDROIDDIR)/index.html;
    cp $(STATICDIR)/img/icon-* $(ANDROIDDIR)/css/images/.;
    cp $(STATICDIR)/js/all-phonegap-min* $(ANDROIDDIR)/js/.;
    cp $(STATICDIR)/css/all-phonegap-min* $(ANDROIDDIR)/css/.;
    cp $(STATICDIR)/js/phonegap-fb-android.js $(ANDROIDDIR)/js/libs/phonegap-fb.js;
    cp $(STATICDIR)/js/libs/zepto-min.js $(ANDROIDDIR)/js/libs/zepto-min.js;

android-build:
    rm -f ./android/bin/everyday.io*
    cd android; ant debug;
    cd ../;
    adb -d install -r ./android/bin/everyday.io-debug.apk
    adb shell am start -n io.everyday.app/io.everyday.app.App

android-release:
    python android_release.py;
    cd android; ant release;

   

Aqui esta la parte de mi Makefile que hace todo para Android.

Here's the part of my Makefile that does everything for Android.

PhoneGap: Debugging

Android: DDMS

iPhone: XCode Debug Log

  function log(something) {
     // ...
      if (isIOS() || isAndroid()) {
        if (typeof something == 'object') {
          something = JSON.stringify(something);
        }
        something = truncateText(something, 2000);
        something = '\nLOG: ' + something;
        var stacktrace = '';
        if (window.printStackTrace) {
          try {
            stacktrace = '\n -' + printStackTrace().slice(4).join('\n -');
            something += '\nSTACKTRACE:' + stacktrace;
          } catch(e) {}
        }
      }
    }
  }
    

Por desgracia, no es siempre posible probar toda la funcionalidad en Chrome - a veces, algunos fallos aparecen solamente en el telefono. Hay que tener una manera de comprender el fallo y resolverlo. En Android, se puede usar DDMS para ver todos los registros, y en iOS se puede usar XCode para verlos. Aqui esta mi función de log, que hace un poco mas que console.log - tambien convierte objetos en strings e incluye un stacktrace.

Unfortunaly, it's not always possible to test all the functionality in Chrome - some bugs only appear once the app is running on the phone. So, you have to have a way of debugging on the phone. For Android, I use DDMS to view the logs, and for iOS, I use the XCode debug log. Here's a snippet of my log function, which does a bit more than console.log, like converting objects to flat strings and adding a stack trace.

PhoneGap: Debugging DOM

In development:

<script src="http://debug.phonegap.com/target/target-script-min.js#eatdifferent">
</script>
   

A veces, el fallo no es de la lógica interna, pero sí de la interfaz, cuando algo aparece diferente en el navegador movil que en Chrome. En este caso, uso una herramienta que se llama Weinre, que me permite inspeccionar el DOM de una pagina en mi movil desde mi laptop, comunicando un servidor con un archivo de JavaScript en la pagina. Hay otras formas de usar esta herramienta - se puede tener su propio servidor local, se puede usar el servidor de PhoneGap en debug.phonegap.com, o se puede usar Adobe Shadow.

Sometimes, the bug isn't with the internal logic but with the UI, when something appears differently in the mobile browser than in Chrome. In this case, I use a tool called Weinre which lets me remotely inspect the DOM from my laptop, by sending commands and information to a local server from a weinre JS file in the webpage. There are a few ways of using Weinre - you can run your own local server, use the one at debug.phonegap.com, or use the Adobe Shadow tool.

PhoneGap: Logging

1) Store logs:

var allLogs = [];
function log(something) {
  var storedSomething = something;
  if (window.JSON) {
    storedSomething = JSON.stringify(something);
  }
  storedSomething = 'LOG @ ' + new Date().toString() +
    ': ' + truncateText(storedSomething, 200);
  allLogs.push(storedSomething);
  // ...
}

2) Send logs and device info with every XHR:

      data += 'browserinfo=' + encodeURIComponent(ED.util.getBrowserInfo());
      data += '&logs=' + ED.util.getLogs().reverse().join(', ');
      data += '&version=' + ED.VERSION;

3)Send logs when feedback sent:

A veces, un usuario encuentra un error en la aplicación que yo no vi cuando estaba probandola. Es muy dificil comprender lo que pasa en el movil de un usuario, así que mi aplicación trata de registrar todo lo que pasa y manda los registros con cada llamada al servidor. Tambien manda toda la información sobre el movil y la version de la aplicación. Entonces, cuando un usuario me dice que pasó algo extraño, puedo ver los registros para entender mejor qué ocurrió en la aplicación.

Sometimes, a user encounters an error in the app that I didn't see on my phone. It's hard to understand what is happening in a remote user's mobile app, so my app tries to log everything that happens and sends those logs with every server request. It also sends information about the phone and the app version. So when a user tells me something funny happened, I can check out the most recent logs and try to get an understanding of what the app was doing at the time.

PhoneGap: Testing

import unittest
from sys import *
import time

import base_tests
import log_page
import login_page
import mobile_page


class MobileTests(base_tests.BaseTests):

    @classmethod
    def setUpClass(cls):
        super(MobileTests, cls).setUpClass()
        MobileTests.clear_log_data()

    def setUp(self):
        self.login_page  = login_page.LoginPage(MobileTests.driver, MobileTests.waiter)
        self.log_page    = log_page.LogPage(MobileTests.driver, MobileTests.waiter)
        self.mobile_page = mobile_page.MobilePage(MobileTests.driver, MobileTests.waiter)
        self.stream_page = stream_page.StreamPage(self.__class__.driver, self.__class__.waiter)
        self.open('/mobile')
        self.login_page.login()
        self.mobile_page.verify_logged_in()
        self.mobile_page.verify_ready()

    def tearDown(self):
        self.mobile_page.logout()

    def test_measurements(self):
        section_id = 'measurements'
        self.mobile_page.open_section(section_id)
        self.log_page.enter_weight()
        self.mobile_page.verify_save_section(section_id)
    
    def test_measurements_edit(self):
        section_id = 'measurements'
        self.mobile_page.open_section(section_id)
        self.log_page.enter_weight('145')
        self.mobile_page.verify_save_section(section_id)

if __name__ == "__main__":
    unittest.main()
   

Claro, seria mejor si no hubiera ningún fallo, y por eso, tengo una lista de pruebas de Selenium. Este código incluye dos de estas pruebas, hay un total de veinticinco pruebas. Las pruebas se ejecutan en Chrome, porque todavía no existe una buena forma de ejecutarlas en un navegador integrado movil. Un dia.

Of course, it would be better if no bugs happened, and to try to make that happen, I have a suite of Selenium tests for my mobile app. This code snippet shows two of the tests, but there are 25 in all. I currently run the tests in Chrome only, as I haven't managed to get them running inside embedded web browsers on the phones yet.

PhoneGap: Performance Testing

<!doctype html>
<html>
 <head>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
  <title>PhoneGap App</title>
  <script>
  var timedEvents = [];
  function timeEvent(name) {
    timedEvents.push({'name': name || 'unnamed', time: Date.now()});
  }

  function showTimedEvents() {
    var timeText = '';
    var timeHtml = '<table>';
    for (var i = 0; i < timedEvents.length; i++) {
      var timedEvent = timedEvents[i];
      timeText += timedEvent.name + ': ' + timedEvent.time;
      var diff = '';
      if (i > 0) {
        diff = (timedEvent.time - timedEvents[i-1].time);
        timeText += ' (' + diff + 'ms elapsed)';
      }
      timeHtml += '<tr><td>' + timedEvent.name + '<td>' + timedEvent.time + '<td>' + diff;
      timeText += '\n';
    }
    console.log(timeText);
    document.body.innerHTML += timeHtml;
  }
  </script>
  <script>timeEvent('Before CSS');</script>
  <link rel="stylesheet" href="css/all-phonegap-min.css?v=10201550"/>
  <script>timeEvent('After CSS');</script>
 </head>
 <body>

  <div>HTML here (more than this)</div>

<script>timeEvent('After HTML');</script>
<script src="js/libs/jquery-1.6.2.min.js"></script>
<script>timeEvent('After jQuery script tag');</script>
<script src="js/all-phonegap-min.js?v=10201550"></script>
<script>timeEvent('After other script tag');</script>
<script>

$(document).ready(function() {
  timeEvent('After document ready');
  myCustomMobileFunction();
  timeEvent('After ready JS called');
  showTimedEvents();
});
</script>
</body>
</html>
   

Al comienzo de esta presentacion, yo dije que el rendimiento no era tan importante para mi. Pero me di cuenta que sí que lo era - en concreto, es importante que mi aplicación cargue rapidamente para que pueda sacar una foto en determinados momentos. Los usuarios quieren sacar fotos de todas sus comidas, pero no quieren pasar toda la comida sacando fotos. No estaba contenta con el tiempo de carga de mi aplicación, y lo investigué despues haciendo logging en todos los archivos y funciones para encontrar las partes lentas. Gracias a estos registros, mejoré el tiempo de carga.

At the start of this, I said that performance wasn't a big consideration for me. Well, as it turns out, it is - more specifically, it's important that my app loads rapidly so you can quickly take a photo. Users want to take photos of all their meals, but they don't want to spend their whole meal taking the photo. I wasn't happy with the loading time of my app, and I investigated what was causing it to slow down by putting timing logs throughout. Thanks to the logs, I was able to make several improvements to speed up the loading time.

Would I do it again?

PROs CONs
  • Code reuse
  • Knowledge reuse
  • Bright future
  • Mobile browser bugginess/stability
  • Bugs take longer to debug
  • Hard to fake native UX

...Yes, probably... but should you?

Ahora, espero que todos ustedes entiendan por qué escogí PhoneGap, y como lo uso. La pregunta final es esta: tomaría la misma decision otra vez?

Hay grandes ventajas de mi estrategia: puedo re-usar la mayoria de mi código entre la aplicación movil y la aplicación web, puedo usar el conocimiento de tecnologias de web que ya tengo. Pero si hay desventajas: los navegadores moviles están llenos de fallos, y debido a que salen nuevas versiones de esos navegadores frecuentamente, siempre hay nuevos fallos. En particular, el navegador de Android es el peor WebKit que he visto - el rendimiento es horrible. Y cuando hay un fallo, toma mucho tiempo arreglarlo. Me tomo una semana solamente en tratar de controlar el foco de un textfield! Ademas, es dificil programar una interfaz que parezca “nativa” usando las tecnologias de web.

Pero yo creo que hay un futuro brillante para las aplicaciónes hibridas, y la experiencia de programarlas siempre está mejorando. Cada dia, hay mas herramientas, bibliotecas, frameworks, y articulos.

En conclusion: si, yo tomaría la misma decision de usar PhoneGap - pero tal vez usaría un framework movil como Sencha, ahora que he visto todos los fallos con los navegadores moviles. O quizás no, ahora que soy una experta en los fallos.

Mi consejo para ustedes: consideren todas las opciónes antes de decidir, y estén listos para cambiar cuando haya una mejor opción en el futuro.

Now, I hope that you all understand why I chose PhoneGap and get an idea of what it's like to develop for it. The final question is: would I do it again?

There are some great advantages to the strategy I adopted: I can re-use the majority of my code base between my web and mobile web apps (making it easy to add new features), and I can re-use my knowledge of web technology (and become more of an expert in it).

But there are definite disadvantages: mobile web browsers are full of bugs, and since there are new versions of those mobile browsers released faitly frequently, there are always new bugs. In particular, the Android browser is the worst WebKit that I've encountered - the rendering performance is so horrible that I had to remove much of my CSS3. Plus, when there is a bug in a mobile browser, it takes quite a long time to fix it, since the tooling is sub-par and the bugs aren't well documented. I once spent a week of my life trying to get a textfield to focus properly on Android! Besides the bugs, it can also be difficult to make an interface that both looks and feels "native" using web technologies.

But, I think there is a bright future for hybrid apps, and the experience of developing them is improving steadily. Each day, there are more tools, libraries, frameworks, and blog posts dedicated to the world of hybrid apps.

In conclusion: yes, I probably would make the same decision again - but maybe I would use a framework like Sencha, so that I didn't have to spend as much time on the little mobile browser bugs (and let their team worry about them) - or maybe not, since now I'm more experienced in understanding and debugging them.

My advice for you: consider all the options before you decide, and be ready to change if a better option arises in the future.