Files: Internal & External Storage (Java)

SharedPreferences is fine for small key/value settings, but RecipeBox also needs to save actual files — most importantly, a photo the user snaps or picks for a recipe's photoUri. Android gives every app two private-ish storage areas for exactly this: internal storage and app-specific external storage. Neither requires the user to grant a runtime permission, and both are wiped clean automatically when the app is uninstalled.

Internal Storage

getFilesDir() — always private, never visible to other apps

vs

App-Specific External

getExternalFilesDir() — on the SD/shared partition, still app-only

vs

Shared / MediaStore

Visible in the user's Gallery, survives uninstall

Internal Storage: getFilesDir()

Every app is given a private directory on internal storage that only that app can read or write — not even the user can browse it without root access, and no other app can touch it. It is automatically deleted the moment the user uninstalls RecipeBox. This is the right place for data that should never leak outside the app: a cached copy of a recipe photo, a temporary export file, or anything sensitive.

// Saving a recipe photo to internal storage
public void saveRecipePhotoInternal(Context context, int recipeId, byte[] photoBytes) {
    String fileName = "recipe_" + recipeId + ".jpg";
    File file = new File(context.getFilesDir(), fileName);

    try (FileOutputStream out = new FileOutputStream(file)) {
        out.write(photoBytes);
        Log.d("RecipeBox", "Saved photo to " + file.getAbsolutePath());
    } catch (IOException e) {
        Log.e("RecipeBox", "Failed to save photo", e);
    }
}
Explanation:
  • context.getFilesDir() returns a File pointing at /data/data/<package>/files.
  • The try-with-resources block automatically closes the stream, even if write() throws.
  • No manifest permission is needed — this directory always belongs to RecipeBox alone.

App-Specific External Storage: getExternalFilesDir()

External storage used to mean the shared SD card, which required a permission and was readable by other apps. Modern Android splits this: app-specific external storage (returned by context.getExternalFilesDir(null)) still lives on the external/shared partition — so it's a good spot for larger files like full-resolution recipe photos — but it is scoped to RecipeBox, requires no storage permission on modern Android, and is deleted automatically on uninstall, just like internal storage. Think of it as a middle ground: bigger and faster to fill than internal storage, but still private to the app.

StoragePrivate to app?Deleted on uninstall?Permission needed?
getFilesDir()YesYesNo
getExternalFilesDir()YesYesNo (modern Android)
Shared / MediaStoreNo — visible to user & other appsNoRuntime permission for legacy access; MediaStore API needs none for writes

Scoped Storage & MediaStore (API 29+)

Since Android 10 (API 29), apps can no longer freely roam the shared external storage the way they once could — this is called scoped storage. If RecipeBox wants to save a photo somewhere the user's own Gallery app will actually show it (rather than hiding it inside RecipeBox's private folder), the correct tool is the MediaStore API, not a raw file path.

// Saving a recipe photo into the shared Pictures collection via MediaStore
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "recipebox_" + recipeId + ".jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/RecipeBox");

Uri collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri photoUri = resolver.insert(collection, values);

try (OutputStream out = resolver.openOutputStream(photoUri)) {
    out.write(photoBytes);
}
Explanation:
  • resolver.insert() asks the system to create a new Gallery entry and hand back a content:// Uri.
  • RecipeBox never touches a raw file path — the system manages where the bytes physically live.
  • No storage permission is required to write a new file this way, even on modern Android.

Reading a File Back: FileInputStream

Whichever directory you chose, reading a file back follows the same pattern in reverse — open an input stream, read the bytes, close the stream. Here is a complete round trip using RecipeBox's app-specific external storage, which is the most common choice for a recipe's photo.

// Save a recipe photo to app-specific external storage
public File saveRecipePhotoExternal(Context context, int recipeId, byte[] photoBytes) throws IOException {
    File dir = context.getExternalFilesDir("recipe_photos");
    if (dir != null && !dir.exists()) {
        dir.mkdirs();
    }
    File file = new File(dir, "recipe_" + recipeId + ".jpg");

    try (FileOutputStream out = new FileOutputStream(file)) {
        out.write(photoBytes);
    }
    return file;
}

// Read it back later, e.g. to display in an ImageView
public byte[] readRecipePhoto(File file) throws IOException {
    try (FileInputStream in = new FileInputStream(file)) {
        byte[] buffer = new byte[(int) file.length()];
        int bytesRead = in.read(buffer);
        Log.d("RecipeBox", bytesRead + " bytes read from " + file.getName());
        return buffer;
    }
}
Explanation:
  • getExternalFilesDir("recipe_photos") gives a subfolder dedicated to photos, keeping things tidy.
  • file.length() tells us exactly how many bytes to allocate for the read buffer.
  • The resulting File's path (or a wrapped content:// Uri) is what gets stored in the Recipe.photoUri field.

Choosing the Right Storage for RecipeBox

RecipeBox needRecommended storageWhy
Recipe photo, private thumbnail cacheApp-specific external (getExternalFilesDir())Larger capacity, still private, auto-cleaned
Small internal cache/export fileInternal (getFilesDir())Guaranteed private, no permission ever needed
Let user see/share recipe photo in GalleryMediaStoreVisible to Gallery and other apps, survives uninstall
User preferences (sort order, theme)SharedPreferencesSimple key/value, not a file at all
Structured recipe data (title, ingredients, steps)Database (next lesson)Needs querying, relationships, and updates — not raw files
Watch out: Never hardcode a raw external path like /sdcard/RecipeBox/photo.jpg. On modern Android that path may not be writable at all under scoped storage — always go through getExternalFilesDir() or MediaStore.
Files are great for photos, but RecipeBox's actual recipe data — title, ingredients, steps, category — is structured and needs querying. The next lesson, SQLiteOpenHelper (Java), covers Android's classic way of storing that structured data in a local database.