Fix GPS Android

https://productforums.google.com/forum/#!topic/nexus/K43H6Bunx2U

Hi, after the 4.4.4 upgrade on my Nexus 4, every time I open Google maps takes forever to find me and if I try to use navigation or any other app (such as tomtom) I keep hearing the same message: gps signal lost.

I contacted Google directly and got some feedback today which I’ll try:
If you are having trouble with GPS on your device, try the following: (hope it solves it)

Step 1: If your device is rooted or modified, revert your device to original factory image.
– Troubleshooting steps may not work on rooted/modified devices.
– You can re-image your device with the instructions from the following link: https://developers.google.com/android/nexus/images

Step 2: To wipe the cache partition, do the following:
1. Go into the device’s ‘Settings.’
2. Under the Device section, tap ‘Storage.’
3. Scroll to the bottom and tap ‘Cached data.’
4. Touch ‘OK’ to confirm.

Step 3: Restart the device in safe mode
– With the device on, hold the ‘Power’ button to get the ‘Power off’ prompt.
– Touch and hold ‘Power off’ until the ‘Reboot to safe mode’ prompt appears.
– Touch ‘OK’ to reboot in safe mode.
– Note: To exit safe mode later, simply restart the device normally.

Step 4: Check location settings on the device.
– Go to “Settings” on the device.
– If using a phone, swipe down the screen with two fingers, then touch “Settings.”
– If using a tablet, swipe down the screen from the right side of the top of the screen, then touch “Settings.”
– Under “Personal,” touch “Location access.”
– Ensure that Location slider at the top right is turned on. You may need to agree to “Location consent” when turning this feature on.
– Touch “Mode” and select “Device only.”
– Although Wi-Fi can improve location services and GPS functionality, we are strictly testing the GPS function.

Step 5: Calibrate and test GPS
– If possible, ensure your device has a clear view of the sky. GPS may not work in some buildings, in a parking garage, surrounded by buildings, in the mountains, underground, etc.
– Hold the device out securely in one hand.
– Move the device in a Figure 8 pattern. and repeat this motion several times.
– After a minute (ideally a few minutes), open Google Maps.
– When the app loads, touch the location button on the bottom right of the map.
– A pop-up may appear asking to “Improve your location.” This is because GPS is not using Wi-Fi or data to help. Touch “Cancel.”
– The app should zoom in on your location.
– Determine if your location is accurate (you may need to zoom out a bit).
– While holding your device securely, if you rotate and point your device to different directions, the arrow on your blue location dot should also rotate to match which direction your device is facing.

********************************************************************************************

I had the signal lost, no GPS, and then no internet.

After a whole week of trying several suggestions in the net, e.g. restart to Safe Mode, “reset [APN] to default”, and others, I found a new suggestion in some UK website for Android. Just adding a “new” APN with a new name got the GPS and internet back (Mobile Network, APNs, then touch the + sign to add a new APN). I tried the browsers and the navigation; all worked a bless. Later, I did a “Reset to default” for APN and it worked fine, as well; looks like adding the new APN (or a new one with a reset to default) removed some bad codes.

Waze

Ho usato fino ad oggi iCoyote (versione 4.5 su Android), ma poi è apparsa una finestrella che diceva che il periodo di prova era scaduto e avrei dovuto scaricare (pagandola 1,59€) una nuova versione e poi avrei dovuto abbonarmi al servzio pagando 11,99€ per un mese oppure 49,99€ per 6 mesi.

Ho immediatamente disinstallato iCoyote ^_^ .

Già avevo il navigatore Waze installato su Android, che per certi versi somiglia a iCoyote, solo che fino ad oggi ho sempre usato quest’ ultimo perchè mi sembrava un buon programma.

Visto che iCoyote mi ha lasciato a piedi, ho fatto un’ altra prova con Waze: devo dire che mi è piaciuto molto, le mappe sono accurate, ci sono i segnalini che indicano la posizione degli altri utenti, ci sono le segnalazioni di autovelox, ingorghi, ecc….. in più c’è l’ indicazione della velocità del traffico sui tratti di strada visualizzati !

