Phonegap app su iTunes Store. Si può fare!

Su moltissimi forum di Phonegap si leggono messaggi di persone disperate perchè iTunes Store non accetta le loro app realizzate con HTML5 e Phonegap.

Proporre una app ad Apple per vedersela esporre sull’iTunes Store non è affare da poco: prima di tutto devi avere un Mac, dopo di che devi passare indenne tutta la procedura di verifica delle varie licenze di sviluppatore, devi inserire l’ID di qua, l’ID di la…a prima vista, una giungla burocratica pazzesca.

Una volta che, invece, si è presa la mano sulle operazioni da compiere, risulta piuttosto veloce ed indolore avere un pacchetto valido da proporre ad Apple. Altro discorso è quello di riuscire a proporre una applicazione ad Apple che risponda ai canoni indicati dall’azienda di Cupertino nella loro guideline.

L’errore più frequente (e per più frequente parliamo del 95% delle volte) che viene compiuto dai programmatori o presunti tali di app è quella di proporre una versione “app” di un normalissimo sito internet. Apple, giustamente, rifiuta l’applicazione indicando che il progetto non è abbastanza…”app”. Non sfrutta in nessuna maniera le peculiarità dello smartphone o del tablet, non da alcun plusvalore all’utente rispetto alla navigazione con safari del sito stesso. Quindi…se volete accingervi a creare la vostra app, fermatevi e ragionate un secondo su quale potrebbe essere una funzionalità in più per la vostra applicazione.

La mia prima app realizzata con Phonegap è la versione mobile del sito Ilportaledelpoker.com, portale dedicato al mondo del texas e sue varianti. Poker, roba da malati. Un po’ come la programmazione, via…

Alla fine, è niente altro che l’archivio delle news inserite sul sito, con però una aggiunta fondamentale: la possibilità di aggiungere, su un database creato on-the-fly sul telefono, gli articoli a preferiti, potendoli poi sfogliare in un secondo tempo.
Attenzione: gli articoli non vengono salvati interamente sul telefono, ma solamente “indicizzati” attraverso il loro ID univoco. Entrando nella sezione preferiti, la app prima carica dal database l’array con gli ID degli articoli, dopo di che ne carica titolo e testo da remoto, direttamente dal database centrale del sito.

Utilizzo, in sostanza, l’oggetto Storage datomi da Phonegap.
Il codice per gestire lo Storage è semplicissimo:

function populateDB(tx) {
     tx.executeSql('DROP TABLE IF EXISTS DEMO');
     tx.executeSql('CREATE TABLE IF NOT EXISTS DEMO (id unique, data)');
     tx.executeSql('INSERT INTO DEMO (id, data) VALUES (1, "First row")');
     tx.executeSql('INSERT INTO DEMO (id, data) VALUES (2, "Second row")');
}

function errorCB(err) {
    alert("Error processing SQL: "+err.code);
}

function successCB() {
    alert("success!");
}

var db = window.openDatabase("Database", "1.0", "Cordova Demo", 200000);
db.transaction(populateDB, errorCB, successCB);

Dichiaro una variabile db, che esegue un comando, ossia window.openDatabase( nome del database, versione del db, nome “visibile” del db e la sua dimensione ); subito dopo eseguo una azione chiamata transaction, che consiste in 3 azioni distinte: azione, errore, successo.
Devo fare delle operazioni sul database? Le carico nella funzione populateDB, ossia quella che, nell’esempio:

  • elimina il database se esiste già
  • ricrea la tabella
  • inserisce una riga con id=1
  • inserisce una riga con id=2

Semplicissimo!
Il sistema poi, a seconda che la funzione populateDB sia andata a buon fine o a vacca totale, richiamerà errorDB e successDB. A voi l’incredibile difficoltà di capire quale verrà lanciata in caso di errore.

Questo piccolo excursus per farvi capire che basta utilizzare un oggettino ed avere un minimo di fantasia per vedere le proprie app accettate da Apple. Certo, se la vostra app è un cesso, non funziona bene a prescindere dalle sue funzionalità oppure gliela proponete dicendogli subito che è una demo o una beta, beh…allora avete proprio tanto tempo da perdere!

Facebook: fare click su mi piace per mostrare la pagina

