Search results for

All search results
Best daily deals

Affiliate links on Android Authority may earn us a commission. Learn more.

How to store data locally in an Android app

We dig into the different options available to store data locally on an android device, complete with sample source code.
By

Published onOctober 3, 2022

Almost every app we use or develop has to store data for one purpose or another. It’s not all the same data, either — some apps need access to settings, images, and much more. The big question is how to manage this data so that your device can grab only what it needs. Luckily for developers, Android is full of ways to store data, and we’re here to run you through how they work.

See also: Making an app with no programming experience: What are your options?

For this article, we’ll discuss the different data storage techniques available to Android developers, along with sample code to get you started or to refresh your memory.

Using Shared Preferences

Programming C Coding Developer

Shared Preferences is the way to go if you’re saving primitive data as key-value pairs. It requires a key, which is a String, and the corresponding value for the said key. The value can be any of the following: a boolean, float, int, long, or another string.

Your Android device stores each app’s Shared Preferences inside of an XML file in a private directory. Apps can also have more than one Shared Preferences file, and they’re ideally used to store app preferences.

See also: Android Studio 4.1 – New features for devs

Before you can store data with shared preferences, you must first get a SharedPreferences object. There are two Context methods that you can use to retrieve a SharedPreferences object.

Code
SharedPreferences sharedPreferences = getPreferences(MODE_PRIVATE);

For when your app will have a single preferences file, and

Code
SharedPreferences sharedPreferences = getSharedPreferences(fileNameString, MODE_PRIVATE);

for when your app could have multiple preferences files, or if you prefer to name your SharedPreferences instance.

On getting the SharedPreferences object, you then access its Editor using the edit() method. To actually add a value, use the Editor’s putXXX() method, where XXX is one of Boolean, String, Float, Long, Int, or StringSet. You can also remove a key-value preference pair with remove().

Finally, make sure to call the Editor’s commit() method after putting or removing values. If you don’t call commit, your changes will not be persisted.

Code
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(keyString, valueString);
editor.commit();

For our sample app, we allow the user to specify a SharedPreferences filename. If the user specifies a name, we request for the SharedPreferences with that name; if not, we request the default SharedPreference object.

Code
String fileNameString = sharedPreferencesBinding.fileNameEditView.getText().toString();
SharedPreferences sharedPreferences;
if(fileNameString.isEmpty()) {
    sharedPreferences = getPreferences(MODE_PRIVATE);
}
else {
    sharedPreferences = getSharedPreferences(fileNameString, MODE_PRIVATE);
}

Unfortunately, there is no way to get a single list of all SharedPreferences files stored by your app. Instead, you will need a static list or access to the SharedPreferences name if you’re storing more than one file.

You could also save your SharedPreferences names in the default file. If you need to store user preferences, you may want to use the PreferenceActivity or PreferenceFragment command. Just remember that they both use Shared Preferences, too.


Using internal storage

Android development tools

There are plenty of times where you may need to persist data, but you find Shared Preferences too limiting. For example, you may need to persist objects or images in Java. You might also need to persist your data logically with the file system hierarchy. This is where internal storage comes in. It is specifically for when you need to store data on the file system, but you don’t want other apps or users to have access.

This data storage is so private, in fact, that it’s deleted from the device as soon as you uninstall your app.

Using internal storage is similar to saving with any other file system. You can get references to File objects, and you can store data of virtually any type using a FileOutputStream. What sets it apart is the fact that its contents are only accessible by your app.

To get access to your internal file directory, use the Context getFilesDir() method. To create (or access) a directory within this internal file directory, use the getDir(directoryName, Context.MODE_XXX) method. The getDir() method returns a reference to a File object representing the specified directory, creating it first if it doesn’t exist.

Code
File directory;
if (filename.isEmpty()) {
    directory = getFilesDir();
}
else {
    directory = getDir(filename, MODE_PRIVATE);
}
File[] files = directory.listFiles();

In the sample above, if the user-specified filename is empty, we get the base internal storage directory. If the user specifies a name, we get the named directory, creating first if needed.

To read files, use your preferred file reading method. For our example, we read the complete file using a Scanner object. To read a file that’s directly within your internal storage directory (not in any subdirectory), you can use the openFileInput(fileName) method.

Code
FileInputStream fis = openFileInput(filename);
Scanner scanner = new Scanner(fis);
scanner.useDelimiter("\\Z");
String content = scanner.next();
scanner.close();

Similarly, to access a file for writing directly within the Internal Storage directory, use the openFileOutput(fileName) method. To save files, we use the FileOutputStream write.

Code
FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE);
fos.write(internalStorageBinding.saveFileEditText.getText().toString().getBytes());
fos.close();

As you can see in the image above, the file path is in a folder not accessible by the file manager or other apps. The only exception to this will be if you have a rooted device.


External Storage

Google has made a few key changes to external storage, beginning with Android 10 and continuing in Android 11. To give users better control over their files and cut down on clutter, apps now have scoped access to external storage by default. This means that they can tap into the specific directory on external storage and the media that the app creates.

For more information about requesting scoped directory access, check out this Android developer tutorial.

If your app tries to access a file that it did not create, you will have to permit it to do so every single time. Data you store outside of select folders will also disappear if you delete your app.

Apps are expected to store files in one of two app-specific locations designed for the app’s specific persistent files and cached files, respectively. To access these locations, the app must verify the storage is available (which is not guaranteed, as it is for internal storage). The volume’s state can be queried using:

Code
Environment.getExternalStorageStage().