Certo, qualcosa di negativo c’è: con iCoyote si poteva disattivare la vista sulla mappa per risparmiare sul traffico dati, con Waze non mi sembra di averla vista (sono ancora in fase di esperimenti).
Per quanto riguarda la privacy, entrambi i navigatori trasmettono la propria posizione ai server dei fornitori del servizio, per cui bisogna “fare a fidarsi”……

SQLite con Android

Il SO Android mette a disposizione gli strumenti per operare con database SQLite: per gestirli è sufficiente implementare una classe “helper”.

Per prima cosa creiamo un’ interfaccia in cui memorizzare i nomi delle colonne e delle tavole del database, per non doverli scrivere ogni volta: estendiamo l’ interfaccia BaseColumns

public interface ProvinciaTable extends BaseColumns {
	String TABLE_NAME = "province";
	String CODICE = "codice";
	String NOME = "nome";

	String[] COLUMNS = new String[] {_ID, CODICE, NOME};
}

si ricordi che in una interfaccia una costante è implicitamente public static final.

Creazione e popolamento del database

L’ SDK ci fornisce la classe astratta SQLiteOpenHelper per gestire “facilmente” i database SQLite: i suoi metodi onCreate e onUpgrade sono astratti, quindi siamo obbligati ad implementarli.
La classe si prenderà cura di aprire il database (se esiste), di crearlo (se non esiste), e di aggiornarlo: userà le transazioni per avere il database sempre in uno stato sensibile.

Vediamo il costruttore:

public class DatabaseHelper extends SQLiteOpenHelper {

	private static final String DATABASE_NAME = "devAPP.db";
	private static final int SCHEMA_VERSION = 1;

	public DatabaseHelper(Context context) {
		super(context, DATABASE_NAME, null, SCHEMA_VERSION);
	}
	//...
}

Lo schema_version serve per gestire gli aggiornamenti del database.
Vediamo ora il callback onCreate, che viene chiamato soltanto quando un database deve essere creato:

public void onCreate(SQLiteDatabase db) {
	// formatto una stringa sql, come si fa con printf in C, con i comandi per creare il database
	String sql = "CREATE TABLE {0} ({1} INTEGER PRIMARY KEY AUTOINCREMENT, {2} TEXT NOT NULL,{3} TEXT NOT NULL);";
	db.execSQL(MessageFormat.format(sql, ProvinciaTable.TABLE_NAME, ProvinciaTable._ID, ProvinciaTable.CODICE, ProvinciaTable.NOME));

	inserisciProvincia(db, "Agrigento", "AG");
	inserisciProvincia(db, "Alessandria", "AL");
	inserisciProvincia(db, "Ancona", "AN");
	//...
	inserisciProvincia(db, "Viterbo", "VT");
}

Per inserire i valori usiamo questo metodo:

private void inserisciProvincia(SQLiteDatabase db, String nome, String codice) {
	ContentValues v = new ContentValues();
	v.put(ProvinciaTable.CODICE, codice);
	v.put(ProvinciaTable.NOME, nome);
	db.insert(ProvinciaTable.TABLE_NAME, null, v);
}

il metodo insert della classe SQLiteDatabase richiede il terzo parametro di tipo ContentValues: questo serve a “mappare” un dato nella rispettiva colonna.
Il secondo parametro invece è opzionale e riguarda la gestione dell’ inserimento di una riga di dati nulli: se posto a true, fa si che vengano inseriti dei NULL nel database invece di restituire errore.

Esecuzione di una query

I metodi finora visti servono a creare e popolare il database, ma ancora non abbiamo incontrato un evento che possa chiamarli.

La classe SQLiteOpenHelper offre due metodi per accedere al database:

  • public synchronized SQLiteDatabase getReadableDatabase ()
  • public synchronized SQLiteDatabase getWritableDatabase ()

