Tu sei qui

Sviluppare app multipiattaforma - Le basi: l’algoritmo e il codice (quarta parte)

Buy me a coffeeBuy me a coffee
Ultimo aggiornamento: 21 Dicembre 2019
> INDICE DEL CORSO
< Articolo precedente: Sviluppare app multipiattaforma - Le basi: l’algoritmo e il codice (terza parte)

Creazione di un'app con Codename OneNel precedente articolo, abbiamo visto quel che occorre, a livello di hardware, software, servizi cloud, manuali, ecc., per iniziare lo sviluppo di app multipiattaforma con Codename One + Java.

Qui do per scontato che tu abbia un'installazione di Netbeans + Oracle Java 8 + Codename One plugin, come da precedenti indicazioni. Riprendo il problema già visto nella seconda parte, quello del calcolo dello spazio percorso da un'automobile, per trasformarlo in un'app molto semplice, per Android e iPhone, qui mostrata nella foto.

Argomenti:


Idee di base della programmazione ad oggetti e il nostro problema dell'automobile

Ci tengo a sottolineare il carattere introduttivo di quanto segue, rimandando alle già citate guide per gli opportuni approfondimenti.

Finora abbiamo parlato di algoritmi e visto come un semplice problema, quello del calcolo della spazio percorso da un'automobile, possa essere suddiviso in algoritmi. Ragionare per risolvere un problema in Java (e in molti altri linguaggi di programmazione) significa vederlo in un'altra prospettiva più ampia, quello della programmazione ad oggetti, riconducibile alla domanda: quali attori, o agenti, o entità, o "oggetti", agiscono e comunicano tra di loro per risolvere il problema? Quando parlo di attori, agenti, entità o oggetti indico lo stesso concetto: ogni "oggetto" è come un "attore" sul palcoscenico (del nostro software), è una "entità" che fa qualcosa, quindi è qualcosa che agisce, un "agente".

Si tratta di modellare il mondo reale in oggetti virtuali, ciascuno dei quali ha una propria autonomia e dà il proprio contributo alla risoluzione di un problema comune; per paragone, gli "oggetti" della "programmazione ad oggetti" sono come le cellule del nostro corpo, ognuna specializzata in qualcosa, ma insieme collaboranti per qualcosa di più grande.

Ricapitolando, ogni oggetto:

  • ha caratteristiche proprie, non necessariamente visibili all'esterno (cioè da altri oggetti);
  • svolge azioni, anche in questo caso non necessariamente visibili all'esterno (cioè da altri oggetti);
  • può comunicare con altri oggetti.

A ciò va aggiunto che tutti gli oggetti che hanno le medesime caratteristiche e funzioni di base fanno parte della stessa classe (di oggetti). Detto in altri termini: ogni classe è come uno stampino per fare oggetti che hanno certi tipi di caratteristiche (variabili interne) e di funzionalità. Ad es., una classe "Automobile" può creare oggetti che come caratteristiche hanno marca e modello e come funzionalità quella di accelerare o di decelerare; una specifica automobile di marca Fiat e modello Punto sarà un oggetto della classe Automobile, cioè una sua istanza.

Una classe può ereditare altre classi può generiche. Ad es., la classe "Automobile" può essere una sottoclasse della classe genitore "Veicolo". In tal caso, una specifica automobile Fiat Punto non sarà soltanto un oggetto della classe "Automobile", ma anche un oggetto della classe "Veicolo".

Le seguenti slides (fonte), sintetiche ed essenziali, possono aiutare a capire meglio i concetti della Programmazione Orientata agli Oggetti (abbreviata OOP):

download pdf

Rivediamo il nostro problema dell'automobile

Ripeto come era impostato il problema:

Problema:
Un'automobile procede alla velocità costante di 108 km/h. Quanti metri percorre in 10 minuti?

Dati:
v = 108 km/h
t = 10 min

Incognite:
s = ? [m]

Riflessioni su come decomporre questo problema in classi e oggetti

Di primo acchito, in un problema così semplice verrebbe da pensare che l'unico oggetto presente sia appunto l'automobile, però è possibile (anzi necessario) individuare altri oggetti. Comunque cominciamo ad esaminare l'automobile: le uniche proprietà rilevanti sono la velocità costante (in km/h), il tempo (in minuti) e lo spazio (in metri).

In realtà, a ben vedere, il fatto che il problema citi un'automobile non significa che stiamo realmente parlando di un'automobile. In questo caso specifico, l'automobile è soltanto un esempio, o per dirla tecnicamente una istanza, di qualcosa di più generico, appartenente alla classe del moto rettilineo uniforme (MRU). In questo caso non ci interessano minimamente la marca, il modello, l'immatricolazione, la carrozzeria, ecc., ma soltanto il tipo di moto e la legge fisica che lo descrive.

Quindi abbiamo una classe di oggetti, chiamiamola MRU, che ha le proprietà di velocità, tempo e spazio, legate tra di loro dalla legge oraria del moto rettilineo uniforme (s=vt).  A questa classe di oggetti appartiene l'oggetto automobile, ovvero l'automobile in questione è un oggetto di tipo MRU. In linea di principio, qualunque altro oggetto che eredita le caratteristiche della classe MRU, ovvero che si muove di moto rettilineo uniforme, può essere un'istanza di tale classe: una palla, un proiettile, una freccia, ecc.

