Logic

And here we are at the interesting part. We have to save the backup folder the first time so we can use it later, remember? To save it we can use Android’s SharedPreferences.

private void saveBackupFolder(String folderPath) {

SharedPreferences.Editor editor = sharedPref.edit();

editor.putString(BACKUP_FOLDER_KEY, folderPath);

editor.apply();

}

Pretty easy. Now when the Backup button is pressed, we check if a value is already stored in SharedPreferences. If yes, we just run the backup method (see my precedent post for the complete code). If not, we show Drive’s picker and save the picked folder.

String backupFolder = backupFolder = sharedPref.getString(BACKUP_FOLDER_KEY, ""); // First we check if a backup folder is set if ("".equals(backupFolder)) {

try {

if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {

if (intentPicker == null)

intentPicker = buildIntent();

//Start the picker to choose a folder

startIntentSenderForResult(

intentPicker, REQUEST_CODE_PICKER, null, 0, 0, 0);

}

} catch (IntentSender.SendIntentException e) {

Log.e(TAG, "Unable to send intent", e);

showErrorDialog();

}

} else {

uploadToDrive(DriveId.decodeFromString(backupFolder));

}

As you can see, if a backup folder is not defined, we open the picker and collect the result in onActivityResult:

@Override

protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {

switch (requestCode) {

// [...]

case REQUEST_CODE_PICKER:

intentPicker = null;



if (resultCode == RESULT_OK) {

//Get the folder drive id

DriveId mFolderDriveId = data.getParcelableExtra(

OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);



saveBackupFolder(mFolderDriveId.encodeToString());



uploadToDrive(mFolderDriveId);

}

break;



// [...]

}

}

Here we used the method saveBackupFolder (that I’ve written before) to save to SharedPreferences a String that contains an encoded DriveId. The id can be decoded later using DriveId.decodeFromString().

The method uploadToDrive() handles the uploading process and saves the backup file in the folder with an hardcoded name (“glucosio.realm”, you’ll see why later).

private void uploadToDrive(DriveId mFolderDriveId) {

if (mFolderDriveId != null) {

//Create the file on GDrive

final DriveFolder folder = mFolderDriveId.asDriveFolder();

Drive.DriveApi.newDriveContents(mGoogleApiClient)

.setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {

@Override

public void onResult(DriveApi.DriveContentsResult result) {

if (!result.getStatus().isSuccess()) {

Log.e(TAG, "Error while trying to create new file contents");

showErrorDialog();

return;

}

final DriveContents driveContents = result.getDriveContents();



// Perform I/O off the UI thread.

new Thread() {

@Override

public void run() {

// write content to DriveContents

OutputStream outputStream = driveContents.getOutputStream();



FileInputStream inputStream = null;

try {

inputStream = new FileInputStream(new File(realm.getPath()));

} catch (FileNotFoundException e) {

reportToFirebase(e, "Error uploading backup from drive, file not found");

showErrorDialog();

e.printStackTrace();

}



byte[] buf = new byte[1024];

int bytesRead;

try {

if (inputStream != null) {

while ((bytesRead = inputStream.read(buf)) > 0) {

outputStream.write(buf, 0, bytesRead);

}

}

} catch (IOException e) {



showErrorDialog();

e.printStackTrace();

}





MetadataChangeSet changeSet = new MetadataChangeSet.Builder()

.setTitle("glucosio.realm")

.setMimeType("text/plain")

.build();



// create a file in selected folder

folder.createFile(mGoogleApiClient, changeSet, driveContents)

.setResultCallback(new ResultCallback<DriveFolder.DriveFileResult>() {

@Override

public void onResult(DriveFolder.DriveFileResult result) {

if (!result.getStatus().isSuccess()) {

Log.d(TAG, "Error while trying to create the file");

showErrorDialog();

finish();

return;

}

showSuccessDialog();

finish();

}

});

}

}.start();

}

});

}

}

Now that we handled the upload, we need to show the saved backups in the ListView. I think the easier method is to perform a search on Google Drive of all the files in the backup folder and pick the ones named “glucosio.realm”.

First I’ve defined a GlucoseBackup class to store all relevant data for each backup file.

public class GlucosioBackup {



private DriveId driveId;

private Date modifiedDate;

private long backupSize;



public GlucosioBackup(DriveId driveId, Date modifiedDate, long backupSize){

this.driveId = driveId;

this.modifiedDate = modifiedDate;

this.backupSize = backupSize;

}



public DriveId getDriveId() {

return driveId;

}



public void setDriveId(DriveId driveId) {

this.driveId = driveId;

}



public Date getModifiedDate() {

return modifiedDate;

}



public void setModifiedDate(Date modifiedDate) {

this.modifiedDate = modifiedDate;

}



public long getBackupSize() {

return backupSize;

}



public void setBackupSize(long backupSize) {

this.backupSize = backupSize;

}

}

Then I’ve written the method to do the search and return and ArrayList of GlucosioBackup.