Il primo metodo crea o apre il database e restituisce lo stesso oggetto che restituirebbe getWritableDatabase, a meno che non sorga un problema che costringe ad aprire il database in sola lettura (ad esempio un disco pieno).
Il secondo metodo è più restrittivo: crea o apre il database, ma richiede esclusivamente un accesso in scrittura, e lancia una SQLiteException se ciò non è possibile.

Dunque, per accedere al database (eventualmente dopo averlo creato ex-novo), è necessario chiamare uno dei due metodi ora visti.
Implemento allora un nuovo metodo che chiamerò dalla Activity principale:

public Cursor getProvince() {
	return (getReadableDatabase().query(
		ProvinciaTable.TABLE_NAME, ProvinciaTable.COLUMNS, null, null, null, null, ProvinciaTable.NOME));
}

dentro questo metodo avviene la chiamata a getReadableDatabase() che ci assicura un accesso al database (sarebbe più raffinato creare un blocco try/catch SQLiteException per controllare eventuali errori), ed effettuo una query sul SQLiteDatabase restituito.

Tra i vari metodi query uso

public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)

i cui parametri sono:

  • il nome della tabella
  • l’ array con i nomi delle colonne (vedere interfaccia creata all’ inizio)
  • 4 parametri per definire la clausola WHERE, argomenti per il bind, la clausola GROUP BY, e la clausola HAVING
  • la clausola ORDER BY (che nello snippet ho impostato a ProvinciaTable.NOME)

L’ oggetto restituito dalla query è Cursor (che ricorda il database handler del PHP): infatti nella Activity dovremo ciclare su di esso per estrarre le righe.
Vediamo come si estraggono i dati:

// siamo nella Activity principale
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	// istanziamo l' helper della connessione
	DatabaseHelper databaseHelper = new DatabaseHelper(this);
	// eseguiamo la query
	Cursor c = databaseHelper.getProvince();

	try {
		while (c.moveToNext()) {
			Log.d("devAPP", c.getLong(0) + " " + c.getString(1) + " " + c.getString(2));
		}
	} finally {
		c.close();
	}
}

il codice è abbastanza semplice ed autoesplicativo.
Faccio solo notare i metodi moveToNext, getLong, e getString: il primo serve a muovere il cursore tra il recordset restituito dalla query (vedere la classe Cursor per altri metodi simili); gli altri due leggono i dati rispettivamente di tipo numerico (Long) e di tipo String.
Infatti la query viene eseguita sulle colonne ProvinciaTable.COLUMNS (vedere funzione getProvince()) che sono definite nell’ interfaccia (vedere all ‘inizio) ProvinciaTable come

String[] COLUMNS = new String[] {_ID, CODICE, NOME};

Conclusione

Al lancio dell’ applicazione viene istanziato il nostro DatabaseHelper, poi viene eseguita la query tramite databaseHelper.getProvince(): questa funzione chiama getReadableDatabase() che, se non trova il database, lo crea ex-novo, e lo fa chiamando il callback onCreate di DatabaseHelper.

Nella Activity ho usato la funzione Log.d(…) della classe Log: questa scrive nel log di Eclipse; per vederlo andare nel menu Window->Show View->Other e in “Android” selezionare Log Cat.

Android SDK, Fragment

I Fragment sono oggetti molto simili alle Activity, solo che possono essere inseriti e rimossi dinamicamente, e possono esserne presenti più di uno nella stessa schermata.

Come inserire Fragment

Un modo per usare i Fragment è quello di creare un layout XML apposito: res/layout/news_article.xml e il suo elemento root sia <FrameLayout>:

<?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

    </FrameLayout>

questo elemento sarà il contenitore dei frammenti che saranno aggiunti programmaticamente.
C’è anche la possibilità di creare layout con frammenti per mezzo di XML, ma non sarà possibile poi modificarlo dinamicamente.

Per inserire questo layout nell’ Activity, usare il comando setContentView:

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);
    ....

