The Android SDK designates persistent storage space as “external”. This should be confused with physical SD cards, which many contemporary Android devices support. The term “external storage” is used in contrast to “internal storage.” Internal is private to the app, and external is public-facing and visible to the user. You can read and write to internal files without any specific permissions, but to work with external storage the device user has to grant that permission.
- View Offline
- [Instructor] The Android STK designates a portion of persistent storage space as external. This shouldn't be confused with physical SD cards, which many contemporary Android devices support. The term external storage is used in contrast to internal storage. Internal is private to the app, and external is public-facing and visible to the user. You can read and write to internal files without any specific permissions, but to work with external storage the device user has to grant you that permission.
To ask for the permission, go to the manifest file, and add a couple of uses-permission tags. I'll type EXT to get to the right place, and I'll choose READ_EXTERNAL_STORAGE. Then, I'll do that one again, and I'll choose WRITE_EXTERNAL_STORAGE. Now, on older versions of Android that would be enough to get the permission. When the user installs the app, the permission would be requested upon installation, and they can grant it or deny it, in which case, they won't be able to install the app.
But, starting with Android six, users can install the app, and then the app has to ask for permission at runtime. READ_EXTERNAL_STORAGE is granted automatically, but for WRITE_EXTERNAL_STORAGE to be granted you have to ask for it, and the user has to grant it. Now, this takes a little bit of Java code, so I've created a gist that we can copy from. The short URL is git.io/vi9mp, and I'll select all of this code and copy it to the clipboard.
And then, returning to Android Studio, I'll paste it down here after all of my event handlers. Now, in some cases, you'll be prompted to add a lot of imports. But if that doesn't happen, like it didn't for me, you'll need to go through add the imports manually. Go to each of the classes that has an error indicator, and use an intention action, and import the class. I'll do that for Environment, for ContextCompat, for Manifest, for ActivityCompat, and for PackageManager.
There are couple other things I need to do here to satisfy this code. First, there's a constant that's expected named REQUEST_PERMISSION_WRITE. This is a request code, similar to the kind of request code you use when you use startActivityForResult. I'll use an intention action and create the constant field, and I'll set it to a numeric value of anything I like. Then, I'll jump down here and see that I need to import the NonNull setting. And then, I'll go down here to permissionGranted.
This is a boolean value, and I'm going to declare it as a field of the class, so I can set it in one place and read it in another. Then, I'll press F2 and make sure that I've handled all of the errors. Now, when the app starts, I'm going to ask for permission like this. The entry method is called checkPermissions. First, it looks to see whether you have access to external storage or not. And I have an error in this code. I'm calling the same method twice, so I'll change this to isExternalStorageWritable.
If either of those methods returns false, then you can't do any of this. That means you're on a device that doesn't let you write to external storage. That won't happen on most phones and tablets, but you need to include the code. Next, there's code to check for permissions. And, again, specifically we're looking for the WRITE_EXTERNAL_STORAGE permission. This permissionCheck integer will match a constant, and if it doesn't match PERMISSION_GRANTED, then we need to request permissions at runtime. And that's what this requestPermissions method does.
It passes in an array of permissions that are being requested and a request code. And, a dialogue is going to be displayed by the application framework. This is asynchronous, so you need a request code to track the operation. I'm using it here, and then I'm using it again in onRequestPermissionsResult. After the user has clicked the appropriate button on the dialogue, this method will be executed, and then you can look at the results. The first item in the grantResults array will represent the one and only permission you requested.
And if it matches the constant permissionGranted, then I'm setting my boolean field to true and displaying a message to the user saying it's all good. So, now, I want to make sure I only try to do file operations if the permission has been granted. So, I'm going to go to the first file operation. That's the method onCreateButtonClick. And I'll ask whether permission is granted. And, if not, I'll call the checkPermissions method and return from this method. Now, I'll copy that code, and I'll paste it here in the onReadButtonClick method, and then I'll do the same thing in onDeleteButtonClick.
If I don't have the permissions, I can't do any of this. Next, I need to make some changes in how I'm writing and deleting the file, because I want to work with external storage. In order to get external storage information, use a method of the environment class named getExternalStorageDirectory. And I'm going to wrap that up in a method, here, that I'll call getFile. It'll return a file object, and I'll start with a return statement and new File.
And I'll use two arguments, a file object representing my external storage directory, and a string, which will be the name of the file that I want to work with. Start with Environment.getExternalStorageDirectory and then my constant, FILE_NAME. And I'll close up this code to make it a little bit more readable. Next, I'll replace all references to the file object with calls to this method. I'll do that in the onCreateButtonClick method, and instead of saying new File(FILE_NAME), I'll call the getFile method.
And I'll do the same thing in the onDeleteButtonClick method. And, finally, to work with these external files, I need to change the way I'm creating and deleting them. I'll go back to my create method. And, here, I'm creating my fileOutputStream by calling openFileOutput. That's the correct approach for internal storage files, but for external storage just use the constructor for the class. I'll create a new fileOutputStream and pass in the file object.
And the code is different for deleting a file as well. The deleteFile method that I'm currently using is for internal storage, and instead I'll use Java's file.delete method. And, now, I'm working with the external storage files. I'm going to stop the application in the emulator, make sure I'm starting from scratch, then I'll run it again. So, now, before I test my code, I'm going to clear the application's internal storage. I'll go to my application list, and I'll click and hold on Android and Files and drag it to App Info.
And then, I'll wipe out any existing data. Then, I'll go back to the application list, and I'll click on the button again. And when the app appears, I get the question, do you want to allow the application to access the files? I'll click Allow, and I see that external storage permission has been granted. Then, I'll click the Create button, and I see that the file has been written to storage. I'll go back to Android Studio, and as I've done before, I'll open up the Android Device Monitor application. Now, the external storage files in an emulator are in the storage directory.
That's not going to be true for every Android device. This location can vary from vendor to vendor. But I'll go to storage, emulated, 0, and there's my file. And, again, this file will be visible to file manager apps and other processes that have access to the device's storage. There's a lot more to learn about working with external storage on Android. For example, all of these subdirectories under the external storage directory can be referenced with the get external files dir method and a constant that points to one of the subdirectories.
The rules for permissions when working with these directories can differ. So, check the Android documentation, but it's important to understand that anything in this area is visible to the outside world.
- Modeling data in POJO classes
- Customizing a ListView item display
- Displaying data in a RecyclerView
- Creating a custom array adapter
- Managing shared preferences with Java
- Creating and importing JSON data files
- Accessing SQLite from the command line
- Retrieving data with SQLite queries