Dopo aver definito le proprietà della classe MRU (velocità, tempo e spazio), potremmo chiederci se questa debba avere dei metodi che fanno qualcosa. Direi proprio di sì: ad es., può avere un metodo che prende in input velocità e tempo e restituisce in output lo spazio. Questo metodo è esattamente quello che serve a noi. Potremmo immaginare almeno altri due metodi (per ottenere la velocità dallo spazio e dal tempo, e per ottenere il tempo dalla velocità e dallo spazio), ma in questo caso non sono necessari.

Avevamo però visto che, per fare correttamente i calcoli, ci serve qualcosa che converta le unità di misura: supponiamo allora di creare una classe Convert, che ha una serie di funzioni, o meglio metodi, per convertire le unità di misura. Tali metodi si limiteranno a prendere in input un valore in una unità di misura e a restituire in output lo stesso valore in un'altra unità di misura. In questo caso, però, ha poco senso immaginare una serie di oggetti che hanno una serie di caratteristiche comuni alla classe Convert: immaginiamo che tale classe esponga direttamente i suoi metodi, senza darci la possibilità né la necessità di usare oggetti che siano sue istanze.

Fin qui, abbiamo: una classe "MRU" e una sua istanza "automobile", e una classe "Convert".

Noi vogliamo fare un'app multipiattaforma: allora ci serve anche la classe principale della nostra app, ovvero quella che sarà interpellata da Android e da iOS quando andremo ad aprire l'app. Potremmo chiamare questa classe principale PrimaApp. Ogni classe principale di qualsiasi app fatta con Codename One deve avere quattro metodi fondamentali: il metodo init() viene richiamato quando l'app è avviata, il metodo start() viene richiamato subito dopo l'init() e ogni volta che l'app passa da background a foreground, il metodo stop() viene richiamato quando l'app passa da foreground a background, il metodo destroy() quando l'app viene killata. Per nostra fortuna, tutti questi metodi vengono creati automaticamente, almeno in una versione iniziale e funzionante. La classe principale, proprio per la sua funzione peculiare, non ha oggetti: i suoi metodi vengono richiamati direttamente dal sistema operativo.

La nostra app, però, dovrà anche avere una interfaccia grafica per l'input e per l'output. Ci serviranno, come minimo: due campi di testo modificabili (TextField) per la velocità e il tempo, un pulsante (Button) per chiedere il calcolo dello spazio e una etichetta (Label) per mostrare il risultato del calcolo.

Tutto finito? Quasi: per concludere, serve un oggetto di tipo Layout, che si preoccupi di posizionare correttamente sullo schermo i due TextField, il Button e la Label.

Ricapitolando, ci servono: la classe principale PrimaApp, alcuni oggetti di tipo grafico (TextField, Button, Label), un oggetto che ha la funzione di layout manager per posizionare gli elementi grafici, una classe MRU con un oggetto di tipo Automobile, una classe Convert.

A livello grafico, ci sarà utile anche qualche CSS.


Il codice di partenza: la classe principale di qualsiasi applicazione Codename One

Non ho intenzione di fare una guida su come si usa Netbeans con Codename One, rimando ai video tutorial già precedentemente linkati. Mi limiterò a indicare le cose essenziali per gli scopi di questa introduzione alla creazione della prima applicazione.

In Netbeans, creiamo un nuovo progetto di tipo "Codename One Project", lo chiamiamo "PrimaApp", lasciamo selezionato "Java 8 Project", poi in Package Name inseriamo qualcosa del tipo "net.informaticalibera.automobile" (al posto di "net.informaticalibera" va bene qualunque altro dominio di proprio possesso, ad es. "it.miodominio"), in Main Class Name scriviamo "PrimaApp" (qui non vanno mai inseriti spazi), come Theme lasciamo "Native" e come Template "Hello World (Bare Bones)".

Come risultato, otteniamo automaticamente un file PrimaApp.java con un codice iniziale così composto:

package net.informaticalibera.automobile;

import static com.codename1.ui.CN.*;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Label;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import java.io.IOException;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.io.NetworkEvent;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename One</a> for the purpose
 * of building native mobile applications using Java.
 */
public class PrimaApp {

    private Form current;
    private Resources theme;

    public void init(Object context) {
        // use two network threads instead of one
        updateNetworkThreadCount(2);

        theme = UIManager.initFirstTheme("/theme");

        // Enable Toolbar on all Forms by default
        Toolbar.setGlobalToolbar(true);

        // Pro only feature
        Log.bindCrashProtection(true);

        addNetworkErrorListener(err -> {
            // prevent the event from propagating
            err.consume();
            if(err.getError() != null) {
                Log.e(err.getError());
            }
            Log.sendLogAsync();
            Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
        });        
    }
    
    public void start() {
        if(current != null){
            current.show();
            return;
        }
        Form hi = new Form("Hi World", BoxLayout.y());
        hi.add(new Label("Hi World"));
        hi.show();
    }