Per riempire il FrameLayout si opera in questo modo:

  • si prende il FragmentManager dell’ Activity in cui si trova il layout
  • si inizia una transazione
  • si eseguono le operazioni (add, replace, remove)
  • si fa il commit

ad esempio:

FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, headerFragment, "headerFragment").commit();

La funzione FragmentTransaction.add(int containerViewId, Fragment fragment, String tag) ha 3 parametri:

  1. un int per l’ id del container
  2. un Fragment da inserire: può anche essere una sottoclasse con metodi sovraccarichi
  3. una Stringa per identificare il frammento per recuperarlo in seguito con FragmentManager.findFragmentByTag(String)

Sostituire Fragment

Si opera come per aggiungere un Fragment, con l’ aggiunta di un nuovo comando: transaction.addToBackStack(null); che serve a salvare nel BackStack il frammento rimosso; il BackStack è gestito dall’ Activity e permette all’ utente di tornare al precedente stato premendo il tasto “back”.

Esempio:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

Comunicazione tra diversi Fragment

E’ possibile stabilire una comunicazione bidirezionale tra un’ attività contenitrice e i suoi frammenti.
Un modo per realizzarla è quello di definire una interfaccia di callback nel frammento, e costringere l’ attività ad implementarla.
Quando l’ attività riceve il callback attraverso l’ interfaccia, può convididere informazioni con altri frammenti del layout.

Esempio di comunicazione Fragment-Activity

public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // Container Activity must implement this interface
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }
    ...
}

Il precedente Fragment (è un ListFragment, una sua sottoclasse) ha una dichiarazione di interfaccia OnHeadlineSelectedListener.
Implementando questa interfaccia nell’ attività contenitrice e sovrascrivendo (overriding) il suo metodo onArticleSelected è possibile inviare l’ evento ad altri frammenti.
Il blocco try-catch serve a verificare che, quando il frammento viene inserito nell’ attività (evento onAttach() !), questa implementi l’ interfaccia: la verifica consiste nell’ istanziare una istanza dell’ interfaccia OnHeadlineSelectedListener tramite la classe dell’ attività, la quale implementa l’ interfaccia stessa.
In caso di errore, viene lanciata un’ eccezione.
In caso di successo, il membro mCallback tiene un riferimento all’ implementazione dell’ interfaccia da parte dell’ attività.

Il frammento usa l’ interfaccia di callback per inviare eventi all’ activity genitrice:

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
   // Send the event to the host activity
   mCallback.onArticleSelected(position);
}

Dal precedente snippet si vede che quando avviene l’ evento onListItemClick nel frammento, il suo listener chiama l’ interfaccia di callback che precedentemente abbiamo “agganciato” all’ attività; in altre parole, quando avviene un evento nel frammento, viene eseguito prima il listener nel frammento, e questo chiama la funzione implementata nell’ attività contenitrice: in questo modo si possono passare variabili dal frammento all’ attività.

Implementare l’ interfaccia

L’ attività che riceve i messaggi deve implementare le interfacce definite nei frammenti:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(Uri articleUri) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article
    }
}

Esempio di comunicazione Activity-Fragment

L’ attività puo inviare messaggi ai frammenti direttamente chiamando i metodi pubblici dei frammenti: può catturare l’ istanza del Fragment con findFragmentById().

Esempio:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article

        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // If article frag is available, we're in two-pane layout...

            // Call a method in the ArticleFragment to update its content
            articleFrag.updateArticleView(position);
        } else {
            // Otherwise, we're in the one-pane layout and must swap frags...

            // Create fragment and give it an argument for the selected article
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();
        }
    }
}

Interfaccia utente, Activity, ed Intent

Interfaccia utente

L’ interfaccia grafica si basa su una gerarchia di oggetti di tipo View e ViewGroup.
Un LinearLayout è una sottoclasse di ViewGroup: al suo interno si può inserire un oggetto View, come un EditText o un Button.

