An Android Storage Access Framework Example

As previously discussed, the Storage Access Framework considerably eases the process of integrating cloud-based storage access into Android applications. Consisting of a picker user interface and a set of new intents, access to files stored on document providers such as Google Drive and Box can now be built into Android applications with relative ease.

With the basics of the Android Storage Access Framework covered in the preceding chapter, this chapter will work through creating an example application that uses the Storage Access Framework to store and manage files.

About the Storage Access Framework Example

The Android application created in this chapter will take the form of a rudimentary text editor designed to create and store text files remotely onto a cloud-based storage service. In practice, the example will work with any cloud-based document storage provider that is compatible with the Storage Access Framework, though for the purpose of this example, the use of Google Drive is assumed.

In functional terms, the application will present the user with a multi-line text view into which text may be entered and edited, together with a set of buttons allowing storage-based text files to be created, opened, and saved.

Creating the Storage Access Framework Example

Select the New Project option from the welcome screen and, within the resulting new project dialog, choose the Empty Views Activity template before clicking on the Next button.

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Enter StorageDemo into the Name field and specify com.ebookfrenzy.storagedemo as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 26: Android 8.0 (Oreo) and the Language menu to Java.

Follow the usual steps in section 11.8 Migrating a Project to View Binding to add view binding support to the project.

Designing the User Interface

The user interface will need to be comprised of three Button views and a single EditText view. Within the Project tool window, navigate to the activity_main.xml layout file located in app -> res -> layout and double-click on it to load it into the Layout Editor tool. With the tool in Design mode, select and delete the Hello World! TextView object.

Drag and position a Button widget in the top left-hand corner of the layout so that both the left and top dotted margin guidelines appear before dropping the widget in place. Position a second Button such that the center and top margin guidelines appear. The third Button widget should then be placed so that the top and right-hand margin guidelines appear.

Change the text attributes on the three buttons to “New”, “Open” and “Save” respectively. Next, position a Plain Text widget so that it is centered horizontally and positioned beneath the center Button so that the user interface layout matches that shown in Figure 69-1. Use the Infer Constraints button in the Layout Editor toolbar to add any missing constraints.

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Select the Plain Text widget in the layout, delete the current text property setting, so the field is initially blank, and set the ID to fileText. Next, extract the string attributes to resource values named string, open_string, and save_string, respectively.

Figure 69-1

Using the Attributes tool window, configure the onClick property on the Button widgets to call methods named newFile, openFile and saveFile respectively.

Adding the Activity Launchers

Following the steps outlined in the chapter entitled “Android Explicit Intents – A Worked Example”, we need to begin by registering activity launchers to handle the creation, opening and saving of file content. Within the MainActivity.java file, add a launcher for each of the three actions as follows:

.
.
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
 
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;

import java.io.IOException;
.
.
    ActivityResultLauncher<Intent> startOpenForResult =      
                                  registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        Intent data = result.getData();
                        if (data != null) {
                            Uri currentUri = data.getData();
                            try {
                                String content =
                                        readFileContent(currentUri);
                                binding.fileText.setText(content);
                            } catch (IOException e) {
                                // Handle error here
                            }
                        }
                    }
                }
            });
 
    ActivityResultLauncher<Intent> startSaveForResult =        
                                    registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    Intent data = result.getData();
                    if (data != null) {
                        Uri currentUri = data.getData();
                        try {
                            writeFileContent(currentUri);
                        } catch (IOException e) {
                            // Handle error here
                        }
                    }
                }
            });
 
    ActivityResultLauncher<Intent> startCreateForResult = 
                                  registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        Intent data = result.getData();
                        if (data != null) {
                            binding.fileText.setText("");
                        }
                    }
                }
            });Code language: Java (java)

Creating a New Storage File

When the New button is selected, the application will need to trigger an ACTION_CREATE_DOCUMENT intent configured to create a file with a plain-text MIME type. When the user interface was designed, the New button was configured to call a method named newFile(). It is within this method that the appropriate intent needs to be launched.

Remaining in the MainActivity.java file, implement this method as follows:

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

package com.ebookfrenzy.storagedemo;
 
 
public class MainActivity extends AppCompatActivity {
.
.
    public void newFile(View view)
    {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
 
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TITLE, "newfile.txt");
 
        startCreateForResult.launch(intent);
    }
.
.
}Code language: Java (java)

This code creates a new ACTION_CREATE_INTENT Intent object. This intent is then configured so that only files that can be opened with a file descriptor are returned (via the Intent.CATEGORY_OPENABLE category setting).

Next the code specifies that the file to be opened is to have a plain text MIME type and a placeholder filename is provided (which can be changed by the user in the picker interface).

When this method is executed and the intent has completed the assigned task, a call will be made to the startCreateForResult() lambda and passed the Uri of the newly created document.

Saving to a Storage File

Now that the application is able to create new storage based files, the next step is to add the ability to save any text entered by the user to a file. The user interface is configured to call the saveFile() method when the Save button is selected by the user. This method will be responsible for starting a new intent of type ACTION_OPEN_ DOCUMENT which will result in the picker user interface appearing so that the user can choose the file to which the text is to be stored.

