Nel seguito metteremo in pratica i concetti espressi nella lezione precedente, scoprendo come potere integrare i servizi di Google Drive nella nostra app. In particolare, vedremo come realizzare un'app che ci consente di salvare un testo su Google Drive, trasformandolo in un file di testo.
L'esempio
Al primo avvio dell'applicazione che stiamo per realizzare verrà richiesto all'utente il consenso alla visualizzazione e gestione dei dati presenti sul proprio account Drive.
L'account sul quale le operazioni dovranno essere svolte sarà scelto dall'utente mediante una finestra di dialogo, tramite la quale è possibile selezionare uno degli account già configurati nel dispositivo, oppure aggiungerne uno nuovo.
Fatto questo verrà visualizzato il layout (visibile nella figura successiva), e solo dopo l'avvenuta connessione ai Google Play Services il pulsante Salva verrà abilitato.
A quel punto si potranno scrivere nell'EditText che inseriremo nel layout, i contenuti da salvare in remoto.
Il nome del file che creeremo avrà il prefisso note- seguito da informazioni temporali. Un Toast notificherà l'avvenuto salvataggio su Drive dei dati, ma la verifica più soddisfacente è quella che si può fare collegandosi da browser al proprio account Drive e verificando l'effettiva presenza del file.
Nel nostro esempio è stato creato il file di nome note-20141102-173110, che vediamo salvato nell'account Drive di destinazione nella seguente figura.
Ora che è chiaro l'esperimento che vogliamo condurre, possiamo procedere con i dettagli implementativi.
Il primo passo consiste nella configurazione del progetto Google. Seguendo quanto già esposto in questa guida, se ne dovrà verificare la
disponibilità tramite la Google Developers Console, eventualmente creandone uno nuovo.
Le due operazioni necessarie da svolgere sono:
- attivazione delle Drive API seguendo il menu APIs & Auth -> APIs;
-
creazione di un nuovo Client ID OAuth. Quest'ultimo è necessario in quanto dovremo accedere ai dati di un account. La procedura può essere attivata
seguendo il menu APIs & Auth -> Credentials.
Vale la pena sottolineare che è della massima importanza legare alla chiave OAuth il package Java usato per l'applicazione.
In caso contrario l'interazione con il servizio non sarebbe permessa. Inoltre, a differenza delle chiavi di tipo Public API Access, non è necessario inserire alcuna
informazione all'interno del manifest del progetto Android.
Veniamo ora al codice.
Il layout è molto semplice (file: /res/layout/activity_main.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Contenuto del file" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="5"
android:gravity="top"
android:id="@+id/filecontents"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/saveButton"
android:onClick="save"
android:enabled="false"
android:text="Salva" />
</LinearLayout>
Anche in questo caso, il codice Java dell'Activity è composto da due porzioni. Nella prima gestiremo le varie fasi di connessione ai servizi Google tramite
la classe GoogleApiClient, nella seconda ci occuperemo esclusivamente dell'interazione con Drive.
L'Activity si presenta così:
public class MainActivity extends Activity implements OnConnectionFailedListener, ConnectionCallbacks
{
private static final int REQUEST_RESOLVE_ERROR = 0;
private static final String DIALOG_ERROR = null;
private GoogleApiClient client;
private boolean resolvingError;
private SimpleDateFormat simple=new SimpleDateFormat("yyyyMMdd-HHmmss");
private final static String PREFIX="note-";
private String newFilename;
private EditText filecontents;
private Button saveButton;
...
...
}
Tra i membri privati il più importante è quello di tipo GoogleApiClient. Costituirà il centro della connessione al servizio. L'implementazione delle
interfacce OnConnectionFailedListener
e ConnectionCallbacks
è necessaria affinchè l'Actvity possa svolgere il ruolo di listener delle fasi di connessione
ai Google Play Services.
Il metodo onCreate
inizializza l'oggetto GoogleApiClient e fissa il layout:
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
client = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
filecontents=(EditText) findViewById(R.id.filecontents);
saveButton=(Button) findViewById(R.id.saveButton);
}
Tutta la fase di connessione e disconnessione si svolge nel lasso di tempo che intercorre tra i metodi onStart
ed onStop
:
@Override
protected void onStart() {
super.onStart();
if (!resolvingError)
{
client.connect();
}
}
@Override
protected void onStop()
{
saveButton.setEnabled(false);
if (client.isConnected())
client.disconnect();
super.onStop();
}
@Override
public void onConnectionFailed(ConnectionResult result)
{
if (resolvingError)
return;
if (result.hasResolution()) {
try {
resolvingError = true;
result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
}
catch (SendIntentException e)
{
client.connect();
}
} else {
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),this, REQUEST_RESOLVE_ERROR).show();
resolvingError = true;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_RESOLVE_ERROR) {
resolvingError = false;
if (resultCode == RESULT_OK)
{
if (!client.isConnecting() &&
!client.isConnected()) {
client.connect();
}
}
}
}
@Override
public void onConnected(Bundle arg0)
{
saveButton.setEnabled(true);
Toast.makeText(this,"Connesso",Toast.LENGTH_LONG).show();
}
@Override
public void onConnectionSuspended(int arg0)
{
saveButton.setEnabled(false);
}
Il codice che si occupa invece della connessione con il servizio Drive inizia cliccando sul pulsante presente nel form. Il metodo da esso innescato è save
:
public void save(View v)
{
newFilename=PREFIX+simple.format(new Date());
Drive.DriveApi.newDriveContents(client).setResultCallback(contentsCallback);
}
Come si può vedere, quest'ultimo metodo definisce un nuovo nome per il file ed invoca un'operazione asincrona che avrà come effetto la creazione di un nuovo contenuto sullo
storage remoto.
Il riferimento contentsCallback
indica quale oggetto svolgerà il ruolo di listener. Ne vediamo subito il codice:
final private ResultCallback<DriveApi.DriveContentsResult> contentsCallback = new ResultCallback<DriveApi.DriveContentsResult>()
{
@Override
public void onResult(DriveApi.DriveContentsResult result)
{
if (!result.getStatus().isSuccess())
{
Toast.makeText(MainActivity.this,"Errore: impossibile creare contenuti in Drive",Toast.LENGTH_LONG).show();
return;
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(newFilename)
.setMimeType("text/plain")
.setStarred(true).build();
Drive.DriveApi.getRootFolder(client)
.createFile(client, changeSet, result.getDriveContents())
.setResultCallback(fileCallback);
}
};
La creazione di un nuovo file ruota per lo più attorno alla definizione dei Metadata, soprattutto il nome del file (setTitle
) ed il tipo (setMimeType
). Il nuovo file sarà collocato nella RootFolder e la sua creazione vera e propria sarà ancora asincrona: l'oggetto fileCallback
, infatti, attenderà la comunicazione del suo completamento.
final private ResultCallback<DriveFolder.DriveFileResult> fileCallback = new ResultCallback<DriveFolder.DriveFileResult>()
{
@Override
public void onResult(DriveFolder.DriveFileResult result)
{
if (!result.getStatus().isSuccess())
{
Toast.makeText(MainActivity.this,"Errore: impossibile scrivere contenuti",Toast.LENGTH_LONG).show();
return;
}
new EditContentsAsyncTask().execute(result.getDriveFile());
}
};
Il listener, in questo caso, si occupa per prima cosa di verificare se la creazione del file ha avuto successo, e solo successivamente avvia la scrittura dei contenuti. Quest'ultima attività verrà svolta in modalità sincrona: sarà infatti attuata all'interno di un metodo doInBackground
di EditContentsAsyncTask, che estende la classe AsyncTask:
public class EditContentsAsyncTask extends AsyncTask<DriveFile, Void, Boolean>
{
private String filecontents;
@Override
protected void onPreExecute()
{
super.onPreExecute();
this.filecontents=MainActivity.this.filecontents.getText().toString();
}
@Override
protected Boolean doInBackground(DriveFile... args)
{
DriveFile file = args[0];
try {
DriveApi.ContentsResult contentsResult = file.openContents(
client, DriveFile.MODE_WRITE_ONLY, null).await();
if (!contentsResult.getStatus().isSuccess()) {
return false;
}
OutputStream outputStream = contentsResult.getContents().getOutputStream();
outputStream.write(filecontents.getBytes());
com.google.android.gms.common.api.Status status = file.commitAndCloseContents(client, contentsResult.getContents()).await();
return status.getStatus().isSuccess();
}
catch (IOException e)
{ }
return false;
}
@Override
protected void onPostExecute(Boolean result)
{
if (result)
{
Toast.makeText(getApplicationContext(), "Salvataggio eseguito con Successo. Nome del file: "+newFilename, Toast.LENGTH_SHORT).show();
}
}
}
Notiamo che al centro del metodo doInBackground
ci sono due operazioni sincrone, entrambe eseguite con il metodo await()
:
-
openContents: apre la connessione con il file e dal suo risultato, in assenza di errori, si ottiene il canale più comodo possibile per inserire
contenuti, un normale OutputStream; - commitAndCloseContents: richiede il salvataggio dei contenuti e la chiusura del file.
Tra le due operazioni appena citate troviamo il codice Java per la scrittura di byte su uno Stream in output.
Conclusioni
L'esempio non mostra altro che uno dei tantissimi compiti che possono essere svolti dalle API di Google Drive. Ha il merito però di mostrare un nuovo
esempio di interazione con i servizi Google e dimostrare ancora una volta che la piattaforma uniforma totalmente l'accesso ad API molto diverse, imponendo
un comune percorso: installazione della libreria dei Google Play Services, attivazione delle API necessarie, definizione delle opportune credenziali
commisurate alle attività da svolgere, connessione ai Google Play Services mediante GoogleApiClient.
Altro aspetto interessante è la possibilità di verificare i risultati dell'esempio direttamente tramite browser, tra i dati di un account Drive. Ciò
dimostra come queste funzionalità permettano alle nostre app di intrecciarsi direttamente con i dati personali gestiti dall'utente finale.