Un importante attributo delle View è layout_weight: esso permette di specificare una quota dello spazio a disposizione delle View contigue.
Ad esempio: se impostassimo a 2 il peso di una View e ad 1 il peso di un’ altra, avremmo che la prima occuperà 2/3 dello spazio (infatti la somma dei pesi è 3); se ad una terza View si desse il peso 1, la prima avrebbe 2/4=1/2 dello spazio, e le altre ne avrebbero 1/4 ciascuna.
Il valore di default per ogni View è zero: quindi se si desse un peso maggiore di 0 ad una sola vista, questa occuperebbe tutto lo spazio lasciato libero dalle rimanenti View impostate a wrap_content.
Un esempio di ciò è un LinearLayout orizzontale in cui c’è una riga con EditText e un Button impostati entrambi a wrap_content per larghezza e altezza. Dando peso pari a 1 alla EditText per allargarla al massimo dello spazio disponibile, il bottone occuperebbe il minore spazio possibile per avvolgere la stringa che contiene.
In questo caso è importante anche modificare layout_width di EditText impostandolo a 0dp: infatti wrap_content richiede un calcolo della larghezza, e il layout_weight ne richiede un altro; usando 0dp si evita il doppio calcolo.

Lanciare una seconda Activity

Supponiamo di utilizzare il bottone “Send” della figura qui sopra per aprire una nuova Activity in cui mostrare la stringa inserita in EditText.

Nel main.xml aggiungere android:onClick=”sendMessage” per indicare una funzione da chiamare quando il bottone viene cliccato; nella classe della prima Activity definire la funzione

/** Called when the user selects the Send button */
public void sendMessage(View view) {
    // Do something in response to button
}

Notare che il nome della funzione deve essere uguale al nome assegnato all’ attributo XML, la funzione deve essere public, deve ritornare void, e avere un solo parametro View.
Per passare alla seconda Activity i dati si usano gli Intent.

Per esempio:

Intent intent = new Intent(this, DisplayMessageActivity.class);

il costruttore prende 2 parametri:

  • un Context: si usa this perchè Activity è una sottoclasse di Context
  • la classe del componente a cui il sistema deve consegnare l’ intent: in questo caso sarà l’ Activity che dovrà essere aperta

poi nell’ intent istanziato si possono inserire i dati da passare:

Intent intent = new Intent(this, DisplayMessageActivity.class);
EditText editText = (EditText) findViewById(R.id.edit_message);
String message = editText.getText().toString();
intent.putExtra(EXTRA_MESSAGE, message);

la funzione putExtra() prende una “chiave” (un nome di variabile) e il suo valore come parametri: bisogna creare la chiave nella Activity (siamo ancora nella prima !)

public class MyFirstActivity extends Activity {
    public final static String EXTRA_MESSAGE = "com.example.myapp.MESSAGE";
    ...
}

è sempre bene definire la chiave usando il package name dell’ applicazione come prefisso per essere sicuri che sia univoca, nel caso di interazione con altre applicazioni.

Avviare la seconda Activity

Per avviare la nuova Activity basta completare la precedente funzione con il comando startActivity(intent):

/** Called when the user selects the Send button */
public void sendMessage(View view) {
    Intent intent = new Intent(this, DisplayMessageActivity.class);
    EditText editText = (EditText) findViewById(R.id.edit_message);
    String message = editText.getText().toString();
    intent.putExtra(EXTRA_MESSAGE, message);
    startActivity(intent);
}

Ora bisogna creare la classe DisplayMessageActivity della nuova Activity; è necessario implementare il metodo onCreate() (ogni sottoclasse di Activity deve farlo) ed è in esso che si inizializzano i componenti essenziali dell’ Activity.

E’ anche necessario dichiarare l’ Activity nel AndroidManifest.xml (con Eclipse è facile aggiungere la nuova activity).

Ora si può “ricevere” l’ intent:

Intent intent = getIntent();
String message = intent.getStringExtra(MyFirstActivity.EXTRA_MESSAGE);