Since we are only working with plain text files, the intent needs to be configured to restrict the user’s selection options to existing files that match the text/plain MIME type. Having identified the actions to be performed by the saveFile() method, this can now be added to the MainActivity.java class file as follows:

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

public void saveFile(View view)
{
       Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
       intent.addCategory(Intent.CATEGORY_OPENABLE);
       intent.setType("text/plain");
 
       startSaveForResult.launch(intent);
}Code language: Java (java)

The lambda assigned to startSaveForResult() calls a method named writeFileContent(), passing through the Uri of the file to which the text is to be written.

Remaining in the MainActivity.java file, implement this method now so that it reads as follows:

package com.ebookfrenzy.storagedemo;
 
import java.io.FileOutputStream;
import android.os.ParcelFileDescriptor;
 
public class MainActivity extends AppCompatActivity {
.
.
       private void writeFileContent(Uri uri) throws IOException
       {
              try{              
                     ParcelFileDescriptor pfd = 
                            this.getContentResolver().
                              openFileDescriptor(uri, "w");
                     
                     FileOutputStream fileOutputStream =
                         new FileOutputStream(
                            pfd.getFileDescriptor());
                     
                     String textContent = 
                            binding.fileText.getText().toString();
                     
                     fileOutputStream.write(textContent.getBytes());
                     
                     fileOutputStream.close();
                     pfd.close();
              } catch (IOException e) {
                     e.printStackTrace();
              }
       }
.
.
}Code language: Java (java)

The method begins by obtaining and opening the file descriptor from the Uri of the file selected by the user. Since the code will need to write to the file, the descriptor is opened in write mode (“w”). The file descriptor is then used as the basis for creating an output stream that will enable the application to write to the file.

The text entered by the user is extracted from the edit text object and written to the output stream before both the file descriptor and stream are closed. Code is also added to handle any IO exceptions encountered during the file writing process.

Opening and Reading a Storage File

Having written the code to create and save text files, the final task is to add some functionality to open and read a file from the storage. This will involve writing the openFile() onClick event handler method and implementing it so that it starts an ACTION_OPEN_DOCUMENT intent:

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

public void openFile(View view)
{
       Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
       intent.addCategory(Intent.CATEGORY_OPENABLE);
       intent.setType("text/plain");
       startOpenForResult.launch(intent);
}Code language: Java (java)

The next task is to implement the readFileContent() method called by the startOpenForResult() lambda:

package com.ebookfrenzy.storagedemo;
.
. 
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
.
.
public class MainActivity extends AppCompatActivity {
.
.
       private String readFileContent(Uri uri) throws IOException {
              
              InputStream inputStream = 
                           getContentResolver().openInputStream(uri);
              BufferedReader reader = 
                     new BufferedReader(new InputStreamReader(
                                 inputStream));
              StringBuilder stringBuilder = new StringBuilder();
              String currentline;
              while ((currentline = reader.readLine()) != null) {
                  stringBuilder.append(currentline).append("\n");
              }
              inputStream.close();
              return stringBuilder.toString();
       }
.
.
}Code language: Java (java)

This method begins by extracting the file descriptor for the selected text file and opening it for reading. The input stream associated with the Uri is then opened and used as the input source for a BufferedReader instance.

Each line within the file is then read and stored in a StringBuilder object. Once all the lines have been read, the input stream and file descriptor are both closed, and the file content is returned as a String object.

Testing the Storage Access Application

With the coding phase complete the app is ready to be tested. Compile and run the application and select the New button. The Storage Access Framework should subsequently display the Downloads user interface as illustrated in Figure 69-2:

Figure 69-2

Click the menu button highlighted above and select the Drive option followed by My Drive and navigate to a suitable location on your Google Drive storage into which to save the file. In the text field at the bottom of the picker interface, change the name from “newfile.txt” to a suitable name (but keeping the .txt extension) before selecting the Save option.

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Figure 69-3

Once the new file has been created, the app should return to the main activity and a notification may appear within the notifications panel which reads “1 file uploaded”.

Figure 69-4

At this point, it should be possible to log into your Google Drive account in a browser window and find the newly created file in the requested location. If the file is missing, make sure that the Android device on which the application is running has an active internet connection. Access to Google Drive on the device may also be verified by running the Google Drive app, which is installed by default on many Android devices, and available for download from the Google Play store.

Now that we have created a file, enter some text into the text area before clicking the “Save” button. Select the previously created text file from the picker to save the content to the file. On returning to the application, delete the text and select the “Open” button, once again choosing your saved file. When control is returned to the application, the text view should have been populated with the content of the text file.

It is important to note that the Storage Access Framework will cache storage files locally if the Android device lacks an active internet connection. Once connectivity is re-established, however, any cached data will be synchronized with the remote storage service. As a final test of the application, therefore, log into your Google Drive account in a browser window, navigate to the saved file and click on it to view the content, which should, all being well, contain the text saved by the application.

Summary

This chapter has worked through creating an example Android Studio application in the form of a very rudimentary text editor designed to use cloud-based storage to create, save, and open files using the Android Storage Access Framework.

 

 

Get the Updated Book

You are reading a sample chapter from an old edition of the Android Studio Essentials – Java Edition book.

Purchase the fully updated Android Studio Jellyfish – Java Edition of this book in eBook or Print format.

The full book contains 92 chapters and over 830 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 


Categories ,