9.2. Local filesystem

The file system allows us to save large files of any kind. We need to distinguish whether we want to store those files on the internal storage system or the external storage system (memory cards). This is typically added to our application settings.

If we are only going to access these files from our application, we do not need any special permissions, whether we are going to store them in internal or external storage. Deleting the application deletes all contents of the two storage types. If we use the Device File Explorer and continue to see the files, we must use the Synchronize context menu option to see that they have actually been deleted.

9.2.1. Basic operations

To implement basic file operations, we use the class File, filesDir or getExternalFilesDir. filesDir provides us with the root path to the file area in the internal memory of our application. Finally, getExternalFilesDir provides us with the root path to the file area of our application in the external memory.

val directory = filesDir

val fileName = "data1.txt"
var file = File(directory, fileName)

if(!file.exists()) {
   val isNewFileCreated: Boolean = file.createNewFile()
}

file.writeText("this is the text")

By default, method writeText() uses UTF-8 encoding.

We can see that the method fileDir returns the value: /data/user/0/com.uoc.localstorage/files

Root of the internal memory filesystem. Root of the internal memory filesystem.
Source: Javier Salvador (Original image) License: CC BY-NC-ND 4.0

If we want to write to external storage, we would do the same but using getExternalFilesDir instead of filesDir. In that case, in the emulator, we will see that it returns a path similar to this one: /storage/emulated/0/Android/data/com.uoc.localstorage/files.

Root of the external memory filesystem. Root of the external memory filesystem.
Source: Javier Salvador (Original image) License: CC BY-NC-ND 4.0

We can use the File class to write binary files using the method writeBytes. We can also associate a FileOutputStream.

var phos: FileOutputStream = FileOutputStream(file)
var text:String = "this is the text"
fos.write(text.toByteArray(Charsets.UTF_8))
fos.close()

If instead of filesDir or getExternalFilesDir we use cacheDir or externalCacheDir, we will be creating the files in a temporary file area. On internal storage, the cache is created in this directory: /data/user/0/com.uoc.localstorage/cache

Temporary files can be destroyed by the user from the system application management area. This option can be found in the followiing path: Settings/Apps/<our app>/Storage/Clear Cache

Learn more: The File class has methods that allow us to perform all the operations we need with files, directories, path comparison, … You can find more information about this class here: https://developer.android.com/reference/java/io/File.

9.2.2. Encrypting text files

We create the keys and type of encryption that we will use to encrypt our files. We use this information to create an encryptedFile that will allow us to encrypt the File.

val masterKey:MasterKey = MasterKey.Builder(this, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
   .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
   .build();

val file: File = File(directory, "secret_data")
val encryptedFile = EncryptedFile.Builder(this,
   file,
   masterKey,
   EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

Now we can use FileOutputStream and FileInputStream to read and write texts. As you will see, we write and using bytes.

var text:String = "this is the text"
// write to the encrypted file
val encryptedOutputStream: FileOutputStream = encryptedFile.openFileOutput()
encryptedOutputStream.write(text.toByteArray(Charsets.UTF_8))
encryptedOutputStream.close()
// read the encrypted file
val encryptedInputStream: FileInputStream = encryptedFile.openFileInput()
var bytes:ByteArray = encryptedInputStream.readBytes()
encryptedInputStream.close()
var text2:String = String(bytes, charset("UTF-8"))