notare la “chiave” definita precedentemente: ora serve a recuperare la stringa.

A questo punto abbiamo tutti i dati: rimane solo da visualizzarli e per semplicità adesso lo farò in maniera programmatica (senza usare i layout XML)

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Get the message from the intent
    Intent intent = getIntent();
    String message = intent.getStringExtra(MyFirstActivity.EXTRA_MESSAGE);

    // Create the text view
    TextView textView = new TextView(this);
    textView.setTextSize(40);
    textView.setText(message);

    setContentView(textView);
}

Android 4.0.3 ListView

Analisi di un semplice programma in Java per Android, scritto con Eclipse 3.7.2.

Per prima cosa creo un nuovo file XML da mettere in res/layout/ che chiamo list_item.xml, e che è un file che descrive il layout della singola riga che andrà a formare la lista: con la IDE è facile creare una nuova TextView

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</TextView>

sempre con l’ aiuto della IDE si può modificare il layout a piacimento.
Inoltre preparo i dati da rappresentare nella lista: in res/values/strings.xml aggiungo un’ array di stringhe del tipo:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">ProvaListView</string>

    <string-array name="list_countries">
        <item>Afghanistan</item>
        <item>Albania</item>
        <item>Algeria</item>
        <item>Andorra</item>
        <item>Angola</item>
        .......
    </string-array>

</resources>

Notare l’ attributo name=”list_countries” di string-array: sarà usato per richiamare i dati in seguito.

Poi modifico il file della Activity del programma: invece di usare questa classe, userò la sua sottoclasse ListActivity

public class ProvaListViewActivity extends ListActivity { ..... }

La classe ListActivity ha alcuni metodi specializzati per creare la lista tramite degli Adapter: questi sono un tramite fra i dati e la vista.
Quindi cancello la riga che imposta la vista con un contenuto “statico”

setContentView(R.layout.main);

e la sostituisco con il metodo public void setListAdapter (ListAdapter adapter) della classe ListActivity; il suo argomento è un oggetto ListAdapter che ha diverse sottoclassi.
In questo esempio userò la sottoclasse ArrayAdapter dichiarando T come String, cioè un ArrayAdapter di stringhe.
La classe ArrayAdapter ha diversi costruttori: userò public ArrayAdapter (Context context, int textViewResourceId, T[] objects) e perciò devo reperire l’ array di oggetti di tipo T = String.
Il tutto è riportato nel seguente snippet:

String[] list_countries = getResources().getStringArray(R.array.list_countries); 
setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, list_countries));

con la prima riga richiamo i dati da res/values/strings.xml con l’ attributo list_countries;
con la seconda creo in modo “anonimo” l’ ArrayAdapter indicando il contesto, il template “list_item”, e i dati.

A questo punto, i dati sono “collegati” all’ adapter, pronti per essere visualizzati.
Rimane da impostare la ListView.

Essa è “contenuta” nella ListActivity, e la prendo con il comando ListView lv = getListView();; ora posso impostare varie opzioni. Ad esempio imposto lv.setTextFilterEnabled(true); (che filtra gli elementi in base all’ input utente) e imposto un callback per i click sui singoli elementi: lv.setOnItemClickListener(listener); in cui “listener” è una interfaccia, con un metodo astratto “onItemClick” che rappresenta la funzione di callback vera e propria.
La definizione di onItemClick è abstract void onItemClick(AdapterView parent, View view, int position, long id), in cui AdapterView (con type generics) è l’ Adapter un cui elemento viene cliccato (in questo caso è una ListView, discendente di AdapterView), view è la vista cliccata nell’ AdapterView (il valore è fornito dall’ Adapter), position e id sono la posizione e l’ id della vista cliccata.
Un esempio di tutto ciò è:

OnItemClickListener listener = new OnItemClickListener() {
        	@Override
        	public void onItemClick(AdapterView<?> parent, View view, int position,
        			long id) {
        		TextView tv = (TextView) view;
        		Toast.makeText(getApplicationContext(), tv.getText(), Toast.LENGTH_LONG).show();
        	}
		};