    public void stop() {
        current = getCurrentForm();
        if(current instanceof Dialog) {
            ((Dialog)current).dispose();
            current = getCurrentForm();
        }
    }
    
    public void destroy() {
    }

}

Senza cercarne di capirne la logica in dettaglio, vediamo subito cosa fa. Premiamo il pulsante "Run Project" di Netbeans (è un simbolo verde nella toolbar, con la forma di play button). Si aprirà il simulatore, mostrando quanto segue:

Codename One tutorial guide

Adesso guardiamo una parte del codice, per lo meno quella che ci interessa nei passaggi successivi. Per la precisione, focalizziamoci per il momento su queste tre righe all'interno del metodo start():

        Form hi = new Form("Hi World", BoxLayout.y());
        hi.add(new Label("Hi World"));
        hi.show();

Significato:

  • "Form hi" significa che "hi" è un oggetto di tipo "Form", cioè è una istanza della classe "Form". Un oggetto di tipo "Form" corrisponde a ciò che l'utente vede nella schermata dell'app, ovvero un "Form" è il contenitore principale che contiene tutti gli oggetti dell'app correntemente visualizzati.

  • "=" significa "assegna a". A sinistra del simbolo di uguale dichiariamo che esiste un oggetto di tipo "Form" e di nome "hi",  ma non sappiamo ancora chi è questo oggetto. A destra dell'uguale diciamo chi è questo oggetto o, per dirla in maniera più appropriata, assegniamo la variabile "hi" a un oggetto esistente in memoria.

  • La scrittura "new Form("Hi World", BoxLayout.y())" comprende varie parti. "new Form" significa "crea un nuovo oggetto di tipo Form e salvalo da qualche parte in memoria" (della gestione della memoria non ci dobbiamo occupare, è tutta automatica). Ciò però non è tutto: al "costruttore" del nuovo oggetto di tipo "Form", ovvero al metodo speciale incaricato di assegnare determinate caratteristiche al nuovo oggetto, passiamo due parametri, che sono "Hi World" e "BoxLayout.y()". Il primo è semplicemente il titolo del "Form", ovvero la scritta che viene visualizzata in alto. Il secondo è il layout manager utilizzato, nello specifico è un algoritmo che dispone gli oggetti verticalmente, uno sotto l'altro.

  • La scrittura "hi.add(new Label("Hi World"))" è composta di più parti. Tradotta in linguaggio naturale vuol dire: crea un nuova etichetta (Label) contenente la stringa "Hi World" è aggiungila al form "hi".

  • Infine "hi.show()" significa "visualizza il form".

Quello che otteniamo da queste tre righe di codice è esattamente ciò che è mostrato nel simulatore.


La nostra prima app per Android e iPhone

Torniamo al nostro problema dell'automobile. Abbiamo detto quali sono le classi che ci servono:

  • la classe principale PrimaApp già ce l'abbiamo, è quella creata automaticamente da Netbeans;
  • alcuni oggetti di tipo grafico (TextField, Button, Label);
  • un oggetto che ha la funzione di layout manager per posizionare gli elementi grafici già ce l'abbiamo, è BoxLayout.y(), che dispone gli oggetti uno sotto l'altro;
  • una classe MRU con un oggetto di tipo Automobile;
  • una classe Convert.

Iniziamo con il sistemare gli elementi grafici. Le precedenti tre linee di codice diventano:

        Form hi = new Form("Automobile", BoxLayout.y());
        hi.add(new Label("Inserisci i dati richiesti:"));
        TextField velocita = new TextField("", "Velocità in km/h");
        TextField tempo = new TextField("", "Tempo in minuti");
        Button calcola = new Button("Calcola lo spazio in metri");
        Label risultato = new Label("");
        hi.addAll(velocita, tempo, calcola, risultato);
        hi.show();

Al di là della spiegazione dettagliata di ogni singola riga, nel complesso cosa fa questo codice è abbastanza intuibile. Comunque ciascuno può leggersi i Javadoc di Codename One, dove per ogni classe (Form, TextField, Button, Label) c'è la relativa documentazione. Probabilmente se copi e incolli queste righe così come sono, alcune verranno sottolineate di rosso perché mancano i rispettivi "import": Netbeans permette di inserire automaticamente gli import mancanti, basta cliccare con il destro sul codice e selezionare "Fix import".

Riaprendo il simulatore, questo è il risultato:

Tutorial Codename One, sviluppo app multipiattaforma

Non male, con l'interfaccia grafica può bastare così.

Passiamo alla classe MRU. In Netbeans, clicchiamo con il tasto destro sul nome del package (nel mio caso, net.informaticalibera.automobile), poi selezioniamo "New" e "Java Class". Alla nuova classe diamo il nome "MRU".