A moltissimi di voi sarà capitato di dover creare una pagina sul vostro sito con del contenuto visibile solo a chi ha fatto “mi piace” su Facebook in una pagina aziendale.

Innanzitutto bisogna creare la propria app in http://developers.facebook.com, perchè, per fare quello che ci serve, abbiamo bisogno di un app ID che Facebook ci deve rilasciare.
Una volta creata la vostra app (usando il tasto Crea Applicazione in alto a destra), vi verrà assegnato un App ID/API Key: è un codice numerico.

Ora quello che ci serve è una pagina .html ed una .php, che useremo per caricare le API di Facebook e indicheremo come gestire la cache al browser.

Create un file e nominatelo facebookapi.php:

 <?php
 $cache_expire = 60*60*24*365;
 header("Pragma: public");
 header("Cache-Control: max-age=".$cache_expire);
 header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$cache_expire) . ' GMT');
 ?>
 <script src="//connect.facebook.net/it_IT/all.js"></script>

Ora create un file index.html:

<html>
  <head>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    <style type="text/css">
      div#container_notlike, div#container_like {
        display: none;
      }
    </style>
  </head>
  <body>
    <div id="fb-root"></div>

    <div id="container_notlike">
      Non hai fatto mi piace sulla mia pagina!
    </div>

    <div id="container_like">
      Ti piace eh?
    </div>

  </body>
</html>

Come potete notare non c’è ancora traccia del codice javascript per gestire gli eventi. Un attimo di pazienza! Abbiamo semplicemente inserito la libreria jQuery ( una versione non nuovissima, a voi le eventuali implementazioni…) e i due div che dovranno apparire a seconda degli eventi del codice per gestire gli eventi su Facebook.
Sono entrambi nascosti, perchè starà al codice javascript mostrare uno piuttosto che un altro. Viene da sp

Ora dobbiamo inserire il codice javascript nell’head, possibilmente dopo che il DOM è pronto per rispondere. Come si fa? Beh, facile…

$(document).ready(function(e){
    //codice...
});

Aggiungiamo ora il codice all’interno del mio document.ready

      window.fbAsyncInit = function() {
        FB.init({
          appId      : '[il vostro APP ID senza le parentesi]',
          channelUrl : 'facebookapi.php',
          status     : true, // controlla lo status su FB
          cookie     : true, // attiva i COOKIES
          xfbml      : true  // fa il parsing XFBML
        });

        FB.getLoginStatus(function(response) {
          var page_id = "[l'id della vostra pagina di Facebook]";
          if (response && response.authResponse) {
            var user_id = response.authResponse.userID;
            var fql_query = "SELECT uid FROM page_fan WHERE page_id = "+page_id+"and uid="+user_id;
            FB.Data.query(fql_query).wait(function(rows) {
              if (rows.length == 1 && rows[0].uid == user_id) {
                //console.log("Ha già fatto LIKE, mostro la pagina");
                $('#container_like').show();
              } else {
                //console.log("Non ha fatto like");
                $('#container_notlike').show();
              }
            });
          } else {
            FB.login(function(response) {
              if (response && response.authResponse) {
                var user_id = response.authResponse.userID;
                var fql_query = "SELECT uid FROM page_fan WHERE page_id = "+page_id+"and uid="+user_id;
                FB.Data.query(fql_query).wait(function(rows) {
                  if (rows.length == 1 && rows[0].uid == user_id) {
                   // console.log("Ha fatto il Like");
                    $('#container_like').show();
                  } else {
                   // console.log("Non ha fatto il Like");
                    $('#container_notlike').show();
                  }
                });
              } else {
                //console.log("Non ha fatto il Like");
                $('#container_notlike').show();
              }
            }, {scope: 'user_likes'});
          }
        });
      };

      // Load the SDK Asynchronously
      (function(d){
        var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
        js = d.createElement('script'); js.id = id; js.async = true;
        js.src = "//connect.facebook.net/en_US/all.js";
        d.getElementsByTagName('head')[0].appendChild(js);
      }(document));

Ora tutto dovrebbe funzionare a dovere.
Chiaramente non sarebbe male implementare la pagina facendo si che, invece di mostrare il div con la scritta “hai fatto like”, venisse caricato del contenuto con una chiamata ajax.
Se non vi funziona, commentate pure esprimendo tutto il vostro disappunto 😉