ListView lv = getListView();
lv.setTextFilterEnabled(true);
lv.setOnItemClickListener(listener);

in cui il callback visualizza semplicemente un Toast con il testo cliccato (utilizzo un type casting da View a TextView).

Il risultato complessivo è:

public class ProvaListViewActivity extends ListActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /** Questo metodo aggiunge alla ListActivity un ListAdapter.
         *  Uso una sua sottoclasse: ArrayAdapter di tipo String.
         *  L' Adapter ha vari costruttori.
         *  Qui uso quello che collega al Context "this" una risorsa,
         *  prende poi una TextView R.layout.list_item,
         *  e infine prende l' array di elementi. */
        String[] list_countries = getResources().getStringArray(R.array.list_countries); 
        setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, list_countries));
        OnItemClickListener listener = new OnItemClickListener() {
        	@Override
        	public void onItemClick(AdapterView<?> parent, View view, int position,
        			long id) {
        		TextView tv = (TextView) view;
        		Toast.makeText(getApplicationContext(), tv.getText(), Toast.LENGTH_LONG).show();
        	}
		};
        ListView lv = getListView();
        lv.setTextFilterEnabled(true);
        lv.setOnItemClickListener(listener);
    }
}

Per quanto riguarda i vari “import”, mi lascio aiutare da Eclipse.

Preparazione dell’ ambiente di sviluppo Android

Utilizzo la IDE Eclipse: http://www.eclipse.org/ , che su Debian Wheezy (attualmente testing) installo con il pacchetto “eclipse”.
Al primo avvio indicare in quale directory Eclipse andrà a salvare i workspaces.

Al termine dell’ installazione, scarico lo SDK Starter Package (circa 25 MB) da http://developer.android.com/sdk/index.html: scompatto il tgz e lo colloco in una directory che poi dovrò indicare alla IDE (per semplicità si può tenere nella home).

Successivamente, installo lo ADT Plugin di Eclipse. Si installa direttamente da dentro la IDE: selezionare dal menu la voce Help > Install New Software….; nella finestra di dialogo che si aprirà, inserire il sito da cui prelevare il software aggiuntivo: premendo Add inserisco un nome (ad esempio Android ADT) e la URL https://dl-ssl.google.com/android/eclipse/; dopo aver premuto OK attendo che viene aggiornata la lista del software disponibile: comparirà la voce Developer Tool con altre sottovoci. Seleziono Developer Tools. Premo Next e attendo il caricamento: compare la lista del software che sarà installato; premo ancora Next e nella successiva finestra accetto le licenze, e poi clicco Finish. Inizierà il download. Può anche comparire un warning sulla non sicurezza delle fonti: continuare ugualmente. Alla fine dell’ installazione, riavviare Eclipse.

Adesso bisogna configurare ADT.
Al riavvio di Eclipse compare una finestra di dialogo che chiede dove è posizionato lo SDK Starter Package: indicare dove è stato scompattato il tgz, e scaricare il più recente SDK. Il download richiede qualche minuto.
Se non compare automaticamente la finestra, o se si vuole controllare quali file vengono installati, selezionare Window > Android SDK Manager dal menu di Eclipse.
Per usare l’ emulatore è necessario installare il pacchetto ARM EABI System Image.
Le Google APIs servono per accedere a Maps, non è necessario scaricarle, così come la documentazione e i sorgenti.

Il software scaricato da ADT viene scaricato nella directory indicata prima (è quella dello Starter Package): ci sono i file degli SDK selezionati e altri tools vari.
Se si vuole operare anche da linea di comando oltre che da Eclipse, è comodo aggiungere al path anche questa directory: editare ~/.bash_profile o ~/.bashrc e aggiungere

export PATH=${PATH}:<dir-di-sdk>/tools:<dir-di-sdk>/platform-tools