Working with file contents

Files in the Google Drive Android API, represented by the DriveFile interface, are specialized resources with Metadata, a DriveId, and DriveContents. The binary content of a file is encapsulated in the DriveContents class.

You can choose to work with the file contents either by using the InputStream and OutputStream classes, or by working with the ParcelFileDescriptor. The access methods available to you depend on the mode the file was opened in.

Mode Method Class
MODE_READ_ONLY getInputStream InputStream
MODE_WRITE_ONLY getOutputStream OutputStream
MODE_READ_WRITE getParcelFileDescriptor ParcelFileDescriptor

Lifecycle of a Drive file

The Drive Android API lets your app access files even if the device is offline. To support offline cases, the API implements a sync engine that runs in the background to merge upstream and downstream changes as network access is available and to resolve conflicts.

Lifecycle of a DriveFile

The lifecycle of a DriveFile object:

  • Perform an initial download request if the file is not yet synced to the local context but the user wants to open the file. The API handles this automatically when a file is requested.
  • Open the contents of a file. This creates a temporary duplicate of the file's binary stream that is only available to your application.
  • Read or modify the file contents, making changes to the temporary duplicate.
  • Commit or discard any file content changes that have been made.
  • If there are changes, the Drive service puts the file contents into a queue for upload to sync them back to the server.

Reading files

Because the Drive Android API automatically handles downloading the file contents if the file has not been synced locally, the process of reading from a file is similar to using local storage. The basic steps to read a file are:

  1. Retrieve the DriveFile object.
  2. Open the file contents.
  3. Retrieve and process the contents using the InputStream or the ParcelFileDescriptor class, depending on the mode used.
  4. Close the file contents.

Opening the file contents

In order to be able to read a file, you must start by opening its DriveContents resource in DriveFile.MODE_READ_ONLY or DriveFile.MODE_READ_WRITE mode, depending on whether you prefer to work with the InputStream or ParcelFileDescriptor class.

The DriveResourceClient.openFile method retrieves the locally synced file resource and opens it. If the file is not synced with the local storage, it retrieves the file from the Drive service and returns a DriveContents resource. For example:

Task<DriveContents> openFileTask =
        getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);

A DriveContents resource contains a temporary copy of the file's binary stream that is only available to your application. If multiple applications attempt to access the same file, there are no race conditions between DriveContents resources. In this situation, the last write operation is the final state of the content.

Handling the response requires you to check if the call was successful. If the call was successful, you can retrieve the DriveContents resource. This resource contains methods to retrieve an InputStream or ParcelFileDescriptor to read the file's binary contents.

The following example demonstrates how to retrieve a file's DriveContents:

openFileTask
        .continueWithTask(new Continuation<DriveContents, Task<Void>>() {
            @Override
            public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception {
                DriveContents contents = task.getResult();
                // Process contents...

                Task<Void> discardTask = getDriveResourceClient().discardContents(contents);
                return discardTask;
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Handle failure
            }
        });

Listening to the download progress

Opening a file can require a long I/O operation if the file is not yet synced locally. You can attach a OpenFileCallback to inform users of the download progress in a ProgressDialog to improve the user experience.

To listen to the download progress, open the file contents with a DownloadProgressListener, as shown in this example:

OpenFileCallback openCallback = new OpenFileCallback() {
    @Override
    public void onProgress(long bytesDownloaded, long bytesExpected) {
        // Update progress dialog with the latest progress.
        int progress = (int) (bytesDownloaded * 100 / bytesExpected);
        Log.d(TAG, String.format("Loading progress: %d percent", progress));
        mProgressBar.setProgress(progress);
    }

    @Override
    public void onContents(@NonNull DriveContents driveContents) {
        // onProgress may not be called for files that are already
        // available on the device. Mark the progress as complete
        // when contents available to ensure status is updated.
        mProgressBar.setProgress(100);
        // Read contents
    }

    @Override
    public void onError(@NonNull Exception e) {
        // Handle error
    }
};

getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY, openCallback);

In the event that the file has already been synced locally, the system does not call the onProgress listener.

Reading from the input stream

A DriveContents resource provides a java.io.InputStream you can use to read the binary contents of the opened file. The sample below illustrates how to read from a DriveContents resource and convert the binary contents into a String object. However, you can consume the input stream in whatever way your application and data format require.

try (BufferedReader reader = new BufferedReader(
             new InputStreamReader(contents.getInputStream()))) {
    StringBuilder builder = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        builder.append(line).append("\n");
    }
    showMessage(getString(R.string.content_loaded));
    mFileContents.setText(builder.toString());
}

Writing files

Writing to a file follows a similar procedure to reading a file, with the difference being that you use either the OutputStream or the ParcelFileDescriptor class to write to the file, determined by the mode you opened the file in.

Opening the file contents

Just as with reading files, you must open contents in order to write to a file. You can use either the DriveFile.MODE_WRITE_ONLY or DriveFile.MODE_READ_WRITE to open the contents in a writable mode, depending on whether you intend to overwrite the content with an OutputStream or append to the content with a ParcelFileDescriptor. You can optionally use a OpenFileCallback to listen to the download status if the latest version of the file is not already synced to the device.

The following example shows how to open and retrieve the file contents:

Task<DriveContents> openTask =
        getDriveResourceClient().openFile(file, DriveFile.MODE_READ_WRITE);

Your application must handle the opening call to verify that the DriveContents resource is successfully opened. Once the contents are open, you can make modifications to them.

Making modifications

The DriveContents resource provides a java.io.OutputStream that lets you write or append binary data to the file contents.

The example below illustrates how to append "Hello world" to a DriveContents object.

openTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
            @Override
            public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception {
                DriveContents driveContents = task.getResult();
                ParcelFileDescriptor pfd = driveContents.getParcelFileDescriptor();
                long bytesToSkip = pfd.getStatSize();
                try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
                    // Skip to end of file
                    while (bytesToSkip > 0) {
                        long skipped = in.skip(bytesToSkip);
                        bytesToSkip -= skipped;
                    }
                }
                try (OutputStream out = new FileOutputStream(pfd.getFileDescriptor())) {
                    out.write("Hello world".getBytes());
                }

                MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                                                      .setStarred(true)
                                                      .setLastViewedByMeDate(new Date())
                                                      .build();
                Task<Void> commitTask =
                        getDriveResourceClient().commitContents(driveContents, changeSet);

                return commitTask;
            }
        })
        .addOnSuccessListener(this,
                new OnSuccessListener<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {
                        showMessage(getString(R.string.content_updated));
                        finish();
                    }
                })
        .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.e(TAG, "Unable to update contents", e);
                showMessage(getString(R.string.content_update_failed));
                finish();
            }
        });

Each DriveContents resource is a temporary duplicate of the file you are trying to modify and is only accessible within your application. Once you're done with the output stream, you must either commit any changes that you made, making them visible in the user's Drive, or discard the changes. The next section demonstrates how to finalize the write operation.

Closing the file contents

You must close each opened DriveContents resource to free related resources, notifying the system whether you want to keep or discard any changes made. In order the keep the changes permanently, use the DriveResourceClient.commitContents method, passing in the DriveContents object to close.

Task<Void> commitTask =
        getDriveResourceClient().commitContents(driveContents, null);

You can also use this method to submit simultaneous metadata changes. For example:

MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                                      .setStarred(true)
                                      .setLastViewedByMeDate(new Date())
                                      .build();
Task<Void> commitTask =
        getDriveResourceClient().commitContents(driveContents, changeSet);

To discard the changes you made to the temporary DriveContents resource, use the DriveResourceClient.discardContents method to avoid writing changes to the file in the user's Drive.

Task<Void> discardTask = getDriveResourceClient().discardContents(contents);

Any committed changes are synced to the user's Drive on the next scheduled sync task.

Enviar comentarios sobre…

Drive API for Android
Drive API for Android