Questo sarà il codice iniziale, che si limita a specificare quali variabili interne (private, cioè non visibili all'esterno) definiscono le caratteristiche degli oggetti di tale classe:

package net.informaticalibera.automobile;

/**
 * Moto Rettilineo Uniforme
 *
 * @author Francesco Galgani, corso di sviluppo app multipiattaforma
 */
public class MRU {
    private int spazio; // metri
    private int tempo; // secondi
    private int velocita; // metri al secondo

   
}

Fatto ciò, abbiamo bisogno di metodi che permettano agli oggetti di altre classi di dialogare con le istanze, cioè con gli oggetti, di MRU. Creiamo automaticamente i metodi getter e setter che permettono, rispettivamente, di leggere e di scrivere i valori delle tre variabili sopra riportate. Clicchiamo con il tasto destro nello spazio vuoto prima dell'ultima parantesi graffa di chiusura, selezioniamo "Insert code", poi "Getter and Setter", poi "Select All", infine "Generate". Il codice precedente è diventato:

package net.informaticalibera.automobile;

/**
 * Moto Rettilineo Uniforme
 *
 * @author Francesco Galgani, corso di sviluppo app multipiattaforma
 */
public class MRU {
    private int spazio; // metri
    private int tempo; // secondi
    private int velocita; // metri al secondo

    public int getSpazio() {
        return spazio;
    }

    public void setSpazio(int spazio) {
        this.spazio = spazio;
    }

    public int getTempo() {
        return tempo;
    }

    public void setTempo(int tempo) {
        this.tempo = tempo;
    }

    public int getVelocita() {
        return velocita;
    }

    public void setVelocita(int velocita) {
        this.velocita = velocita;
    }
    
    
}

Tecnicamente questa classe così creata è un POJO (Plain Old Java Object), termine che a volte compare nelle guide e che semplicemente indica una classe Java come questa, molte semplice, con setters e getters. Nel nostro problema, noi creeremo un oggetto "automobile" conoscendo velocità e tempo. Aggiungiamo quindi un "costruttore" che permetta di definire velocità e tempo: ricordo che i costruttori sono metodi speciali richiamati quando un oggetto viene istanziato (cioè creato).

Clicchiamo con il destro nello spazio vuoto sopra la parantesi graffa finale di chiusura. Utilizziamo di nuovo la funzione "Insert code", questa volta scegliamo "Constructor", poi selezioniamo "tempo" e "velocita". Infine clicchiamo su "Generate". ll codice completo della classe MRU diventa:

package net.informaticalibera.automobile;

/**
 * Moto Rettilineo Uniforme
 *
 * @author Francesco Galgani, corso di sviluppo app multipiattaforma
 */
public class MRU {
    private int spazio; // metri
    private int tempo; // secondi
    private int velocita; // metri al secondo

    public int getSpazio() {
        return spazio;
    }

    public void setSpazio(int spazio) {
        this.spazio = spazio;
    }

    public int getTempo() {
        return tempo;
    }

    public void setTempo(int tempo) {
        this.tempo = tempo;
    }

    public int getVelocita() {
        return velocita;
    }

    public void setVelocita(int velocita) {
        this.velocita = velocita;
    }

    public MRU(int tempo, int velocita) {
        this.tempo = tempo;
        this.velocita = velocita;
    }
     
}

Finora abbiamo definito le proprietà di MRU, ma non cosa fa. A noi interessa che permetta almeno di calcolare lo spazio. Aggiungiamo quindi il metodo seguente, subito sotto il costruttore:

    /**
     * Applica la formula spazio = velocita * tempo
     *
     * @return spazio in metri
     *
     */
    public int spazio() {
        return velocita * tempo;
    }

Avevamo già incontrato questo metodo nel precedente articolo, ma con una differenza fondamentale: in questo caso il metodo spazio() non ha bisogno di ricevere i parametri di velocità e tempo, in quanto essi sono già contenuti nella classe.

Fatto ciò, creiamo la classe "Convert", sempre all'interno dello stesso package. Abbiamo bisogno di due metodi "statici" e "pubblici", cioè utilizzabili senza bisogno di istanziare oggetti. Tecnicamente, "Convert" sarà una "helper class", e più precisamente una "utility class", secondo le definizioni riportate su Wikipedia. Nello specifico, ci servono un metodo per convertire i km/h in m/s e un altro metodo per convertire i minuti in secondi. Ecco il codice completo di questa classe:

package net.informaticalibera.automobile;

/**
 * Convertitore di unità di misura
 *
 * @author Francesco Galgani, corso di sviluppo app multipiattaforma
 */
public class Convert {

    /**
     * Converte i kilometri all'ora in metri al secondo
     *
     * @param kmh kilomentri all'ora
     * @return metri al secondo
     */
    public static float kmhToMs(float kmh) {
        return kmh / 3.6f;
    }

    /**
     * Converte minuti in secondi
     *
     * @param min minuti
     * @return secondi
     *
     */
    public static int sec(int min) {
        return min * 60;
    }

}

Da notare che avevamo già incontrato questi due metodi nel precedente articolo.

A questo punto abbiamo tutto l'occorrente per completare l'app. Nella classe principale "PrimaApp", completo il metodo start() aggiungendo una logica che specifici che cosa accade quando l'utente tocca il pulsante "calcola". 

        calcola.addActionListener(l -> {
            float kmh = Float.valueOf(velocita.getText());
            int min = Integer.valueOf(tempo.getText());
            float ms = Convert.kmhToMs(kmh);
            int sec = Convert.sec(min);
            MRU automobile = new MRU(sec, (int)ms);
            int spazioPercorso = automobile.spazio();
            risultato.setText("Spazio percorso: " + spazioPercorso + " metri");
            hi.revalidate();
            
            Log.p("Tempo: " + sec + " secondi");
            Log.p("Velocità: " + ms + " metri al secondo");
            Log.p("Spazio percorso: " + spazioPercorso + " metri");
        });

Spiegazione:

  • Al pulsante "calcola" viene aggiunto un "ActionListener", cioè un ascoltatore che viene richiamato quando il pulsante viene toccato; il codice successivo è ciò che fa l'ActionListener quando viene invocato.
  • La variabile "kmh" di tipo float (cioè numero decimale) è ottenuta dal testo inserito nel campo di testo precedentemente chiamato "velocita".
  • La variabile "min" di tipo int (cioè numero intero) è ottenuta dal tasto inserito nel campo di testo precedentemente chiamato "tempo".
  • La variabile "ms" memorizza il risultato della conversione dei kilometri orari in metri al secondo.
  • La variabile "sec" memorizza il risultato della conversione dei minuti in secondi.
  • Viene istanziato un oggetto "automobile" di tipo "MRU", passando come parametri al costruttore i secondi e i metri al secondo. Qui ho eseguito un casting dei metri al secondo da float e int (la parte decimale, se presente, andrà persa).
  • La variabile "spazioPercorso" memorizza il risultato del calcolo dello spazio percorso, calcolo eseguito dall'oggetto "automobile".
  • Il testo presente nell'etichetta "risultato", qualunque esso sia (all'inizio non c'è alcun testo), viene sostituito con il risultato dello spazio percorso.
  • Il Form esegue un "revalidate", ovvero l'interfaccia grafica viene aggiornata.
  • Le ultime tre istruzioni eseguono un logging, visibile nello screenshot seguente, in basso.

Risultato di ciò che vedo in Netbeans:

Netbeans e Codename One, corso sviluppo app

E' tutto come previsto.

Come chicca finale, proviamo a inserire un bordo arrotondato intorno al pulsante tramite CSS. Per prima cosa, i CSS in Codename One vanno abilitati, le istruzioni sono nel paragrafo Activating CSS del manuale di Codename One.

Dopo l'abilitazione, dobbiamo aprire con Netbeans il file theme.css, che si trova nella cartella CSS del progetto (in Netbeans, bisogna cliccare sul tab "Files" e poi sfogliare le cartelle per aprire il file in questione, comunque nulla vieta di aprirlo anche con un editor esterno). Inizialmente il file si presenterà così:

#Constants {
    includeNativeBool: true;
}