Phonegap e Android: missing one of the following

Oggi ho provato ad installare tutto il sistema per poter esportare la mia Phonegap App su ambiente Android. Più facile a dirsi che a farsi.
Giusto per non farsi mancare nulla, Windows comincia sempre con rispondere con messaggi di errori del tipo: comando non riconosciuto. E già bestemmie come se piovesse…

Scarico Phonegap 2.5.0 e provo, così, senza aver letto niente altro, a lanciare il comando create..

create c:\apps com.nomeazienza.nomeapp nomeapp

Ovviamente terremoto e tragedia, non funziona niente.
Mi accorgo immediatamente che, differentemente da quanto pensassi, non ho installato nè il Java SDK nè ANT, di cui ignoravo l’esistenza fino a qualche minuto prima.

Nel dubbio mi riscarico tutto:

Non grandi cose, siamo sui 300mb.
Installo il tutto e per controllare che tutto sia funzionante apro la shell e rilancio il comando create come sopra.

Nonostante tutto, continua a non funzionare, mi viene presentato un messaggio Missing one of the following (JDK, SDK, ANT).
Per ovviare a tutto ciò, dovete controllare che tutti i comandi elencati qui siano funzionanti:

  • java
  • javac
  • ant
  • adb
  • android

Non funzionano? Probabilmente nelle variabili di sistema mancano i riferimenti alle cartelle dei vari SDK.
Un esempio su tutti, per far funzionare il comando javac si può inserire in variabile di sistema il riferimento alla cartella JavaSdk:

set path="%path%;c:\program files\java\jdk7\bin"

Cosa fa questo comando?
Semplicemente setta la variabile di sistema path con un nuovo valore, c:\program files\java\jdk7\bin, e lo aggiunge al contenuto della variabile stessa %path%.

Per tutti i comandi elencati sopra il discorso è lo stesso.

Ok, il comando create ora funziona, ma cosa fa?

Una volta indicato al comando create dove creare il progetto, dovete semplicemente aprire la cartella generata ( nel mio caso sarà c:\apps\nomeapp ). Troverete tutti i files necessari per generare la vostra Phonegap App per Android.

Ora non vi resta che aprire Eclipse, seguire le istruzioni indicate sulla pagina di Phonegap, Getting started per Android.


Elenco links utili:
Android SDK: http://developer.android.com/sdk/index.html
java sdk: http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
Apache Ant: http://ant.apache.org/bindownload.cgi
Phonegap per Android: come iniziare: http://docs.phonegap.com/en/2.5.0/guide_getting-started_android_index.md.html#Getting%20Started%20with%20Android
Aggiungere alle variabili di sistema: http://stackoverflow.com/questions/1678520/javac-not-working-in-windows-command-prompt
far funzionare il comando create di phonegap per android: http://simonmacdonald.blogspot.ca/2012/11/getting-create-command-to-work-on.html

Sottoporre ad Apple una app creata con Phonegap

Ho letto su molti forum, Phonegap forum stesso su tutti, che moltissima gente ha problemi a sottoporre ad Apple la propria app, e che spesso gli viene rifiutata perchè “non abbastanza app”.

Il motivo, in verità, è piuttosto semplice: se creo una app che è semplicemente una trasposizione mobile di un sito web, al 99% verrà rifiutata da Apple, che argomenterà il rifiuto con la frase “La vostra app non utilizza alcuno strumento tipico di uno smartphone (gps, database, rubrica), quindi spostate il vostro sviluppo verso una versione mobile del sito”. Grazie tante…

A parte la condicio sine qua non di fare una app che non faccia vomitare, è piuttosto semplice utilizzare uno degli strumenti che Phonegap mette a disposizione, uno su tutti lo Storage.

Per utilizzare il database che mette a disposizione Phonegap, è necessario richiamarlo con la seguente funzione:

function activateDB(){
	var db = window.openDatabase("NOME_DB", "VERSIONE_DB", "DISPLAY_NAME_DB", 5*1024*1024);
    db.transaction(populateDB, errorCB, successCB);
}