If MEDIA_MOUNTED is returned, that means you can read and write files to external storage. You will find a number of predefined directories that should aid with logical storage and prevent clutter. These include the likes of DIRECTORY_DOCUMENTS and DIRECTORY_MOVIES.

You can read a full explanation of how to use scoped storage here.


SQLite database

Typing Writing Coding Keyboard

Finally, Android provides support for apps to use SQLite databases for data storage. The databases you create remain specific to your app and can only be accessed inside your app. Of course, you should have at least some knowledge of SQL before you attempt to store data with an SQLite database.

See also: A guide to Android app development for complete beginners in five easy steps

We’ll discuss each of these in turn, and we use data binding techniques for our sample code. Android provides complete support for SQLite databases. The recommended way of creating SQLite databases is to subclass the SQLiteOpenHelper class and override the onCreate() method. For this sample, we create a single table.

Code
public class SampleSQLiteDBHelper extends SQLiteOpenHelper {
    private static final int DATABASE_VERSION = 2;
    public static final String DATABASE_NAME = "sample_database";
    public static final String PERSON_TABLE_NAME = "person";
    public static final String PERSON_COLUMN_ID = "_id";
    public static final String PERSON_COLUMN_NAME = "name";
    public static final String PERSON_COLUMN_AGE = "age";
    public static final String PERSON_COLUMN_GENDER = "gender";

    public SampleSQLiteDBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL("CREATE TABLE " + PERSON_TABLE_NAME + " (" +
                PERSON_COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                PERSON_COLUMN_NAME + " TEXT, " +
                PERSON_COLUMN_AGE + " INT UNSIGNED, " +
                PERSON_COLUMN_GENDER + " TEXT" + ")");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + PERSON_TABLE_NAME);
        onCreate(sqLiteDatabase);
    }
}

To add data:

Code
private void saveToDB() {
        SQLiteDatabase database = new SampleSQLiteDBHelper(this).getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(SampleSQLiteDBHelper.PERSON_COLUMN_NAME, activityBinding.nameEditText.getText().toString());
        values.put(SampleSQLiteDBHelper.PERSON_COLUMN_AGE, activityBinding.ageEditText.getText().toString());
        values.put(SampleSQLiteDBHelper.PERSON_COLUMN_GENDER, activityBinding.genderEditText.getText().toString());
        long newRowId = database.insert(SampleSQLiteDBHelper.PERSON_TABLE_NAME, null, values);

        Toast.makeText(this, "The new Row Id is " + newRowId, Toast.LENGTH_LONG).show();
    }

To read data:

Code
private void readFromDB() {
        String name = activityBinding.nameEditText.getText().toString();
        String gender = activityBinding.genderEditText.getText().toString();
        String age = activityBinding.ageEditText.getText().toString();
        if(age.isEmpty())
            age = "0";

        SQLiteDatabase database = new SampleSQLiteDBHelper(this).getReadableDatabase();

        String[] projection = {
                SampleSQLiteDBHelper.PERSON_COLUMN_ID,
                SampleSQLiteDBHelper.PERSON_COLUMN_NAME,
                SampleSQLiteDBHelper.PERSON_COLUMN_AGE,
                SampleSQLiteDBHelper.PERSON_COLUMN_GENDER
        };

        String selection =
                SampleSQLiteDBHelper.PERSON_COLUMN_NAME + " like ? and " +
                        SampleSQLiteDBHelper.PERSON_COLUMN_AGE + " > ? and " +
                        SampleSQLiteDBHelper.PERSON_COLUMN_GENDER + " like ?";

        String[] selectionArgs = {"%" + name + "%", age, "%" + gender + "%"};

        Cursor cursor = database.query(
                SampleSQLiteDBHelper.PERSON_TABLE_NAME,   // The table to query
                projection,                               // The columns to return
                selection,                                // The columns for the WHERE clause
                selectionArgs,                            // The values for the WHERE clause
                null,                                     // don't group the rows
                null,                                     // don't filter by row groups
                null                                      // don't sort
        );

        Log.d("TAG", "The total cursor count is " + cursor.getCount());
        activityBinding.recycleView.setAdapter(new MyRecyclerViewCursorAdapter(this, cursor));
    }

SQLite storage offers the power and speed of a full-featured relational database to your app. If you intend to store data that you may later query, you should consider using the SQLite storage option.


Saving Cache Files

Android developer news

Android also provides a means to cache some data rather than store it permanently. You can cache data in either internal storage or external storage. Cache files may be deleted by the Android system when the device is low on space.

See also: How to clear app cache on the Samsung Galaxy S10

To get the internal storage cache directory, use the getCacheDir() method. This returns a File object that represents your app’s internal storage directory. You can access the external cache directory with the similarly named getExternalCacheDir().

Although the Android device can delete your cache files if needed, you should not rely on this behavior. Instead, you should maintain the size of your cache files yourself and always try to keep your cache within a reasonable limit, like the recommended 1MB.


So, which method should you use?

How to install Java

There are advantages and disadvantages to using each of the different storage methods available. Shared Preferences is the easiest to use, especially if you want to store discrete primitive data types. However, internal and external storage is best for storing files such as music, videos, and documents, while SQLite wins if you need to perform fast searches and queries on your data.

Ultimately, the storage method you choose should depend on your data types, the length of time you need the data, and how private you want the data to be.

You can still grab the source code for the app above on GitHub if you’re hoping to practice for yourself. Feel free to use it as you see fit, and don’t hesitate to reach out in the comments below.

Read next: Python vs Java: Which language should you learn and what are the differences?

You might like