Spiegare il significato di questo poco codice CSS iniziale non è banale, per il momento accontentiamoci di sapere che Android e iOS hanno uno stile grafico di default diverso. La riga includeNativeBool: true; serve appunto per includere automaticamente le impostazioni grafiche di default usate dai due sistemi operativi. In linea generale questa riga deve essere sempre inclusa tra le "costanti" del tema. Esistono molte altre costanti, ma sorvoliamo.

Aggiungiamo invece il codice per mettere un bordo arrotondato intorno al pulsante. Il nostro codice diventa:

#Constants {
    includeNativeBool: true;
}

Button {
    border: 1pt #3399ff cn1-pill-border;
}

Ecco il risultato:

Codename One tutorials

Concludo con qualche piccolo abbellimento, solo a scopo dimostrativo.

Diamo al pulsante una dimensione adeguata al testo. Per far ciò, modifica la riga di codice Java:

hi.addAll(velocita, tempo, calcola, risultato);

in

hi.addAll(velocita, tempo, FlowLayout.encloseCenter(calcola), risultato);

Da notare che FlowLayout è un layout manager, cioè un algoritmo che si occupa di come i componenti di Codename One devono apparire sullo schermo. Risultato:

Codename One sviluppo app guida

Fatto ciò, voglio aggiungere l'icona di un'automobile in basso.

Per far ciò, modifico la riga:

Form hi = new Form("Automobile", BoxLayout.y());

in

Form hi = new Form("Automobile", BoxLayout.yLast());

Questa modifica significa semplicemente che l'ultimo elemento aggiunto al Form deve essere posizionato in basso.

Poi, immediamente prima di hi.show(), aggiungo la riga che serve per aggiungere l'icona:

hi.add(FlowLayout.encloseCenter(new Label(FontImage.createMaterial(FontImage.MATERIAL_DIRECTIONS_CAR, "Label", 50))));

Per il momento, senza entrare nei dettagli di tale riga di codice, mi limito a segnalare che Codename One permette di inserire facilmente, in quanto già incluse in Codename One stesso, tutte le icone che si trovano sul sito: https://material.io/resources/icons/. L'icona che ho scelto è appunto una di quelle. Risultato:

Codename One sviluppo multipiattaforma tutorial

L'intero progetto Netbeans da me usato per questo tutorial è scaricabile da questo link.
Aggiungo solo che, dal punto di vista strettamente matematico, il codice da me proposto introduce alcune approssimazioni che in alcune circostanze non rendono esatti i calcoli (soprattutto a causa dei casting), ad ogni modo, per gli scopi introduttivi di questo tutorial, non credo che ciò sia rilevante.