function populateDB(tx) {

	tx.executeSql('CREATE TABLE IF NOT EXISTS tblPreferiti(id INTEGER PRIMARY KEY ASC, idarticoli INTEGER)');

	}

function errorCB(err) {
		console.log("!!! Error processing SQL: "+err.code + ' - ' + err.message );
}

function successCB() {
}

La funzione activateDB() mi carica nella variabile db il database NOME_DB, dopo di che mi lancia la funzione populateDB() che, a seconda del risultato positivo o negativo, mi richiama successDB() o errorDB(). Tutto chiaro, no?
Come vedete, è piuttosto semplice interfacciarsi con il DB e basta salvarvi qualcosa al suo interno per utilizzare uno degli strumenti messi a disposizione da Phonegap.
Nel mio caso, ho creato una APP ( non ancora disponibile su Apple Store, ma appena lo sarà posterò il link.. ) e ho utilizzato il db per salvarvi gli articoli preferiti.
In verità salvo esclusivamente l’ID degli articoli, che vengono poi caricati dal server remoto ogni volta. Un utilizzo minimo del DB, ma…sufficiente.

Jsonp e parse file json errato

Lavorando su un app con Phonegap mi sono imbattuto in una rogna non da poco, risolta grazie a qualche invocazione dell’Altissimo e un paio d’ore di sbattimento.
La problematica era di risolvere l’annoso problema dell’errore di console di Chrome Origin null is not allowed by Access-Control-Allow-Origin.
Leggendo vari post su internet, la problematica è capitata a tantissima gente e la risoluzione della stessa non sembra così palese come può sembrare.

Io stesso, soprattutto nei blog italiani, non ho trovato molte soluzioni rapide.
A parte la quantità indecente di soloni che si beano della propria conoscenza, cosa che mediamente fa incazzare come quando si paga l’IMU, che preferiscono rompere i maroni ore ed ore, invece che dare velocemente la soluzione, come se far faticare la gente fosse, in Italia, uno sport nazionale. Nei blog americani, invece, ci si imbatte più spesso in persone più malleabili e meno snob.

Veniamo velocemente al codice:

$.ajax({
		url:"http://www.vostrodom.it/pagina.asp",
		type:"GET",
		dataType:"json",
		data:"id=" + ID 
}) [...]

Molto semplicemente, una bella chiamata con jQuery.ajax, ad una pagina sul vostro sito, con il passaggio di una variabile querystring id.
Se usate Google Chrome, noterete un bel messaggio di errore, che avvisa che non possono essere effettuate chiamate CrossDomain, ossia fuori dal dominio della pagina stessa.
La problematica avviene soprattutto se avete lanciato una pagina da file:\\ .

Come ovviare al problema? Usando come dataType:jsonp e non json.
Occhio, le problematiche, usando questo sistema, diventano altre, però, per lo meno, una rogna, di dosso, ve la togliete. Per esempio, scordatevi il passaggio POST dei dati: con JsonP avvengono tutte via GET, con tutte le problematiche di dati e sicurezza che ne derivano. Se queste noie non vi interessano, risolvete in fretta il problema.

Risolto un problema, però, ne viene fuori un altro: jsonp utilizza una funzione javascript per ovviare al Domain Crossing. Non modificando niente, potrebbe venire fuori un errore del tipo Uncaught SyntaxError: Unexpected token.

Un passo alla volta.
Poniamo il caso che il vostro file json sia come nell’esempio seguente:

{
    "glossary": {
        "title": "example glossary" 
    }
}

Al file json manca un quid, ossia una funzione che conglobi tutto.
Modificate il vostro json aggiungendo, ad esempio, readJson([vostro-codice-json]).

readJson({
    "glossary": {
        "title": "example glossary" 
    }
})

Dopo di che, andiamo nel codice javascript a dirgli che la funzione callback che deve leggere si chiama readJson.

$.ajax({
		url:"http://www.vostrodom.it/pagina.asp",
		type:"GET",
		dataType:"json",
		data:"id=" + ID,
                jsonpCallback:	"readJson"
}) [...]

A questo punto, tutti gli odiosi messaggi d’errore della console dovrebbero sparire e, magicamente, potrete fare le vostre belle chiamate json esterne.