Reading and Writing Files on Android

Working with files is a common task in many Android applications. Whether you need to read from or write to a file, Android provides a robust set of APIs to handle file operations efficiently. This blog post will explore how to perform file I/O operations in Android using Kotlin, including reading from and writing to files, handling different types of storage, and ensuring best practices for file handling.

1. Introduction to File I/O in Android

File I/O (Input/Output) operations allow an application to save data persistently. In Android, you can save files in two main types of storage: internal and external. Each type of storage has its use cases and considerations.

Internal Storage

  • Private to the application.
  • Files stored here are not accessible by other applications.
  • Suitable for storing sensitive data.

External Storage

  • Can be accessed by the user and other applications.
  • Suitable for storing non-sensitive data like media files.
  • Requires permission to read and write.

2. Types of Storage in Android

Internal Storage

Internal storage is ideal for storing private data. Files stored here are sandboxed to the application, ensuring that no other app can access them.

External Storage

External storage is best used for files that the user expects to access directly, such as photos or documents. External storage requires permission checks and is shared across all applications.

3. Writing to Files

Writing to Internal Storage

To write to a file in internal storage, you can use the openFileOutput method. Here’s an example:

import android.content.Context

fun writeToFile(context: Context, fileName: String, data: String) {
    try {
        context.openFileOutput(fileName, Context.MODE_PRIVATE).use { output ->
            output.write(data.toByteArray())
        }
        println("File written successfully!")
    } catch (e: Exception) {
        e.printStackTrace()
        println("Error writing to file: ${e.message}")
    }
}

In this example, Context.MODE_PRIVATE ensures that the file is only accessible by your app.

Writing to External Storage

Writing to external storage requires permission checks and should handle the availability of external storage.

import android.os.Environment
import java.io.File
import java.io.FileOutputStream
import java.io.IOException

fun writeToExternalFile(fileName: String, data: String) {
    if (isExternalStorageWritable()) {
        val file = File(Environment.getExternalStorageDirectory(), fileName)
        try {
            FileOutputStream(file).use { output ->
                output.write(data.toByteArray())
            }
            println("File written successfully to external storage!")
        } catch (e: IOException) {
            e.printStackTrace()
            println("Error writing to file: ${e.message}")
        }
    } else {
        println("External storage is not writable.")
    }
}

fun isExternalStorageWritable(): Boolean {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

Permissions for writing to external storage must be declared in the Android manifest, and runtime permission checks are necessary for devices running Android 6.0 (API level 23) and higher.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Runtime permission check:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
    != PackageManager.PERMISSION_GRANTED) {

    ActivityCompat.requestPermissions(this,
        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
        REQUEST_WRITE_STORAGE)
}

4. Reading from Files

Reading from Internal Storage

Reading a file from internal storage is straightforward using the openFileInput method.

fun readFromFile(context: Context, fileName: String): String? {
    return try {
        context.openFileInput(fileName).use { input ->
            input.bufferedReader().use {
                it.readText()
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

Reading from External Storage

Reading from external storage also requires permission checks. Here’s an example:

fun readFromExternalFile(fileName: String): String? {
    if (isExternalStorageReadable()) {
        val file = File(Environment.getExternalStorageDirectory(), fileName)
        return try {
            file.inputStream().use { input ->
                input.bufferedReader().use {
                    it.readText()
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }
    } else {
        println("External storage is not readable.")
        return null
    }
}

fun isExternalStorageReadable(): Boolean {
    return Environment.getExternalStorageState() in
            setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}

5. Best Practices for File I/O

  • Error Handling: Always handle exceptions that may occur during file operations to avoid crashes.
  • Permissions: Ensure you check for and request the necessary permissions, especially for external storage.
  • Security: Avoid storing sensitive data in external storage. Use internal storage for private data.
  • Efficiency: Avoid performing file I/O operations on the main thread to prevent blocking the UI.

6. Conclusion

File I/O operations are an essential part of Android development, enabling persistent data storage for various use cases. By understanding the different types of storage and implementing best practices, you can ensure efficient and secure file operations in your applications.

In this post, we explored how to read from and write to files in both internal and external storage using Kotlin. With these basics, you can confidently manage file I/O in your Android apps, ensuring data is stored safely and efficiently.