Primi esempi di interfacce grafiche con Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it © 2012 - CEFRIEL
The present original document was produced by CEFRIEL and the Teacher for the benefit and internal use of this course, and nobody else may claim any right or paternity on it. No right to use the document for any purpose other than the Intended purpose and no right to distribute, disclose, release, furnish or disseminate it or a part of it in any way or form to anyone without the prior express written consent of CEFRIEL and the Teacher. © copyright Cefriel and the Teacher-Milan-Italy-23/06/2008. All rights reserved in accordance with rule of law and international agreements. © 2012 - CEFRIEL
Sommario MVC pattern Tutto in XML Widget e container SLIDE CONTENUTO MVC pattern Perché preoccuparsene, come implementarlo Tutto in XML Come descrivere interfacce grafiche in XML: basi Widget e container Esempi di utilizzo di widget e container in XML Interagire con l’utente Esempi di eventi e accesso ai dati della UI Logging & Debugging Loggare per sapere, debuggare le proprie app © 2012 - CEFRIEL
I design pattern (GRASP, GoF, altri) Cosa sono i design pattern? Soluzioni efficaci a problemi frequenti; le trovate «preconfezionate» in libri (GoF), su forum; operano a diversi livelli (classe, collaborazione, framework, componenti). GRASP: sono più linee guida che design pattern; low copuling & high coesion sono i più importanti. GoF: dal libro «Design patterns» della gang of four; si dividono in strutturali, creazionali, comportamentali; descrivono soluzioni a livello di collaborazione. MVC (Model View Control), MVP (Model View Presenter): definiscono una architettura astratta in vengono separati: il modello dei dati su cui l’applicazione opera; la/le viste dei dati da mostrare all’utente; le operazioni da svolgere in risposta agli eventi. Non comprende la logica di business!!! © 2012 - CEFRIEL
MVC e MVP A programmare seguendo l’istinto: si finisce per ottenere l’anti-pattern SmartGUI; l’applicazione cresce attorno all’interfaccia grafica; alla fine non saremo più in grado di staccare le funzionalità dall’interfaccia (se non con molto sforzo). Model e logica di business: l’applicazione dovrebbe nascere sulla carta in UML; il modello concettuale (dei dati) dovrebbe guidarci; ("Model Driven Design", Evans) la logica di business dovrebbe venir realizzata PRIMA dell’interfaccia di interazione con l’utente; realizzare sia una GUI che una CLI aiuta a separare. MVC e MVP vengono dopo: sono pattern da applicare quando il core esiste già; servono per costruire l’interfaccia (grafica); © 2012 - CEFRIEL
MVC e MVP MVC: MVP (view attiva/passiva): Model DB Controller View Logica di business MVC: il modello rappresenta i dati, eventualmente da DB; le viste mostrano dati e strumenti di interazione; i controller interagiscono con le viste e i dati per richiamare la logica di business. MVP (view attiva/passiva): riguarda la presentazione dei dati (non l’interazione); arricchisce l’interazione tra modello e vista; il presenter manipola i dati perché possano essere presentati correttamente; Model View Presenter © 2012 - CEFRIEL
MVC in Android DB Meglio se generata da file XML in /res/layout Model View JavaBeans android.view.View Controller Contengono dati accessibili come property (vedi dopo) Activity © 2012 - CEFRIEL
Un primo esempio con layout in XML Scopo: realizzare una semplice calcolatrice che effettui i calcoli su espressioni contenenti solo interi relativi e frazioni, esprimendo il risultato come una frazione. Architettura: decidiamo di dividere in due parti il «progetto»: libreria core di calcolo (in grado di parsare una stringa, ed effettuare i calcoli); applicazione Android con una semplice interfaccia classica da calcolatrice. L’interfaccia comprenderà: un layout XML in cui vengono descritti i widget da includere nell’interfaccia; un’attività che esegue le azioni richieste dagli eventi (aggiungere caratteri, fare conti). © 2012 - CEFRIEL
Un layout da calcolatrice <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <EditText android:id="@+id/display" android:text="0" android:textColor="#000000" android:maxLines="5" android:enabled="false" android:editable="false" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <TableLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchColumns="0,1,2,3"> <TableRow> <Button android:id="@+id/canc" android:text="<="/> <Button android:id="@+id/clear" android:text="C"/> <Button android:id="@+id/compute" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_span="2" android:text="="/> </TableRow> </TableLayout> </LinearLayout> © 2012 - CEFRIEL
Ascoltare gli eventi @Override public void onCreate(Bundle savedInstanceState) { // Initialization: super.onCreate(savedInstanceState); setContentView(R.layout.main); // Connecting the event listeners: ((Button)findViewById(R.id.compute)).setOnClickListener(onCompute); ((Button)findViewById(R.id.num_0)).setOnClickListener(onAddChar); … ((Button)findViewById(R.id.par_close)).setOnClickListener(onAddChar); ((Button)findViewById(R.id.canc)).setOnClickListener(onCanc); ((Button)findViewById(R.id.clear)).setOnClickListener(onClear); } Scelgo il layout XML da utilizzare in questa attività Si osservi come vengono reperiti i widget dalla vista Connetto gli eventi utilizzando dei miei listener (vedere dopo) © 2012 - CEFRIEL
Un ascoltatore private OnClickListener onCompute = new OnClickListener() { public void onClick(View button) { try { // No errors up to now: ((EditText)findViewById(R.id.display)).setTextColor(Color.BLACK); // Obtaining the expression: CharSequence expr = ((EditText)findViewById(R.id.display)).getText(); // Computing: Fraction res = Calculator.compute(new StringBuilder(expr).toString()); // Setting the text result: ((EditText)findViewById(R.id.display)).setText(res.toString()); isInitial = true; // Done: } catch (Throwable t) { ((EditText)findViewById(R.id.display)).setTextColor(Color.RED); } }; Se non ci sono errori il testo è nero Se ci sono errori il testo è rosso © 2012 - CEFRIEL
Per la soluzione vedere in FractionCalc2 Proviamola Provate a compilare ed installare (nell’emulatore o anche nel dispositivo reale); su emulatore provate il debuger; osservate cosa avviene sulla vista. Esercizio: provate a modificare gli attributi dei widgetper vedere che effetto hanno. aggiungete una checkbox con cui decidere se mostrare il risultato come frazione o decimale; per farlo dovete utilizzare: il widget CheckBox; OnCheckedChangeListener Per la soluzione vedere in FractionCalc2 © 2012 - CEFRIEL
A grande successo.. nuova release! Associare un’icona all’app: creare l’icona a risoluzioni diverse: in res/drawable 48x48 in res/drawable-ldpi 36x36 in res/drawable-hdpi 72x72 modificare AndroidManifest.xml: <application android:label="@string/app_name" android:icon="@drawable/fraction_calc_icon"> NOTA: nei nomi di risorse solo [a-z], [0-9], ‘_’ o ‘.’. Modificare versione mostrata ed effettiva (codice): <manifest … package="com.gab.tests.android.fractioncalc" android:versionCode="2" android:versionName="1.1"> 1.1 verrà mostrato (nel market), 2 è un intero incrementale © 2012 - CEFRIEL
Ho i tasti sul telefono.. Perché non usarli? Ogni attività può ricevere tanti eventi! Il tocco dello schermo viene usato di frequente… …ma se ci sono tasti VERI vengono usati volentieri! @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_0: onAddChar.onClick(findViewById(R.id.num_0)); break; case KeyEvent.KEYCODE_9: onAddChar.onClick(findViewById(R.id.num_9)); break; case KeyEvent.KEYCODE_PLUS: onAddChar.onClick(findViewById(R.id.op_PLUS)); break; case KeyEvent.KEYCODE_EQUALS: onCompute.onClick(findViewById(R.id.compute)); break; case KeyEvent.KEYCODE_BACK: onCanc.onClick(findViewById(R.id.canc)); break; default: return super.onKeyUp(keyCode, event); } return true; © 2012 - CEFRIEL
Inseriamo un’icona sul pulsante <= Soluzione 1: su ogni widget possiamo definire la proprietà «background» con una risorsa «drawable»; <Button android:id="@+id/canc" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:drawable/ic_input_delete"/> NOTA: invece che riferirci a un drawable “nostro” ci siamo riferiti a uno predefinito: @android:drawable/ic_input_delete per un elenco si vedano nell’SDK: android-sdk\platforms\android-10\data\res\drawable-ldpi android-sdk\platforms\android-10\data\res\drawable-hdpi Problemi con questa soluzione: immagine adattata alle dimensioni del pulsante (provate); noi vorremmo sovrapporre l’icona sul pulsante. © 2012 - CEFRIEL
Inseriamo un’icona sul pulsante <= Soluzione 2: al posto del pulsante inseriamo un layout relativo; all’interno del layout inseriamo: il pulsante (riempiendo tutto lo spazio); l’immagine (centrata ma non adattata). Ne risulta: <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/canc" android:layout_width="fill_parent" android:layout_height="fill_parent"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@android:drawable/ic_input_delete" android:layout_centerInParent="true"/> </RelativeLayout> Soluzione 3: utilizzare un ImageButton (con src il drawable). © 2012 - CEFRIEL
Ruotare il dispositivo e… …il layout non va più bene! (nell’emulatore provare con CTRL+F12 (sinistro) Per orientazioni diverse: non serve cambiare controller; servono view differenti. Creiamo la cartella per i layout XML in versione landscape: res/layout-land Creiamoci dentro un layout alternativo per il landscape: tabella 5x4 Giochiamo con l’orientazione © 2012 - CEFRIEL
Essendo noti sfruttiamo numero righe/colonne Layout alternativo Altri strumenti esistono per livelli di API più alti: dalle API14 esiste il GridLayout: vedi in altriEsempi/xml/main_conGrid.xml occhio al range di dispositivi che volete supportare; L’esempio con GridLayout: <GridLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:columnCount="5" android:rowCount="4"> … <Button android:id="@+id/compute" android:layout_rowSpan="2" android:layout_gravity="fill" android:text="="/> </GridLayout> Essendo noti sfruttiamo numero righe/colonne Usiamo rowSpan per riempire 2 righe, gravity fill per allargare il Button © 2012 - CEFRIEL
Se sappiamo cos’è successo.. diciamolo! Mostriamo una dialog di errore: se c’è una divisione per zero; negli altri casi (problema di sintassi); Più modi per mostrare una dialog: utilizzando l’XML per descriverne una, ed eseguendo showDialog; istanziandola ed invocando show; con DialogFragment. Il primo è deprecato (non usare); Vediamo il secondo: final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setButton("Chiudi", new android.content.DialogInterface.OnClickListener() { public void onClick(DialogInterface dlgInt, int button) { dialog.hide(); } }); dialog.setTitle(title); dialog.setMessage(message); dialog.show(); © 2012 - CEFRIEL
Logging per sapere cosa succede Come loggare: da specificare le seguenti informazioni: livello del log (verbose, debug, info, warning, error, wtf(!!!)…); CHI genera il log (classe? Package? Stringa a scelta); COSA è successo (messaggio di log). utilizzare la classe Log (metodi statici): se bastano i metodi v, d, i, w, e, wtf; println è il metodo «raw» per scrivere log; Come loggare in maniera furba: la generazione di log è fastidiosa e onerosa; centralizzandola la si può controllare! si veda la classe Logger in FractionCalc2. Il compilatore e l’ottimizzatore SANNO il fatto loro! E per leggere i log? adb logcat FractionCalc:i *:s (filtrato, livelli [v,d,i,w,e,f,s]) © 2012 - CEFRIEL
Logging: cosa succede? © 2012 - CEFRIEL
Debugging: step-by-step, monitor Per poter eseguire il debug su dispositivo: abilitarlo sul dispositivo da impostazioniapplicazioni abilitarlo nell’applicazione (attributo del manifest): <application android:label="@string/app_name" android:icon="@drawable/fraction_calc_icon" android:debuggable="true"> in NetBeans, build e debug: per l’emulatore se non abbiamo ancora provato… è l’ora di farlo! per il dispositivo fisico richiede la presenza del dispositivo, ma volte è più rapido. Un monitor con cui accedere al device: nell’sdk (dir tools, deve essere nel path) troviamo: DDMS (Dalvik Debug Monitor Service)… …proviamo a lanciarlo dalla linea di comando! © 2012 - CEFRIEL
DDMS DDMS comunica col device tramite: DDMS permette di eseguire: servizio demone adb-server; TCP/IP con forward su USB DDMS permette di eseguire: monitoring di ram, processi, thread, allocazione, gc, radio, gps, …; cattura di screen-shot; interazione con il FS. © 2012 - CEFRIEL
Debug delle interfacce grafiche Analizzare l’albero delle view: ci viene fornito lo strumento hierarchyviewer: cattura un’istantanea dello stato dell’albero; ci permette di vedere: come è strutturato l’albero delle view; per ogni nodo: posizione e dimensioni; uno sketch dell’interfaccia grafica; lo screen-shot nodo per nodo; Immagine del tutto. © 2012 - CEFRIEL
Testing Lo unit testing serve a testare in automatico e ripetibilmente la propria applicazione: simulare dei casi d’uso dell’applicazione (scimmia); testare una porzione di applicazione (componenti). Facciamo correre la scimmia: in modalità pseudo-random: adb shell monkey -p com.gab.tests.android.fractioncalc 1000 ammaestrandone una col pitone: comando monkeyrunner per eseguire uno script python; un esempio di seguito, ma si può fare di tutto: from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice device = MonkeyRunner.waitForConnection() package = 'com.gab.tests.android.fractioncalc' activity = package + '.FractionCalc' device.startActivity(component=package + '/' + activity) device.touch(50,200,MonkeyDevice.DOWN_AND_UP) # 1 device.touch(50,300,MonkeyDevice.DOWN_AND_UP) # 7 … Ottengo il device Avvio l’applicazione Simulazione tocco © 2012 - CEFRIEL
Come funziona l’Android Unit Testing Nostra applicazione in esecuzione Tools per accedere agli internals Pacchetti di test che usano gli strumenti © 2012 - CEFRIEL
Unit Testing Creazione di un progetto di test: utilizziamo questa volta i tool da riga di comando: dalla directory radice del progetto da testare: android create test-project -m ../../FractionCalc2 -n FractionCalc2Test -p tests viene generata una dir tests con un progetto di testing: vengono creati per noi (tra il resto): AndoridManifest.xml vediamo cosa contiene FractionCalcTest.java vediamo cosa contiene Esecuzione dei test: prima di poter eseguire con successo dei test: dobbiamo strumentare i test…; … e scrivere le unità di testing automatico. per strumentare ed eseguire i nostri test: ant/bin deve essere nel PATH © 2012 - CEFRIEL
Preparare ed eseguire i test: Unit Testing Preparare ed eseguire i test: dobbiamo prima strumentare l’applicazione di test: ant instrument install poi possiamo eseguire una prima prova: adb shell am instrument -w -e class com.gab.tests.android.fractioncalc.FractionCalcTest com.gab.tests.android.fractioncalc.tests/android.test.InstrumentationTestRunner NOTA: fallisce perché non ci sono unità. Unità di testing: Android testing framework estende JUnit; esistono unità di test (foglie) e suite di test (rami); è possibile definire una vera e propria foresta di test; ogni unità di test comprende più test più le fixtures. © 2012 - CEFRIEL
Preparare le unità di test Ci è stata preparata la classe FractionCalcTest: implementato per Android, eredita da JUnit: ActivityInstrumentationTestCase2<FractionCalc> ActivityTestCase InstrumentationTestCase TestCase si prepara per lavorare con la nostra attività main: public FractionCalcTest() { super("com.gab.tests.android.fractioncalc", FractionCalc.class); } dobbiamo aggiungere i test e ciò che ci sta attorno: Activity frac; public void setUp() { frac = getActivity(); } public void tearDown() { frac.finish(); } public void testExample() { sendKeys("1 7 SLASH 2 MINUS 2 SLASH 4 EQUALS"); CharSequence text = ((EditText)frac.findViewById(R.id.display)).getText(); assertEquals("Result of 17/2-2/4=", "8", new StringBuilder(text).toString()); } © 2012 - CEFRIEL
L’esecuzione dei test Vanno installati ogni volta: ant instrument install ant test … -test-project-check: test: [echo] Running tests ... [exec] [exec] com.gab.tests.android.fractioncalc.FractionCalcTest:. [exec] Test results for InstrumentationTestRunner=. [exec] Time: 3.263 [exec] OK (1 test) BUILD SUCCESSFUL Total time: 8 seconds Provate ad osservare l’emulatore durante il test!!! © 2012 - CEFRIEL
Cosa abbiamo visto sino ad ora? Da qui dove si va? Cosa abbiamo visto sino ad ora? Abbiamo «giocato» con una prima applicazione; abbiamo costruito una GUI e ascoltato degli eventi; abbiamo provato ad estenderla (versione 2); abbiamo scoperto come fare testing automatico e debugging della nostra applicazione (strumenti). Cosa manca? Non abbiamo formalizzato COSA una attività sia; non abbiamo esplorato l’universo dei widget; in Android non esistono mica solo le GUI!!! Continuiamo a giocare! © 2012 - CEFRIEL