Esercizi - Spunti di approfondimento per imparare a usare Codename One

Quanto segue sono suggerimenti di compiti da svolgere per imparare a sviluppare app multipiattaforma con Codename One, facendo riferimento alla documentazione che già ho indicato nell'articolo precedente.

In questi esercizi, ho inserito link potenzialmente utili.

Comprendere l'ambiente di lavoro

  • Crea un progetto Codename One Hello World (Bare Bones) e installalo sul tuo telefonino.
    • A cosa serve il nome del package?
    • Perché per produrre i file apk (per Android) e ipa (per iOS) servono alcuni certificati?
    • Prova ad utilizzare sia la Dashboard sul sito di Codename One sia la Build App di Codename One. Nota che per Android esiste anche una Build App scaricabile da Google Play. È più comoda la Dashboard o l'app?
    • Inviati via e-mail il link all'apk e del file ipa tramite all'apposita opzione della Dashboard.
  • Accedi ai Javadoc di Codename One e mettiti il link nei preferiti: ti serviranno continuamente.
  • Abilita l'uso dei CSS nel progetto precedentemente creato.
  • Accedi al manuale di Codename One, trova la pagina che riguarda i CSS e mettila nei preferiti.
  • Scaricati il pdf del manuale di Codename One: potrai esserti utile per fare veloci ricerche testuali.