private void getBackupsFromDrive(DriveFolder folder){

final Activity activity = this;

SortOrder sortOrder = new SortOrder.Builder()

.addSortDescending(SortableField.MODIFIED_DATE).build();

Query query = new Query.Builder()

.addFilter(Filters.eq(SearchableField.TITLE, "glucosio.realm"))

.addFilter(Filters.eq(SearchableField.TRASHED, false))

.setSortOrder(sortOrder)

.build();

folder.queryChildren(mGoogleApiClient, query)

.setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {



private ArrayList<GlucosioBackup> backupsArray = new ArrayList<GlucosioBackup>();



@Override

public void onResult(DriveApi.MetadataBufferResult result) {

MetadataBuffer buffer = result.getMetadataBuffer();

int size = buffer.getCount();

for (int i=0; i<size; i++){

Metadata metadata = buffer.get(i);

DriveId driveId = metadata.getDriveId();

Date modifiedDate = metadata.getModifiedDate();

long backupSize = metadata.getFileSize();

backupsArray.add(new GlucosioBackup(driveId, modifiedDate, backupSize));

backupListView.setAdapter(new BackupAdapter(activity, R.layout.preferences_backup, backupsArray));

}



}

});

}

We filtered our results and picked only the ones with “glucosio.realm” in the title, with the Trashed attribute set to false (for obvious reasons :P). Then we received the metadata buffer from Drive and extracted the id, modified date and size of each file. We also created an array of GlucoseReadings and passed the whole thing to an adapter.

Yeah, guess what? We need to build an adapter for the ListView now :)

Build the ArrayAdapter

We can simply use an ArrayAdapter. Read more about it here.

The constructor requires the activity context, the layout file that will populate each row of the ListView and the ArrayList of data.

BackupAdapter(Context context, int resource, List<GlucosioBackup> items)

We override the getView method of the adapter and set all values in our TextViews.

@Override

public View getView(int position, View convertView, ViewGroup parent) {

View v = convertView;



if (v == null) {

LayoutInflater vi;

vi = LayoutInflater.from(getContext());

v = vi.inflate(R.layout.activity_backup_drive_restore_item, null);

}



GlucosioBackup p = getItem(position);

final DriveId driveId= p.getDriveId();

final String modified = p.getModifiedDate();

final String size = humanReadableByteCount(p.getBackupSize(), true);



if (p != null) {

TextView modifiedTextView = (TextView) v.findViewById(R.id.item_history_time);

TextView typeTextView = (TextView) v.findViewById(R.id.item_history_type);

modifiedTextView.setText(modified);

typeTextView.setText(size);

}



v.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

// Show custom dialog

final Dialog dialog = new Dialog(context);

dialog.setContentView(R.layout.dialog_backup_restore);

TextView createdTextView = (TextView) dialog.findViewById(R.id.dialog_backup_restore_created);

TextView sizeTextView = (TextView) dialog.findViewById(R.id.dialog_backup_restore_size);

Button restoreButton = (Button) dialog.findViewById(R.id.dialog_backup_restore_button_restore);

Button cancelButton = (Button) dialog.findViewById(R.id.dialog_backup_restore_button_cancel);



createdTextView.setText(modified);

sizeTextView.setText(size);



restoreButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

((BackupActivity)context).downloadFromDrive(driveId.asDriveFile());

}

});



cancelButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

dialog.dismiss();

}

});



dialog.show();

}

});



return v;

}

Ah, yes, I’m also converting raw file size to a human readable format. Here’s the method (thanks StackOverflow :P).

private static String humanReadableByteCount(long bytes, boolean si) {

int unit = si ? 1000 : 1024;

if (bytes < unit) return bytes + " B";

int exp = (int) (Math.log(bytes) / Math.log(unit));

String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");

return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);

}

Notice that we set an OnClickListener to the whole row layout. Each time a row is pressed, it shows the restore dialog. And if the user taps on Restore, it calls the restore method from the main activity (again, read my previous post to find out how to restore a file).

And what if the user wants to open the backup directory directly on Drive?

Nothing more simple, we take Folder’s metadata from Drive, get the directly link for the folder (using Drive’s API metadata.getAlternateLink()) and call a new intent that will fire up Google Drive app and show the content of our folder.

private void openOnDrive(DriveId driveId){

driveId.asDriveFolder().getMetadata((mGoogleApiClient)).setResultCallback(

new ResultCallback<DriveResource.MetadataResult>() {

@Override

public void onResult(DriveResource.MetadataResult result) {

if (!result.getStatus().isSuccess()) {

showErrorDialog();

return;

}

Metadata metadata = result.getMetadata();

String url = metadata.getAlternateLink();

Intent i = new Intent(Intent.ACTION_VIEW);

i.setData(Uri.parse(url));

startActivity(i);

}

}

);

}

As always, the full code is available on GitHub. Fell free to improve it and send us a pull request ❤

This is the final result.

As a bonus, here’s our promotional video that shows Glucosio’s Backup feature in action (yeah, it’s not only backup. With it you can also sync each device logged with the same Google account).