Assaggiamo il codice

  • A cosa servono i metodi init(), start(), stop() e destroy() presenti nell'app Hello World? (A questo proposito, suggerisco di guardare anche i capitoli gratuiti introduttivi di "Create an Uber Clone in 7 days")
  • Scrivi un commento nel metodo start()
    • Puoi usare una diversa sintassi per scrivere un commento?
    • I Javadoc sono commenti?
    • Per approfondimento, leggiti questo capitolo dedicato ai commenti e ai Javadoc.
  • Vai su qualche istruzione dell'app e prova a premere la combinazione di tasti Ctrl+space o Ctrl+p, succede qualcosa?
  • Fai in modo che, quando l'app Hello World viene eseguita, logghi un messaggio a tuo piacimento.
    • Prova ad aggiungere un'indicazione sul livello del logging, ad es. specificando che il tuo log deve avere il livello "debug".
    • Prova a cambiare, nell'init(), il livello di default del logging, specificando "info": il tuo log si vede ancora?
  • Cambia la scritta "Hello world" in qualcos'altro.
  • Sotto aggiungi un'ulteriore scritta.
  • Prova a immaginare a cosa possono servire tutte le istruzioni di questo programma di esempio, fanne una descrizione a parole
  • Apri il sito delle "material icons".
    • Cosa sono?
    • Scegli un'icona che ti piace e inserisci a sinistra di una delle due scritte
      • Non devi scaricare nulla: Codename One include al suo interno il supporto completo alle material icons di Google
  • Cambia lo stile delle due scritte, facendo in modo che differiscano nel colore e nel tipo di carattere (ad es. una grassetto e l'altra corsivo)
    • Usa i CSS
  • Commenta le due scritte
  • Fai in modo che l'app mostri, uno dopo l'altro, i numeri da 0 a 99
  • Fai in modo che l'app mostri soltanto i numeri da 0 a 99 che sono pari
  • Fai in modo che l'app contenga un casella per l'inserimento di un numero e un pulsante: quando l'utente tocca il pulsante, l'app deve scrivere se il numero è pari o è dispari (rivedi l'app di esempio usata nel presente articolo)
    • Per la casella di testo, usa un TextField
    • Per il pulsante, usa un Button
    • Tu hai bisogno che un pezzo di codice venga eseguito a seguito di una azione, in questo caso a seguito della pressione del pulsante: il metodo addActionListener di Button serve per questo
    • Per la scritta se il numero è pari o dispari, usa una Label
    • Ricorda che ogni Label ha il metodo setText()
    • Forse il metodo revalidate() di Form, in questo caso, non è strettamente necessario, però usalo comunque
      • A cosa serve?
  • Netbeans ti offre un'opzione per cambiare il codice dell'ActionListener usando una "inner class" oppure una "lambda expression"
    • Prova i due casi
    • Cambia qualcosa a livello di esecuzione?
    • Quale ti piace di più?
    • Non entriamo troppo nei dettagli, per il momento sappi che esiste (in certi casi) questa doppia modalità di scrivere il codice
  • Togli il pulsante e lascia solo la casella di testo: fai in modo che l'app scriva se il numero inserito è pari o dispari via via che l'utente lo scrive o lo modica
    • In questo caso serve un addDataChangeListener di TextField
    • Questa volta inserisci dei controlli: se l'utente cancella ciò che ha scritto oppure se inserisce una qualunque combinazione di lettere e cifre non valida (ovvero qualcosa di diverso dai numeri da 0 a 99) l'app deve mostrare un avviso con scritto "Input non valido"
  • Concludiamo questa parte complicando la questione: fai in modo che l'app, invece di scrivere se il numero di digitato (sempre da 0 a 99) è pari o dispari, scriva se è o non è primo (cioè divisibile soltanto per se stesso e per 1).
    • Aggiungi un metodo che ti restituisce un valore booleano e che prende in input un numero intero
    • Fai in modo che questo metodo verifichi che l'input sia valido, altrimenti deve lanciare una IllegalArgumentException
    • Prima di scrivere il codice di questo metodo, ragiona su qual è una modalità semplice per verificare se un numero è primo
      • Ricorda che oltre alla quattro operazioni di base, disponi anche della funzione modulo, indicata con il simbolo di percentuale %

Classi e metodi

  • Descrivi con parole tue quel che pensi di aver capito (o non capito) su che cos'è un metodo e che cos'è una classe.
    • Confronta la tua risposta con quello che è scritto qui, a pag. 54, par. 2.2 e seguenti
  • Nell'app, crea due input di testo per inserire base e altezza di un rettangolo, e un pulsante per calcolare l'area.
  • Crea una classe Rettangolo, che abbia un costruttore che accetta base e altezza e un metodo che restituisce l'area
    • Che cosa rappresenta questa classe?
    • Quali variabili ci servono?
    • Che visibilità devono avere queste variabili?
    • Di che tipo possono essere queste variabili?
    • Che differenza c'è tra classe e istanza (o entità) della classe?
  • Fai in modo che quando l'utente preme il pulsante per calcolare l'area, il suo ActionListener crei una nuova istanza di Rettangolo e ne richiami il metodo per il calcolo dell'area, mostrando il risultato in un'apposita Label.
  • Supponiamo che se l'utente non specifica la base del rettangolo, questa sia uguale a 10
    • Fai in modo che, in questa evenienza, l'ActionListener richiami un costruttore di Rettangolo a cui sia sufficiente passare un solo argomento
    • Non cancellare il costruttore che già c'è, ma aggiungine un altro
    • È quindi possibile avere più metodi (e costruttori) con lo stesso nome, ma con parametri diversi? (overloading)
    • A cosa può servire questa possibilità?
  • Nella classe Rettangolo, cancella i due costruttori che hai creato a mano e ricreali automaticamente con l'opzione Insert code di Netbeans
    • Cancella solo i costruttori e non le variabili
  • Modica l'app in modo che calcoli l'area di un quadrato
    • L'utente deve quindi solo inserire un lato
    • Geometricamente un quadrato è un rettangolo con tutti i lati uguali
    • Anche a livello di codice, una quadrato è un rettangolo, quindi...
      • Crea una nuova classe Quadrato che estende la classe Rettangolo (questo si chiama overriding)
      • Ragiona sulla visibilità delle variabili: hai bisogno che le variabili di Rettangolo siano visibili a Quadrato, o puoi farne a meno?
      • Prova a scrivere il codice usando ognuno dei quattro livelli di visibilità possibili (livello di package, privato, pubblico, protected).
  • Allo stato attuale del codice, è possibile modificare il lato di Quadrato dopo aver creato un oggetto di tipo Quadrato?
    • Dopo aver chiarito perché sì o perché no, aggiungi i metodi getter e setter.
    • I metodi getter e setter possono essere aggiunti manualmente o con l'opzione Insert code di Netbeans: prova entrambe queste possibilità.
    • A questo punto, è necessario istanziare ogni volta un nuovo Quadrato per calcolarne l'area, oppure è possibile fare diversamente?
  • Rifletti su quanto hai provato ora: secondo te, in quali casi può essere utile avere più oggetti di una stessa classe e in quali un solo oggetto (singleton)?
  • Ci sono dei casi in cui può essere utile non avere neanche un'istanza di una classe, ovvero richiamare direttamente i metodi della classe senza bisogno di istanziarla?
    • Prova a scrivere un esempio di classe con un costruttore "privato" e un metodo "static" per calcolare l'area di un cerchio conoscendone il raggio: questa classe può essere istanziata? Il metodo static è utilizzabile? Prova ad utilizzarlo nell'app per fare in modo che venga visualizzata l'area conoscendo il raggio immesso dall'utente.
  • Nell'app cancella il campo di testo e il pulsante; inserisci poi tre nuovi pulsanti con scritto Tromba, Tamburo, Chitarra
    • Cosa hanno in comune una tromba, un tamburo e una chitarra?
    • Costruisci una interfaccia StrumentoMusicale che contiene il metodo produciSuono(), il quale restituisce una stringa di testo con l'onomatopea del suono dello strumento.
    • Crea tre classi Tromba, Tamburo e Chitarra che implementano la classe StrumentoMusicale.
    • Fai in modo che, quando l'utente tocca un pulsante dell'app relativo ad uno strumento, venga visualizzata la stringa restituita da produciSuono()
  • Direi che può bastare. Prova ad esporre tutto ciò che hai imparato sulle classi.

4 Letture teoriche e prove pratiche

Stila un elenco di domande su ciò che non ti è chiaro, poi prova a cercare le risposte nella documentazione da me indicata e più in generale su Internet. Creati dei casi di esempio (test case) in cui sperimentare quello che stai imparando e cercando di capire. Se qualcosa continua a non esserti chiaro, chiedi in Rete: in particolare, per porre le tue domande, comincia a familiarizzare con Stack Overflow, supporto indispensabile per qualsiasi programmatore.

Localizzare l'app

  • Crea un nuova app con una scritta, che dovrà apparire in italiano o in inglese a seconda della lingua del telefonino.
    • Dai un'occhiata al metodo UIManager.getInstance().setBundle()
    • Perché ho usato UIManager.getInstance().setBundle() e non UIManager.setBundle()?
    • Prova a creare la mappa di stringhe di cui hai bisogno nella maniera più semplice possibile. Vedi questa sezione del manuale.

Esploriamo i layout

  • Crea 10 Container di colori diversi
  • Leggi questa sezione del manuale
  • Disponili usando:
    • BoxLayout.y
    • BoxLayout.x
    • FlowLayout
    • BorderLayout
    • LayeredLayout
  • Prova a ricreare la demo dei layout disponibile nell'app Kitchen Sink, disponibile nelle demo di Codename One.
  • Guarda un'app qualsiasi e prova a ragionare su come è possibile combinare tra loro i layout per ottenere una determinata interfaccia grafica

6.1 Un layout specico soltanto per l'immissione di dati

  • Prova a creare un'app che visualizzi una serie di campi di input usando un TextModeLayout. Come riferimento, leggi l'articolo Pixel Perfect - Text Input (Part 2) - Codename One.
    • Come cambia la visualizzazione su Android e iPhone? Usa il simulatore per vedere la differenza.
  • Aggiungi dei controlli sulla validità degli input, ad es. la password deve essere di almeno di otto caratteri.
  • Aggiungi un pulsante di invio e fai in modo che questo rimanga disabilitato nché tutti gli input non risultano validi.

Esploriamo i CSS

  • Scegli una schermata di un'app che tu vuoi ricreare con Codename One
  • Ricreala davvero, usando i layout visti precedentemente in combinazione con i CSS forniti da Codename One
  • Ripeti questo esercizio più volte, scegliendo altre schermate.

8 Memorizzazione dei dati sul file system

  • Leggi la sezione del manuale intitolata File System, Storage, Network & Parsing per avere un punto di riferimento.
  • Crea un'app che contiene soltanto un checkbox: fai in modo che l'app si ricordi la selezione o deselezione di tale checkbox, anche dopo essere stata killata e riaperta, usando la classe Preferences.
  • Crea un'app che permetta di scattare una fotografia e di visualizzare la fotografia scattata: tale foto deve essere salvata in maniera permanente nel FileSystem.
  • Crea un'app che visualizzi una qualsiasi immagine presa da Internet, specificandone l'url e usando un sistema di cache (usa URLImage).

Properties are amazing

  • Leggi Codename One Properties Are Amazing, è un punto di riferimento.
  • Crea un'app che visualizzi una serie di campi per inserire alcuni dati (ad es. dati anagrafici) usando un TextModeLayout, fai in modo che alla pressione di un pulsante Invia questi campi vengano memorizzati in un PropertyBusinessObject e convertiti in un file JSON, il quale sarà mostrato all'utente.
  • Fai in modo che questa app salvi nello Storage il PropertyBusinessObject dopo la pressione del pulsante Invia e che ricarichi dallo Storage il PropertyBusinessObject ogni volta che l'app viene di nuovo inizializzata
    • Come ti comporteresti se uno di questi campi fosse una password?

10 Threading

  • Per questa parte, fai riferimento alle lezioni gratuite della Codename One Academy
  • Cos'è il threading?
  • Cos'è l'EDT di Codename One?
  • Crea un'app con un scritta e un timer che dopo cinque secondi cambia quella scritta
    • Usa sia UITimer sia Timer
    • I timer in questione vengono eseguito nell'EDT o fuori dall'EDT?
    • C'è una eccezione EDTViolation?
    • Come si fa a porre rimedio a una EDTViolation?
  • Crea un'app che esegua una REST call asincrona, ad es. per scaricare un file JSON o una stringa di testo
    • La connessione avviene nell'EDT o fuori dall'EDT?
    • Fai in modo che l'app logghi se la connessione è andata o no a buon ne.

11 Usare le estensioni di Codename One

  • Crea un nuova app.
  • Tra le estensioni di Codename One ce n'è una che permette di identificare marca e modello del telefonino: si chiama "Device", la trovi nell'Extension Manager, installa questa estensione nell'app che hai appena creato.
  • Prova ad usarla leggendo la documentazione, ad es. loggando marca e modello del telefonino.

12 Le interfacce native

Questo è un argomento eccezionalmente complesso che va oltre Java, per ora limitati a prendere visione di questo tutorial.

13 Spring Boot

Questa è la parte lato server, che quindi non riguarda più Codename One. Per il momento lascio questa parte in sospeso, magari la affronterò in futuri articoli, tu intanto puoi iniziare a cercare documentazione in Rete. Nota che nella Codename One Academy ci sono alcune lezioni (nei corsi a pagamento) che spiegano come usare Spring Boot congiuntamente a Codename One; in tali lezioni, viene anche affrontata la configurazione di un VPS. Spring Boot viene anche citato e affrontato nel libro "Create an Uber Clone in 7 days".

Francesco Galgani,
20 dicembre 2019

Classificazione: